Skip to content

Upgrade the dev toolchain - #4

Merged
rieschl merged 1 commit into
masterfrom
upgrade-dev-toolchain
Aug 5, 2026
Merged

Upgrade the dev toolchain#4
rieschl merged 1 commit into
masterfrom
upgrade-dev-toolchain

Conversation

@MidnightDesign

Copy link
Copy Markdown
Contributor

Why

The static-analysis and test tooling had aged out of the supported PHP range, to the point where this package could not be worked on with a current PHP.

vimeo/psalm ^5.10 caps at 5.26.1, whose last supported runtime is PHP 8.3. On 8.4 it aborts during scanning:

Uncaught InvalidArgumentException: $value must be a scalar.
  in Psalm/Internal/Codebase/ConstantTypeResolver.php:409

Psalm also hard-sets error_reporting(E_ALL) and turns every notice into an exception, so even loading the autoloader blew up on the PHP 8.4 deprecations in thecodingmachine/safe (a transitive infection 0.27 dependency), before Psalm read a single project file. The practical effect: composer update needed --ignore-platform-req=php+ on a PHP 8.4 host, and the gates had to be run in a php:8.3-cli container.

What changed

require-dev only, brought in line with eventjet/adyen, which is already green on 8.4. require is untouched — no eventjet/json, no php constraint change; those belong to #3.

Package Before After Resolved
infection/infection ^0.27.0 ^0.31.2 0.31.9
maglnet/composer-require-checker ^4.6 ^4.16 4.20.0
phpstan/extension-installer ^1.3 ^1.4 1.4.3
phpstan/phpstan ^1.10 ^2.1 2.2.8
phpstan/phpstan-phpunit ^1.3 ^2.0 2.0.18
phpstan/phpstan-strict-rules ^1.5 ^2.0 2.0.12
phpunit/phpunit ^10.2 ^12.3 12.5.33
psalm/plugin-phpunit ^0.18.4 ^0.19.5 0.19.7
vimeo/psalm ^5.10 ^6.13 6.16.1

eventjet/coding-standard and guzzlehttp/guzzle are left alone — their existing constraints already resolve to the current releases (3.19.2 and 7.15.2), so bumping them would be noise.

Fallout and how it was fixed

All of it in test code. src/ is untouched by this PR, so the shipped package is byte-identical.

phpstan 2 → a real invariant violation in TestHttpClient. $map is declared list<array{Matcher, ResponseInterface, positive-int}>, but sendRequest() removed a consumed entry with unset($this->map[$index]), which punches a hole in the array and leaves it a non-list — so the declared type was a lie:

Property TestHttpClient::$map (list<…>) does not accept array<int<0, max>, …>.
💡 array<int<0, max>, …> might not be a list.

Fixed with array_splice($this->map, $index, 1), which removes and renumbers in one step, so the invariant holds. I chose this over widening the property to array<int, …> because the list-ness is the intended design — entries are appended with $this->map[] =. The two differ only in key renumbering, and every reader of $map either iterates it with fresh keys or indexes it with a key taken from that same iteration, so behavior is unchanged. Verified by exercising the paths the suite does not cover: removing the first of two mappings, the maxMatches counter decrementing, and exhaustion — all identical before and after.

psalm 6 → MissingOverrideAttribute on the two setUp() methods and on TestHttpClient::sendRequest(). Added #[Override], matching the convention already used throughout eventjet/adyen.

phpunit 12 → nothing. This package has no data providers to migrate to attributes, and phpunit.xml validates against the 12.x schema as-is.

Also enabled findUnusedPsalmSuppress (as eventjet/adyen does) so a suppression that stops being necessary gets reported instead of quietly rotting. It confirmed the one existing @psalm-suppress in TestHttpClient is still required, so that stays. Drop this line if you'd rather keep the diff to dependencies alone.

No ignores, baseline entries, disabled rules or skipped tests were added.

Verification

Run on the PHP 8.4.11 host, with no --ignore-platform-req and no container — which is the point of the change:

Gate Exit Result
composer update 0 resolves cleanly, no platform override
composer check-deps 0 no unknown symbols
composer cs-check 0 0 of 22 files need fixing
composer phpstan 0 no errors
composer psalm 0 no errors, 100% type inference
composer phpunit 0 6 tests, 7 assertions, 1 skipped (integration test needs OPENAI_API_KEY)
composer infection 0 11/11 mutants killed, 100% MSI, 100% covered MSI

The deprecation noise that previously flooded every command is gone as well.

Why there are no CI checks on this PR

GitHub Actions cannot run for this repo, and that is not caused by this change. eventjet/openai is not in the selected-repositories list for the org secrets CI_PAT, SATIS_PASSWORD, SATIS_USER and SSH_PRIVATE_KEY:

