From 49ca4728145ea42b07faaf38bdb0063978256555 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 21:56:51 +0000 Subject: [PATCH] feat: support PHP 8.4 and 8.5 in parallel Widen php to ^8.4 and require z-engine as 8.4.x-dev || 8.5.x-dev so Composer resolves the engine-bindings line matching the running minor. Run tests and PHPStan on both minors in CI, and update the README and CLAUDE.md to describe the parallel-minor policy instead of the 8.4 pin. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Y5XNnAjcG8xSxun92V23Sm --- .github/workflows/ci.yml | 25 ++++++++++++-------- CLAUDE.md | 50 ++++++++++++++++++++++------------------ README.md | 10 ++++---- composer.json | 6 +++-- 4 files changed, 52 insertions(+), 39 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c254511..e3be3e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,21 +9,24 @@ on: permissions: contents: read -# This library targets PHP 8.4 only - it rides z-engine's engine bindings. -env: - PHP_MINOR: '8.4' - +# z-engine tracks one PHP minor per release line; the "8.4.x-dev || 8.5.x-dev" +# constraint lets Composer resolve the line matching each runner's PHP, so the +# suite runs on PHP 8.4 and 8.5 in parallel. jobs: tests: - name: Tests + name: Tests (PHP ${{ matrix.php }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ['8.4', '8.5'] steps: - uses: actions/checkout@v7 - name: Set up PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ env.PHP_MINOR }} + php-version: ${{ matrix.php }} extensions: ffi ini-values: ffi.enable=1, zend.assertions=1, opcache.jit=off coverage: none @@ -35,13 +38,17 @@ jobs: run: composer test static-analysis: - name: PHPStan (level max) + name: PHPStan (level max, PHP ${{ matrix.php }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ['8.4', '8.5'] steps: - uses: actions/checkout@v7 - uses: shivammathur/setup-php@v2 with: - php-version: ${{ env.PHP_MINOR }} + php-version: ${{ matrix.php }} extensions: ffi ini-values: ffi.enable=1, zend.assertions=1, opcache.jit=off coverage: none @@ -55,7 +62,7 @@ jobs: - uses: actions/checkout@v7 - uses: shivammathur/setup-php@v2 with: - php-version: ${{ env.PHP_MINOR }} + php-version: '8.4' extensions: ffi ini-values: ffi.enable=1, zend.assertions=1, opcache.jit=off coverage: none diff --git a/CLAUDE.md b/CLAUDE.md index 8101b23..9944bc8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,20 +7,22 @@ to install `create_object`, `do_operation` and `compare` handlers directly onto `Matrix` class entry through PHP FFI. The matrix arithmetic underneath is ordinary, readable PHP; the only unusual part is how the engine is convinced to call it. -## The one rule that is non-negotiable: the PHP version is pinned - -`composer.json` requires **`php: ~8.4.0`** — an exact minor, not a floor. z-engine -reads engine structures (`zend_class_entry`, `zval`, `zend_object_handlers`) by byte -offset, and those offsets change on every PHP minor release. Running against the -wrong minor does not throw a nice exception; it reads and writes the wrong memory. - -`ZEngine\Core::init()` (called from `bootstrap.php`) enforces the match and aborts -with a clear message. **Never "fix" an initialization failure by loosening the PHP -constraint, skipping `Core::init()`, or defeating the guard.** If the environment's -PHP does not satisfy `~8.4.0`, the environment is wrong — say so and stop. - -The same applies in reverse: z-engine's own FFI definitions live on per-minor -branches, so this package must consume the z-engine line built for PHP 8.4. +## The one rule that is non-negotiable: PHP minor and z-engine line move together + +`composer.json` requires **`php: ^8.4`**, and PHP 8.4 and 8.5 are supported **in +parallel** — each minor riding its own z-engine line. z-engine reads engine +structures (`zend_class_entry`, `zval`, `zend_object_handlers`) by byte offset, and +those offsets change on every PHP minor release. Running against the wrong minor +does not throw a nice exception; it reads and writes the wrong memory. + +That is why z-engine is required as **`8.4.x-dev || 8.5.x-dev`**: Composer resolves +the line matching the running PHP (the `8.4` branch on PHP 8.4, `master` — aliased +`8.5.x-dev` — on PHP 8.5). `ZEngine\Core::init()` (called from `bootstrap.php`) +enforces the exact match and aborts with a clear message. **Never "fix" an +initialization failure by loosening the constraints past the minors z-engine has +definitions for, skipping `Core::init()`, or defeating the guard.** If the +environment's PHP does not satisfy `^8.4` (8.4 or 8.5), the environment is wrong — +say so and stop. ## Running tests @@ -139,7 +141,7 @@ tests/Functional/*.phpt the functional suite, one behaviour per file phpunit.xml.dist PHPUnit 12 config (suite points at tests/, suffix .phpt) phpstan.dist.neon static analysis config, level max .php-cs-fixer.dist.php coding standards config (PER-CS2.0) -.github/workflows/ci.yml jobs: tests, static-analysis, coding-standards — PHP 8.4 +.github/workflows/ci.yml jobs: tests, static-analysis, coding-standards — PHP 8.4 and 8.5 ``` `src/Matrix.php` is the whole library. There is no framework here to hide behind: a @@ -152,7 +154,7 @@ at once. are **static**. They do throw — `InvalidArgumentException` for a dimension mismatch, `LogicException` for an operand combination the class does not implement — but be precise about what that means for a caller: these hooks run - inside an **FFI callback**, and PHP 8.4 does not let an exception cross that + inside an **FFI callback**, and PHP does not let an exception cross that boundary. The engine reports the exception and then aborts with `Fatal error: Throwing from FFI callbacks is not allowed`. The script dies with exit code 255; a userland `try`/`catch` around `$a + $b` **will not catch it**. @@ -177,7 +179,7 @@ at once. feat(matrix): support element-wise exponentiation by scalar fix(bootstrap): install create_object handler before do_operation test(tests): cover division by zero -ci: run the suite on PHP 8.4 with ffi.enable=1 +ci: run the suite on PHP 8.4 and 8.5 with ffi.enable=1 docs: rewrite the README in the z-engine style ``` @@ -188,11 +190,13 @@ proposing a change rather than hand-formatting. ## Dependency policy -- `lisachenko/z-engine` is required as **`dev-master`**. There is no stable tag with - PHP 8.4 support yet, so the root `composer.json` also carries +- `lisachenko/z-engine` is required as **`8.4.x-dev || 8.5.x-dev`** — one dev line + per supported PHP minor, resolved by Composer to match the running PHP. Those are + development branches, so the root `composer.json` also carries `"minimum-stability": "dev"` with `"prefer-stable": true` — Composer resolves development stability only at the root level, so consumers need the same pair. -- PHP stays pinned at `~8.4.0`, in lockstep with the z-engine line this package - tracks. Bumping one without the other is never correct. -- When z-engine ships a stable PHP 8.4 release, both the constraint and the - root stability flags should be tightened in a single change. +- PHP stays at `^8.4`, in lockstep with the set of z-engine lines this package + tracks: a new PHP minor is added here only together with the z-engine line built + for it, and never one without the other. +- When z-engine ships stable releases for the supported minors, the constraint and + the root stability flags should be tightened in a single change. diff --git a/README.md b/README.md index 1f56447..3aa7450 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![CI](https://img.shields.io/github/actions/workflow/status/lisachenko/native-php-matrix/ci.yml?branch=master&label=CI)](https://github.com/lisachenko/native-php-matrix/actions/workflows/ci.yml) [![GitHub release](https://img.shields.io/github/release/lisachenko/native-php-matrix.svg)](https://github.com/lisachenko/native-php-matrix/releases/latest) -[![PHP Version](https://img.shields.io/badge/php-8.4-8892BF.svg)](https://php.net/) +[![PHP Version](https://img.shields.io/badge/php-8.4%20%7C%208.5-8892BF.svg)](https://php.net/) [![License](https://img.shields.io/packagist/l/lisachenko/native-php-matrix.svg)](https://packagist.org/packages/lisachenko/native-php-matrix) [![PHPStan](https://img.shields.io/badge/PHPStan-level%20max-brightgreen.svg)](https://phpstan.org/) @@ -39,7 +39,7 @@ The trick is [lisachenko/z-engine](https://github.com/lisachenko/z-engine), whic Every operation returns a **new** `Matrix`; the class is `final` and its state is `readonly`, so nothing is ever mutated in place. The constructor validates that you passed a rectangular list of rows holding only `int`/`float` cells and raises a catchable `InvalidArgumentException` when it does not. -Operator-level failures are a different story, and it is worth being blunt about it: a dimension mismatch (`InvalidArgumentException`) or an unimplemented operand combination such as `$a - 2` (`LogicException`) is raised *inside an FFI callback*, and PHP 8.4 does not allow an exception to cross that boundary. The engine prints the exception and then halts with `Fatal error: Throwing from FFI callbacks is not allowed`. You cannot `try`/`catch` it — check your dimensions before you multiply. +Operator-level failures are a different story, and it is worth being blunt about it: a dimension mismatch (`InvalidArgumentException`) or an unimplemented operand combination such as `$a - 2` (`LogicException`) is raised *inside an FFI callback*, and PHP does not allow an exception to cross that boundary. The engine prints the exception and then halts with `Fatal error: Throwing from FFI callbacks is not allowed`. You cannot `try`/`catch` it — check your dimensions before you multiply. Generics are honest about arithmetic: `Matrix` divided by `2` is a `Matrix`, because PHP's `/` widens. Nothing pretends to preserve `T` where the maths does not. @@ -55,10 +55,10 @@ The maths is plain PHP. The magic is only in getting the engine to call it. ## Requirements -- **PHP `~8.4.0`** — an exact minor, not a floor. Z-Engine reads engine structures by byte offset and those offsets change on every PHP minor release; `Core::init()` refuses to boot on a mismatch rather than corrupting memory. +- **PHP `^8.4`** — 8.4 and 8.5 are supported in parallel. Z-Engine reads engine structures by byte offset and those offsets change on every PHP minor release, so each minor rides its own Z-Engine line; `Core::init()` refuses to boot on a mismatch rather than corrupting memory. - **`ext-ffi` enabled**, with `ffi.enable=1` for CLI usage. - **x64, non-thread-safe (NTS)** build — the same platform limitations as [Z-Engine](https://github.com/lisachenko/z-engine#requirements--support-matrix). -- The **matching Z-Engine minor branch**. This package tracks the PHP 8.4 line; mixing a Z-Engine built for another minor is not a configuration choice, it is undefined behaviour. +- The **matching Z-Engine minor branch**. Composer resolves it for you from the `8.4.x-dev || 8.5.x-dev` constraint; mixing a Z-Engine built for another minor is not a configuration choice, it is undefined behaviour. ## Installation @@ -66,7 +66,7 @@ The maths is plain PHP. The magic is only in getting the engine to call it. composer require lisachenko/native-php-matrix:dev-master ``` -Z-Engine ships stable PHP 8.4 releases (`8.4.0` and up), and this package requires it as `~8.4.0` (z-engine minors track PHP minors and are not interchangeable) — no dev dependency there anymore. Until the next native-php-matrix release is tagged, the package itself is still consumed from `dev-master`; Composer only resolves development stability at the **root** level, so your `composer.json` needs: +This package requires Z-Engine as `8.4.x-dev || 8.5.x-dev` — z-engine minors track PHP minors and are not interchangeable, so Composer resolves the line matching your PHP automatically (the `8.4` branch on PHP 8.4, `master` on PHP 8.5). Those are development branches, and the package itself is consumed from `dev-master`; Composer only resolves development stability at the **root** level, so your `composer.json` needs: ```json { diff --git a/composer.json b/composer.json index 8007b12..a0033e9 100644 --- a/composer.json +++ b/composer.json @@ -11,10 +11,12 @@ } ], "require": { - "php": "~8.4.0", + "php": "^8.4", "ext-ffi": "*", - "lisachenko/z-engine": "~8.4.0" + "lisachenko/z-engine": "8.4.x-dev || 8.5.x-dev" }, + "minimum-stability": "dev", + "prefer-stable": true, "require-dev": { "friendsofphp/php-cs-fixer": "^3.75", "phpstan/phpstan": "^2.1",