Maintenance overhaul: PHP 8.4, z-engine dev-master, modern README, CI, PHPStan generics - #13
Merged
Merged
Conversation
Z-Engine reads Zend Engine structures by byte offset, and those offsets change on every PHP minor release, so the constraint is pinned to the exact minor (~8.4.0) rather than a floor. There is no stable z-engine tag with PHP 8.4 support yet, so it is consumed from dev-master, which requires the root package to carry minimum-stability "dev" with prefer-stable. BREAKING CHANGE: PHP 7.4 and 8.0 are no longer supported; the package now requires PHP ~8.4.0, ext-ffi and lisachenko/z-engine dev-master. Also declares ext-ffi explicitly, adds the test/phpstan/cs:check/cs:fix scripts and enables sort-packages. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01514q9dSHg3UgK6PNdT6Tx1
The class is now final with private readonly state, so a Matrix cannot be mutated after construction and every operation returns a new instance. The constructor validates its input up front instead of trusting the caller: the outer array must be a non-empty list, every row must be a non-empty list of the same length, and every cell must be an int or a float. Previously a ragged or empty array produced a broken object that failed later, far from the cause. Scalar operands are now declared natively as int|float rather than being checked with is_numeric() inside each method. Because the engine can hand a numeric string to the operator hook, __doOperation() funnels operands through a new asScalar() helper that coerces numeric strings, keeping "$matrix * '2'" working under strict_types=1. multiply() extracts the multiplier's columns once and reuses them across every row of the left operand, instead of calling array_column() inside the inner loop; the result is unchanged. Adds PHPStan level-max generics (@template-covariant T of int|float). The annotations stay honest about widening: division and exponentiation can turn a Matrix<int> into a Matrix<int|float>, so operations return Matrix<int|float> rather than pretending T is preserved. bootstrap.php now probes the engine state with isset(Core::$executor) instead of class_exists(Core::class, false). The old guard only asked whether the class was loaded, which says nothing about whether another package had already called Core::init(); $executor is a typed static assigned only by init(), so an uninitialised property is the accurate "nobody has booted the engine" signal. BREAKING CHANGE: Matrix is now final and cannot be extended, its constructor rejects empty, ragged and non-numeric input with InvalidArgumentException, and the scalar methods accept int|float instead of any numeric value. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01514q9dSHg3UgK6PNdT6Tx1
…nit 12
testFailsOnSubtractingIncompatibleMatrices used the "+" operator, so it
asserted the addition failure path and the subtraction guard was never
actually exercised. It now uses "-".
Adds testFailsOnEmptyMatrix and testFailsOnRaggedMatrix to cover the new
constructor validation. These use try/catch because constructor validation is
ordinary userland code; the incompatible-dimension tests cannot, because an
exception thrown from an operator crosses an FFI callback boundary that PHP
8.4 refuses to let it cross, and surfaces as a fatal error instead.
Every .phpt now carries an explicit --INI-- section so the child processes
never depend on the parent's php.ini:
- ffi.enable=1, which cannot be switched on at runtime
- opcache.jit=off, because the JIT rewrites the executor internals the
hooks patch
- error_reporting=E_ALL & ~E_DEPRECATED, because z-engine dev-master still
declares implicitly nullable parameters (ZEngine\Type\OpLine::__construct)
which PHP 8.4 reports as a deprecation. PHPUnit's .phpt runner forces
display_errors=1, so without it that dependency deprecation is prepended
to the captured output of every test and each --EXPECT-- block fails on
noise unrelated to this library. It can be dropped once z-engine declares
those parameters ?Type.
phpunit.xml.dist moves to the PHPUnit 12.5 schema: the coverage/logging blocks
are replaced by <source>, and warnings, notices and deprecations are now
surfaced rather than silently swallowed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01514q9dSHg3UgK6PNdT6Tx1
PHPStan runs at level max over src and bootstrap.php with no baseline. treatPhpDocTypesAsCertain is deliberately left false: the constructor's runtime validation re-checks types the docblocks already promise, and turning it on reports those defensive checks as alreadyNarrowedType errors even though they are the only thing standing between a caller's untyped array and a malformed Matrix. The php-cs-fixer ruleset (PER-CS2.0 plus strict types, ordered imports and minimal operator alignment) mirrors the one used by lisachenko/z-engine, so both projects format identically. .gitignore additionally excludes the PHPUnit and php-cs-fixer caches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01514q9dSHg3UgK6PNdT6Tx1
The old phpunit.yml built a matrix over PHP 7.4 and 8.0 with lowest/highest dependency resolution, none of which can work now that the package is pinned to PHP 8.4 and z-engine dev-master. It is replaced by ci.yml with three jobs: tests, static-analysis and coding-standards. All jobs run on PHP 8.4 with ffi.enable=1 and opcache.jit=off, the settings the library needs to boot at all, and pin actions to current major versions. Coverage is disabled because the suite is .phpt based and no coverage was being published anyway. Dependabot additionally tracks the github-actions ecosystem, so the workflow action versions stay current alongside Composer dependencies. BREAKING CHANGE: CI no longer tests PHP 7.4 or 8.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01514q9dSHg3UgK6PNdT6Tx1
Rewrites the README around what the library actually is: the first userland PHP extension implementing real operator overloading, with no C and no PECL build. Adds z-engine-style badges (the CI badge now points at ci.yml, not the retired workflow), a requirements section that explains why the PHP version is pinned rather than merely stating it, worked usage examples for every supported operator, and a roadmap of the engine handlers not yet implemented. Documents the failure semantics precisely: constructor validation raises a catchable InvalidArgumentException, but a dimension mismatch or an unsupported operand combination is raised inside an FFI callback, which PHP 8.4 turns into a fatal error that userland cannot catch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01514q9dSHg3UgK6PNdT6Tx1
CLAUDE.md records the contract a contributor or agent needs before touching this repository: why the PHP version is pinned and must never be loosened to silence an initialization failure, the three mandatory .phpt INI settings and the reason for each, the hook contracts, and the rule that generics must stay honest about arithmetic widening the cell type. Adds three scoped agent definitions: test-runner (run and interpret the suite, treating a segfault as an engine-level bug rather than a flaky test), code-reviewer (read-only review against the hook, version-pin and generics rules) and phpt-author (write .phpt tests to the house conventions). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01514q9dSHg3UgK6PNdT6Tx1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Full maintenance pass bringing the library from its 2021 state (PHP 7.4/8.0, z-engine 0.9) to the modern z-engine stack.
Dependencies & runtime
php: ~8.4.0,ext-ffi: *,lisachenko/z-engine: dev-master(z-engine master has no branch alias or 8.4-compatible tag yet — tracked in chore: relax the z-engine dev-master pin once a stable 8.4 tag exists #12), rootminimum-stability: dev+prefer-stable: truetest/phpstan/cs:check/cs:fixcomposer scriptsCode (
src/Matrix.php,bootstrap.php)Matrixis nowfinalwithreadonlyproperties and full constructor validation (empty, non-list, ragged, and non-int|floatcells throwInvalidArgumentException) — previouslynew Matrix([])crashedint|floatparameter types on scalar operations; numeric-string operands ($matrix * '2') still work via explicit coercion in the operator hook@template-covariant T of int|float, internal state typednon-empty-list<non-empty-list<T>>; arithmetic results honestly widen toMatrix<int|float>. Level max, no baseline, zero errorsbootstrap.php:!class_exists(Core::class, false)→!isset(Core::$executor)(the actual "already initialized" probe)multiply()now transposes the multiplier once instead of callingarray_column()inside the inner loopTests
testFailsOnSubtractingIncompatibleMatrices.phpt, which was testing+instead of-testFailsOnEmptyMatrix.phptandtestFailsOnRaggedMatrix.phpt.phptcarries an--INI--block (ffi.enable=1,opcache.jit=off, anderror_reportingmasking a z-engine PHP 8.4 deprecation — upstream fix filed as bug: implicitly nullable parameter in OpLine triggers PHP 8.4 deprecation on every FFI test run z-engine#96)phpunit.xml.distmigrated to the PHPUnit 12 schemaCI/CD & tooling
.github/workflows/ci.yml(replacesphpunit.yml):tests,static-analysis,coding-standardsjobs on PHP 8.4 with FFI,checkout@v4/setup-php@v2/composer-install@v3,pull_requesttrigger and least-privilegepermissionsdependabot.ymlnow also watches thegithub-actionsecosystemphpstan.dist.neon,.php-cs-fixer.dist.php(PER-CS2.0, z-engine ruleset),.editorconfigDocs & Claude onboarding
**operator, accurate PHP 8.4/FFI requirements, dev-master install instructions, roadmapCLAUDE.md(modeled on z-engine's AGENTS.md) and.claude/agents/definitions (test-runner,code-reviewer,phpt-author)Roadmap issues registered
#6 (dimension access), #7 (
count()), #8 (foreachiteration), #9 (casts), #10 (var_dumpdebug info), #11 (FFI BLAS backend), #12 (relax dev-master pin)Verification
.phpttests pass on PHP 8.4.19 NTS against z-engine dev-master (0e544dc)+ - * / ** ==, scalar-left multiplication, numeric strings, all validation throws)🤖 Generated with Claude Code
https://claude.ai/code/session_01514q9dSHg3UgK6PNdT6Tx1
Generated by Claude Code