You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Turns out readonly class isn't the only 8.1 blocker. I found three more:
ReflectionMethod::hasPrototype() — 8.2 method, used in attribute resolution.
fn(): never => throw ... — arrow fn that only throws is a runtime bug in 8.1, fixed in 8.2.
memory_reset_peak_usage() — 8.2 function, used by the bench plugin.
So just dropping readonly compiles but blows up at runtime.
My plan: don't touch the source. Keep it on 8.2 with readonly and the 8.2 APIs. Instead, in the 8.1 CI job I install Testo as usual (composer install --ignore-platform-req=php), then run the whole installed codebase through Rector's downgrade sets, then run the full test suite against the downgraded copy. Green on 8.1 means 8.1 works, without dragging compatibility shims into the real code.
The downgrade is wired as a reusable GitHub action that lowers a folder to any PHP version: it installs Rector isolated (its own composer.json, so the project's deps stay untouched), rewrites the sources in place, then removes itself. So the flow is: install deps, run the action to downgrade, run tests.
Eventually I want to package the whole thing into one simple action like "run your Testo tests on 8.1" action that hides all of this. Point it at your suite, it sets up the lower runtime, downgrades under the hood, runs Testo, done.
Rector already downgrades readonly class, but not the three cases above, so I took them upstream:
Activity
#[Require]attribute #324roxblnfk commentedon Sep 8, 2026
Turns out
readonly classisn't the only 8.1 blocker. I found three more:ReflectionMethod::hasPrototype()— 8.2 method, used in attribute resolution.fn(): never => throw ...— arrow fn that only throws is a runtime bug in 8.1, fixed in 8.2.memory_reset_peak_usage()— 8.2 function, used by the bench plugin.So just dropping
readonlycompiles but blows up at runtime.My plan: don't touch the source. Keep it on 8.2 with
readonlyand the 8.2 APIs. Instead, in the 8.1 CI job I install Testo as usual (composer install --ignore-platform-req=php), then run the whole installed codebase through Rector's downgrade sets, then run the full test suite against the downgraded copy. Green on 8.1 means 8.1 works, without dragging compatibility shims into the real code.The downgrade is wired as a reusable GitHub action that lowers a folder to any PHP version: it installs Rector isolated (its own
composer.json, so the project's deps stay untouched), rewrites the sources in place, then removes itself. So the flow is: install deps, run the action to downgrade, run tests.Eventually I want to package the whole thing into one simple action like "run your Testo tests on 8.1" action that hides all of this. Point it at your suite, it sets up the lower runtime, downgrades under the hood, runs Testo, done.
Rector already downgrades
readonly class, but not the three cases above, so I took them upstream:fn(): never => throw— [DowngradePhp81] Handle ArrowFunction in DowngradeNeverTypeDeclarationRector rectorphp/rector-downgrade-php#397ReflectionMethod::hasPrototype()— [DowngradePhp82] Add DowngradeReflectionMethodHasPrototypeRector rectorphp/rector-downgrade-php#398memory_reset_peak_usage()— Downgrade: how to handle memory_reset_peak_usage() (no polyfill possible) rectorphp/rector#9890 (can't be polyfilled, so it stays guarded withfunction_exists()in the source for now)WDYT?