veewee/reflecta
Unleash the Power of Optics in your code!
1.0.1
511K downloads/mo
#3944 most downloaded on Packagist
veewee/reflecta
What this package is like to depend on
Last release 2 months ago
29 May 2026
Release timing varies
gaps range from 2 weeks to 8 months
Some releases are documented
notes for 10 of 22 stable releases
Nothing withdrawn
no release was ever pulled
3 years old
22 releases · first in 2023
8 releases in the last 12 months
see the full history below
Release timeline
22 releases · Nov 2023 to May 2026Releases
latest 22-
1.0.129 May 2026Release notes
Open source →What's Changed
- Keep unnecessary artefacts out of releases by @raphaelstolt in #33
New Contributors
- @raphaelstolt made their first contribution in #33
Full Changelog: 1.0.0...1.0.1
-
1.0.027 May 2026Release notes
Open source →What's Changed
Full Changelog: 0.19.0...1.0.0
Upgrading to 1.0.0
This release migrates
IsoandLensfrom the two-parameter form to the
four-parameter STAB form. The change is almost entirely at the type
level. Runtime method signatures are unchanged, so most code keeps working
without edits. You only need to act if you write explicit Psalm/PHPDoc type
annotations for optics, implement the interfaces yourself, or rely on
compose()rejecting empty input.New to STAB? Read the 🧬 STAB optics walkthrough for the
why behind the four parameters.
TL;DR
Before After Iso<S, A>Iso<S, T, A, B>Lens<S, A>Lens<S, T, A, B>IsoInterface<S, A>IsoInterface<S, T, A, B>LensInterface<S, A>LensInterface<S, T, A, B>SandAkeep their old meaning (the whole, and the focus).TandBare
the outgoing counterparts that let a write change the type. If your optic
never changes type on write, repeat the parameters:Lens<S, S, A, A>.
What changed
1. Four type parameters instead of two
IsoandLens(and their interfaces) now carryS,T,A,B:/** @var Lens<Person, Person, string, string> */ // non-type-changing /** @var Lens<Person, AnonymizedPerson, string, HashedName> */ // type-changing
The concrete
Iso/Lensclasses are invariant on all four slots; the
IsoInterface/LensInterfaceare covariant on all four. See the next
section for what that distinction means in practice.2. Invariant classes vs. covariant interfaces
The variance differs between the concrete types and the interfaces, and that is
deliberate:- The concrete
IsoandLensuse@template(invariant). The four slots
must match exactly. This keeps the constructor callables sound:setboth
consumesBand producesT, so the concrete type can't safely vary on
those slots. IsoInterfaceandLensInterfaceuse@template-covariant. A
LensInterface<Cat, Cat, string, string>is accepted where a
LensInterface<Animal, Animal, string, string>is expected, so you can store
and pass optics around by their interface.
So when you want that subtype flexibility, type your stored properties,
parameters, and return types against the interface (LensInterface<...>or
IsoInterface<...>) rather than the concrete class. A property typed as
Lens<Animal, ...>rejects aLens<Cat, ...>; the same property typed as
LensInterface<Animal, ...>accepts it.// invariant: only an exact Lens<Animal,...> fits /** @var Lens<Animal, Animal, string, string> */ // covariant: a LensInterface<Cat,...> is also accepted /** @var LensInterface<Animal, Animal, string, string> */
3. Type-changing write API
The signatures now allow a write to produce a different type:
Lens::set(S, B): TLens::update(S, callable(A): B): TIso::from(B): T
At runtime these take exactly the same arguments as before; only the declared
types widened. Existing non-type-changing usage (B = A,T = S) is
unaffected.4.
compose()accepts empty inputIso\compose()andLens\compose()now takearray<...>instead of
non-empty-array<...>. Composition is a monoid:- empty input → identity optic
- single input → that optic, unchanged
If you previously relied on Psalm flagging an empty
compose()call, that
check is gone.5.
optional()return type widened// before LensInterface<S, A|null> // after LensInterface<S|null, T|null, A|null, B|null>
6.
AdjacentTemplateValidatorremovedThe custom Psalm plugin that detected
compose()boundary mismatches is gone.
Boundary mismatches are now caught by standard Psalm inference from the STAB
signatures. No configuration change is needed; just expect the diagnostics to
come from Psalm core rather than the plugin.
What you need to do
Runtime-only users
Nothing.
set,update,from,composeand friends behave identically at
runtime.If you annotate optics in PHPDoc
Update two-parameter annotations to four. For the common non-type-changing
case, duplicate:-/** @var Lens<Person, string> */ +/** @var Lens<Person, Person, string, string> */ -/** @param IsoInterface<Foo, Bar> $iso */ +/** @param IsoInterface<Foo, Foo, Bar, Bar> $iso */
If your optic genuinely changes type on write, fill in the real
T/B:/** @var Lens<Person, AnonymizedPerson, string, HashedName> */If you implement
IsoInterface/LensInterfaceyourselfAdd the
TandBtemplate parameters and update the method signatures
(set,trySet,update,tryUpdate,from,tryFrom,optional,
compose,inverse,asLens) to match the new interface. See
src/Lens/LensInterface.phpandsrc/Iso/IsoInterface.phpfor the reference
shapes.If you depend on
compose()rejecting empty arraysAdd your own non-empty check before calling
compose()if you still need it.
Verifying your upgrade
Run your static analyzer after bumping the dependency:
composer update veewee/reflecta vendor/bin/psalm
Most fallout shows up as
InvalidArgumentorMismatchingDocblockParamTypeon
optic annotations. Apply the two-to-four parameter expansion above to fix them. - The concrete
-
0.19.026 May 2026 -
0.18.027 Mar 2026 -
0.17.026 Mar 2026 -
0.16.019 Mar 2026 -
0.15.011 Mar 2026Release notes
Open source → -
0.14.014 Oct 2025 -
0.13.006 Feb 2025 -
0.12.024 Jan 2025 -
0.11.019 Dec 2024Nothing published for this version
-
0.10.025 Oct 2024Nothing published for this version
-
0.9.006 Sep 2024Nothing published for this version
-
0.8.118 Jun 2024Nothing published for this version
-
0.8.014 Jun 2024Nothing published for this version
-
0.7.013 Jun 2024Nothing published for this version
-
0.6.012 Jun 2024Nothing published for this version
-
0.5.006 Jun 2024Nothing published for this version
-
0.4.003 May 2024Nothing published for this version
-
0.3.030 Jan 2024Nothing published for this version
-
0.2.003 Dec 2023Nothing published for this version
-
0.1.017 Nov 2023Nothing published for this version