Skip to main content

Release: 0.42 - Life & Everything

Latest update

0.42.0 - Life & Everything

Released 2026-06-06 · GitHub release

Richer typed PHP interop (attributes, enums, named args, by-ref) + lazy iterator-seq + hygienic let-macros + gacela 1.15 fix.

🐛 Fixed

  • if-let/when-let/if-some/when-first: now hygienic, a user binding named temp-sym no longer collides with the macros' internal temporary
  • Quasiquote of an empty list (`()) yields an empty list instead of throwing Index out of bounds
  • str/index-of: returns nil for an empty string instead of a raw PHP ValueError
  • Lexer: error/source-map columns counted in code points, not bytes (correct locations for multibyte source)
  • Printer: structs print with the . separator ((my.ns.point 1 2)) instead of \ (#2255)
  • Docs: function :example outputs now match REPL output, so phel doc and the API reference are accurate
  • phel->php: integer/string map keys now convert instead of throwing getName() on int (#2298)
  • Structs: $struct['field'] array access now accepts a plain PHP string offset (was keyword-only) (#2313, #2319)
  • Dependencies: support gacela-project/gacela 1.15. Gacela 1.15 persists the merged app config (gacela-merged-config.php); Phel now points Gacela's cache at the project-relative .phel/cache (was the shared system temp dir) so one project's srcDirs/config can't leak into another, fixing the packaged phel test/build when run against a user project. Tests isolate the cache per run via a PHPUnit extension

🎉 Added

  • defstruct: a :php block declares inline PHP magic methods (__invoke, __toString, __get, …) with no backing interface. A custom __invoke must stay map-call-compatible (one arg or variadic), enforced with a clear compile-time error
  • phel format: collapses consecutive blank lines to one, and indents more definition/body forms (defstruct, defprotocol, defmethod, reify, doseq, letfn, …) the cljfmt way instead of call-style alignment
  • Docs: the load/in-ns/use/var special forms are now in the API reference and phel doc (a regression test guards against gaps), and core math/predicate functions gained :example/:see-also metadata
  • phel.reflect: class-attributes/method-attributes/property-attributes/attributes read PHP 8 attributes as {:name :args} maps (#2314, #2320)
  • phel.reflect: enum->keyword/keyword->enum/enum-values bridge native PHP enums to keywords and back (#2315, #2321)
  • iterator-seq: lazy seq over a PHP Traversable, pulled one element at a time (#2312, #2318)
  • phel.http: JSON request bodies decode into :parsed-body, plus json-response/html-response builders (#2271)
  • Docs: runnable database-CRUD and Doctrine-entity examples (docs/examples/13/14) plus a maps-not-entities Persistence section in framework-integration.md (#2281, #2282) Richer PHP interop for bridging Phel to typed PHP / framework code (all opt-in, untagged forms unchanged):
  • Generated classes carry opt-in metadata: ^{:php/attr} PHP 8 attributes (class/method/parameter), ^{:tag} typed signatures (incl. union/intersection), ^{:php/doc} PHPDoc, and ^{:php/json}/^{:php/stringable} on structs (#2280, #2289, #2292, #2293, #2294, #2295)
  • defenum native backed enums, and defexception with an optional parent class (#2291, #2297)
  • php/ref passes a local by reference into php/->/php/:: (#2299) and plain PHP function calls like preg_match/sort (#2310, #2316)
  • hydrate/bean bridge a Phel map and a typed PHP object both ways (#2296)
  • PHP 8 named arguments in php/new/php/->/php/:: via the :& marker, e.g. (php/new \App\Mailer :& :host "smtp") (#2311, #2317)

⚖️ Changed

  • Dev: upgraded the test suite to PHPUnit 13 (from 10). Replaced removed/changed APIs (assertContainsOnlyassertContainsOnlyString, multiple expectOutputRegex calls, closure-comparison in one analyzer test). A follow-up will convert the remaining unconfigured mocks to stubs (PHPUnit-notice only, non-failing)
  • Internals: modular-architecture cleanup of src/php/, relocated leaky cross-module value objects to their proper homes and split many oversized analyzer/command/compiler/runtime classes into focused collaborators (pure refactors, no behavior change) (#2261, #2262, #2263, #2264, #2273, #2274, #2275, #2276, #2277, #2278)
  • Tests: RegistryTest/PhelVarTest snapshot and restore the global Registry, fixing order-dependent phel.core failures (#2256)
  • Docs: condensed every docs/ guide (~17% smaller), verified against the runtime, cross-linked to phel-lang.org
  • Docs: collapsed needlessly multiline :doc/docstrings across src/phel/ (418 strings, 57 files) so prose renders on one line in phel doc and the API reference instead of with stray line-break spacing; structured docs (code blocks, aligned key tables, multi-paragraph, indented examples) are preserved (#2340)
  • Quality: repo-wide maintainability pass, docblocks and :doc/:see-also/:example metadata, dead-code/type/naming cleanups, helper extractions, ~40 new unit tests (no behavior change)
  • agent-install: simplified to copying skill files and the .agents/ docs tree; dropped version stamping, --check, --list
  • phel.ai: default chat model is now claude-sonnet-4-6; nearest computes the query magnitude once instead of per item
  • Dropped redundant (:require phel.core) from 14 library namespaces (always preloaded); kept in phel.string, which needs the core/ alias for core/reverse
  • defstruct :php block: the incompatible-__invoke arity error now points at the [this & _] workaround for callers that want a no-meaningful-argument invoke

👥 Contributors

@JesusValeraDev @chemaclass

Full Changelog: v0.41.0...v0.42.0

Downloads

v0.42.0


View release on GitHub