$ gh api repos/eventjet/openai/actions/organization-secrets --jq .total_count
0
$ gh api repos/eventjet/eventjet-adyen/actions/organization-secrets --jq '.total_count, (.secrets[].name)'
4
CI_PAT
SATIS_PASSWORD
SATIS_USER
SSH_PRIVATE_KEY

The shared eventjet/ci workflow requires all four, so runs die before a job is even created — which is why recent runs show 0-second failures. Fixing it means adding this repo to those four secrets in org settings, which needs admin access and is out of scope here. The green signal for this PR is the local 8.4 run above.

For the reviewer

This PR leaves require.php at >=8.2 while require-dev now needs 8.3+ (phpunit 12 and infection 0.31 both require >=8.3). Consumers are unaffected — the published package still installs on 8.2, and #[Override] only appears in autoload-dev test code — but a contributor on PHP 8.2 will not be able to install dev dependencies. #3 raises require.php to >=8.3 and closes that gap, so the inconsistency lasts only for the window between the two merges. Landing #3 promptly after this one, or merging them in the other order, both resolve it.

The static-analysis and test tooling had aged out of the supported PHP
range. vimeo/psalm ^5.10 caps at 5.26.1, whose last supported runtime is
PHP 8.3; on 8.4 it aborts during scanning with "InvalidArgumentException:
$value must be a scalar" while reflecting a core class. Because psalm
also hard-sets error_reporting(E_ALL) and converts every notice into an
exception, even loading the autoloader blew up on the PHP 8.4
deprecations in thecodingmachine/safe, a transitive infection 0.27
dependency. The practical effect was that the package could not be
installed on a PHP 8.4 host without --ignore-platform-req, and the gates
could not be run there at all.

The versions are brought in line with eventjet/adyen, which is already
green on 8.4:

    infection/infection               ^0.27.0  -> ^0.31.2   (0.31.9)
    maglnet/composer-require-checker  ^4.6     -> ^4.16     (4.20.0)
    phpstan/extension-installer       ^1.3     -> ^1.4      (1.4.3)
    phpstan/phpstan                   ^1.10    -> ^2.1      (2.2.8)
    phpstan/phpstan-phpunit           ^1.3     -> ^2.0      (2.0.18)
    phpstan/phpstan-strict-rules      ^1.5     -> ^2.0      (2.0.12)
    phpunit/phpunit                   ^10.2    -> ^12.3     (12.5.33)
    psalm/plugin-phpunit              ^0.18.4  -> ^0.19.5   (0.19.7)
    vimeo/psalm                       ^5.10    -> ^6.13     (6.16.1)

eventjet/coding-standard and guzzlehttp/guzzle are left alone; their
existing constraints already resolve to the current releases.

Fallout, all of it in test code -- src/ is untouched by this change:

phpstan 2 caught a real invariant violation in TestHttpClient. The $map
property is declared list<...>, but sendRequest() removed a consumed
entry with unset(), which punches a hole in the array and leaves it a
non-list. Replaced with array_splice(), which removes and renumbers in
one step so the declared type stays true. The two differ only in key
renumbering, and every reader of $map either iterates it with fresh keys
or indexes it with a key taken from that same iteration, so behaviour is
unchanged. Verified by exercising the paths the suite does not cover:
removing the first of two mappings, the maxMatches counter decrementing,
and exhaustion.

psalm 6 added MissingOverrideAttribute, which fired on the two setUp()
methods and on TestHttpClient::sendRequest(). Added #[Override], matching
the convention already used throughout eventjet/adyen.

phpunit 12 needed nothing: this package has no data providers to migrate
to attributes, and phpunit.xml validates against the 12.x schema as-is.

Also enabled findUnusedPsalmSuppress, as eventjet/adyen does, so a
suppression that stops being necessary is reported instead of quietly
rotting. It confirmed that the one existing @psalm-suppress in
TestHttpClient is still required, so that stays.

No ignores, baseline entries, disabled rules or skipped tests were added.

Verified on the PHP 8.4.11 host with no --ignore-platform-req and no
container: composer update resolves cleanly, and check-deps, cs-check,
phpstan, psalm, phpunit and infection all exit 0 (6 tests, 1 skipped for
the missing OPENAI_API_KEY; infection 11/11 mutants killed, 100% MSI and
100% covered MSI).
@rieschl rieschl changed the title Upgrade the dev toolchain to run on PHP 8.4 Upgrade the dev toolchain Aug 5, 2026
@rieschl
rieschl merged commit 5c41d00 into master Aug 5, 2026
@rieschl
rieschl deleted the upgrade-dev-toolchain branch August 5, 2026 15:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants