Skip to content

Feat: PHP 8.4, a test suite and CI - #1

Merged
khanzadimahdi merged 1 commit into
masterfrom
modernize/php-8.4-and-a-test-suite
Aug 16, 2026
Merged

Feat: PHP 8.4, a test suite and CI#1
khanzadimahdi merged 1 commit into
masterfrom
modernize/php-8.4-and-a-test-suite

Conversation

@khanzadimahdi

Copy link
Copy Markdown
Member

Part of the modernization series across the shetabit packages. This one is framework-agnostic, so there is no Laravel matrix — PHP 8.4/8.5 × lowest/highest dependencies.

The rename logic was rewriting its own output

transform() applied the format one pair at a time, each pass running over the result of the pass before it. So a key that had just been renamed was a candidate for the next rename:

data format before after
['a'=>1,'b'=>2] ['a'=>'b','b'=>'a'] ['a'=>2] — the 1 is gone ['a'=>2,'b'=>1]
['a'=>1,'b'=>2,'c'=>3] rotate three ['a'=>3] — two values gone all three rotated
['a'=>1,'b'=>2] ['a'=>'b','b'=>'c'] ['c'=>2] ['b'=>1,'c'=>2]

Two columns that were mixed up could not be swapped back, and a chain of renames ran away. All keys are renamed in one pass now.

Alongside that:

  • A renamed key lost its value to a key that already carried the destination name — ['legacy_total'=>12900,'total'=>0] with ['legacy_total'=>'total'] gave ['total'=>0].
  • Integer keys were renumbered by any rename, because the merge behind it reindexed them: [10=>'ten','ord_id'=>4711,20=>'twenty'] came back with 0 and 1.
  • A lopsided format threw ValueError out of array_combine() in the middle of the library. It is an InvalidFormatException naming both counts now.
  • The function_exists() guard named a different function than the one it guarded — it checked array_slice_assoc before declaring array_splice_assoc, so an application that already had a function of the first name got a fatal error on the second. The whole file is gone (see below).
  • Transform::get() declared an implicitly nullable parameter, which PHP 8.4 deprecates.

Also

  • Transformer::recursive() applies the format to nested arrays, including the arrays inside a list. Off by default, so flat behaviour is unchanged; the package had no nested handling at all before.
  • 66 tests, 100% line coverage of src. PHPStan level 7, no baseline.
  • Three workflows (tests matrix, code style, static analysis), a Dockerfile with pcov and a Makefile.

Breaking

PHP 8.4 minimum (there was no require section at all). Collision semantics changed as described above. from()/to() declare int|string|array and the Transform setters return static. The global array_splice_assoc() and its autoload.files entry are removed — shetabit/transform-request was checked and does not use it. Transformer::replaceKey() (protected) removed. composer.lock removed from the repo, as a library should not lock.

This lands as the next major. shetabit/transform-request pins ^2.0.1|^3.0 and is ready for it.

Verified

composer ci green on PHP 8.4 and 8.5 × lowest and highest (4 runs), rector clean, composer validate --strict passes, random test order green.

🤖 Generated with Claude Code

The package now requires PHP 8.4 and declares it: composer.json had no
require section at all. Every parameter, return value and property of src/
carries a type, and the development tooling was brought up to date --
PHPUnit 11.5/12/13, PHP_CodeSniffer 4 (phpcsstandards, not squizlabs),
PHPStan at level 7 without a baseline, and Rector.

The renaming itself was rewritten, because applying the format one pair at a
time over the result of the pair before it is wrong in several ways. Two keys
could not trade names: ['a' => 'b', 'b' => 'a'] renamed a to b and then
renamed that same b back to a, so ['a' => 1, 'b' => 2] came out as ['a' => 2]
and the value of a was simply gone. A chain ['a' => 'b', 'b' => 'c'] carried
the value of a all the way to c for the same reason. Every key is renamed at
once now. Where two keys would end up with the same name the renamed one
wins, and of two renamed ones the one that comes first in the data.

Two more things went wrong inside the splice that moved a key. It was built
with array_merge(), which renumbers integer keys, so renaming one key of
[10 => 'ten', 'ord_id' => 4711, 20 => 'twenty'] renumbered 10 and 20 to 0 and
1. And a renamed key spliced in ahead of an untouched key that already
carried the destination name lost to it, so ['legacy_total' => 12900,
'total' => 0] renamed with ['legacy_total' => 'total'] came out as
['total' => 0].

The shipped global function is gone. src/Functions/array_slice_assoc.php
asked function_exists('array_slice_assoc') before declaring
array_splice_assoc() -- two different names -- so anything else in the
process declaring array_slice_assoc() left the function the package calls
undeclared and every transformation died with a fatal error, while a second
declaration of array_splice_assoc() itself went unnoticed. Nothing needs it
any more.

A format whose from() and to() do not hold the same number of keys throws an
InvalidFormatException instead of the ValueError of array_combine(), and
every exception of the package now extends TransformerException.
Transform::get() spells its argument TransformerInterface|null, which PHP 8.4
no longer deprecates.

A transformer can be told to be recursive, in which case it applies its
format to every array nested in the data, the arrays of a list included. It
is off by default, so a flat transformation is unaffected.

66 tests cover src completely, split into a Unit and a Feature suite; the
feature tests run realistic nested payloads through the package end to end.
Three GitHub Actions workflows run them on PHP 8.4 and 8.5 against both the
lowest and the highest dependencies, next to the coding style check, the
static analysis and the coverage. A Dockerfile and a Makefile run all of it
without PHP on the machine. composer.lock is no longer committed.

This is a breaking major. shetabit/transform-request requires ^2.0 and has to
widen that constraint before it can be used with this release.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@khanzadimahdi
khanzadimahdi merged commit 881f9a1 into master Aug 16, 2026
7 checks passed
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.

1 participant