From 223e21fb3bc513117c804961c0618c1f0742f74d Mon Sep 17 00:00:00 2001 From: Marco Wansinck Date: Tue, 16 Jun 2026 16:32:07 +0200 Subject: [PATCH 1/6] chore: modernise dev tooling (Rector, PHPStan 2, PHPUnit 11) Dev-only modernisation; runtime support unchanged (Symfony floor stays ^6.4 LTS, PHP stays ^8.2). Tooling: - Add Rector 2.x with a rector.php targeting PHP 8.2 (the lowest supported version, so generated code never breaks 8.2 consumers) - Bump PHPStan ^1.8.7 -> ^2.1; drop the removed checkMissingIterableValueType / checkGenericClassInNonGenericObjectType params and re-express the same opt-outs via ignoreErrors identifiers (level 9 kept) - Bump PHPUnit ^9.5|^10.0 -> ^11.0 and migrate phpunit.xml.dist (coverage include -> source); test run is now deprecation-free Code (applied by Rector, all PHP 8.2-valid, no behavioural change): - Constructor property promotion in AbsoluteTimeWindow, RelativeTimeWindow, ShipmentStep, Vehicle - Drop a useless intermediate variable in DateTimeUtil (x2) - Remove incorrect @param phpdocs on the supports*() normalizer methods (they receive mixed; the docblocks made PHPStan flag instanceof as always-true) CI: - Run PHPStan and Rector (dry-run) inside the Symfony matrix, so static analysis runs against each Symfony/PHP combination - Remove the dead commented-out PHPStan block (stale .phar path) Verified locally (PHP 8.5): phpunit 3/3, phpstan no errors, rector clean, php-cs-fixer 0 files. --- .github/workflows/ci.yml | 13 ++++++++---- composer.json | 5 +++-- phpstan.neon | 8 ++++++-- phpunit.xml.dist | 12 +++++------ rector.php | 20 +++++++++++++++++++ src/Resource/AbsoluteTimeWindow.php | 8 +------- src/Resource/RelativeTimeWindow.php | 8 +------- src/Resource/ShipmentStep.php | 5 +---- src/Resource/Vehicle.php | 5 +---- .../Normalizer/LocationNormalizer.php | 3 --- .../Normalizer/TimeWindowNormalizer.php | 3 --- src/Util/DateTimeUtil.php | 6 ++---- 12 files changed, 50 insertions(+), 46 deletions(-) create mode 100644 rector.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08c9502..b1d0cc9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,10 +91,15 @@ jobs: composer config extra.symfony.require "${{ matrix.symfony }}.*" composer update --no-progress --no-scripts -# # —— PHPStan —————————————————————————————————————————————————————————— -# - name: PHPStan (PHP Static Analysis) -# run: php vendor/phpstan/phpstan/phpstan.phar --memory-limit=1G - # —— Run tests ————————————————————————————————————————————————————————— - name: Run Tests run: php vendor/bin/phpunit + + # —— PHPStan ——————————————————————————————————————————————————————————— + # Run per Symfony/PHP combination so version-specific type issues surface. + - name: PHPStan (static analysis) + run: php vendor/bin/phpstan analyse --no-progress + + # —— Rector ———————————————————————————————————————————————————————————— + - name: Rector (dry-run) + run: php vendor/bin/rector process --dry-run --no-progress-bar diff --git a/composer.json b/composer.json index 6f35611..a6a43dc 100644 --- a/composer.json +++ b/composer.json @@ -22,8 +22,9 @@ }, "require-dev" : { "friendsofphp/php-cs-fixer": "^3.15", - "phpstan/phpstan": "^1.8.7", - "phpunit/phpunit": "^9.5|^10.0", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^11.0", + "rector/rector": "^2.0", "symfony/var-dumper": "^6.4|^7.4|^8.0" }, "autoload": { diff --git a/phpstan.neon b/phpstan.neon index 8c3efb2..33cedb9 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,5 +3,9 @@ parameters: paths: - src - tests - checkMissingIterableValueType: false - checkGenericClassInNonGenericObjectType: false + ignoreErrors: + # PHPStan 2.0 removed the `checkMissingIterableValueType` and + # `checkGenericClassInNonGenericObjectType` parameters; keep the + # project's original opt-out by ignoring the equivalent identifiers. + - identifier: missingType.iterableValue + - identifier: missingType.generics diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c0e0e76..3c1331a 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,13 +1,13 @@ - - - - ./src/ - - + ./tests/ + + + ./src/ + + diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..ab61176 --- /dev/null +++ b/rector.php @@ -0,0 +1,20 @@ +withPaths([ + __DIR__.'/src', + __DIR__.'/tests', + ]) + // Target the lowest supported PHP version so generated code stays + // compatible with every consumer (composer.json requires php ^8.2). + ->withPhpSets(php82: true) + ->withPreparedSets( + deadCode: true, + codeQuality: true, + typeDeclarations: true, + earlyReturn: true, + ); diff --git a/src/Resource/AbsoluteTimeWindow.php b/src/Resource/AbsoluteTimeWindow.php index acdbe4c..52d2978 100644 --- a/src/Resource/AbsoluteTimeWindow.php +++ b/src/Resource/AbsoluteTimeWindow.php @@ -6,13 +6,7 @@ final class AbsoluteTimeWindow implements TimeWindowInterface { - public \DateTimeImmutable $start; - - public \DateTimeImmutable $end; - - public function __construct(\DateTimeImmutable $start, \DateTimeImmutable $end) + public function __construct(public \DateTimeImmutable $start, public \DateTimeImmutable $end) { - $this->start = $start; - $this->end = $end; } } diff --git a/src/Resource/RelativeTimeWindow.php b/src/Resource/RelativeTimeWindow.php index c41e628..f139411 100644 --- a/src/Resource/RelativeTimeWindow.php +++ b/src/Resource/RelativeTimeWindow.php @@ -6,13 +6,7 @@ final class RelativeTimeWindow implements TimeWindowInterface { - public int $start; - - public int $end; - - public function __construct(int $start, int $end) + public function __construct(public int $start, public int $end) { - $this->start = $start; - $this->end = $end; } } diff --git a/src/Resource/ShipmentStep.php b/src/Resource/ShipmentStep.php index 634779d..7ae55b1 100644 --- a/src/Resource/ShipmentStep.php +++ b/src/Resource/ShipmentStep.php @@ -6,8 +6,6 @@ class ShipmentStep { - public int $id; - public string $description; public Location $location; @@ -23,8 +21,7 @@ class ShipmentStep */ public array $timeWindows; - public function __construct(int $id) + public function __construct(public int $id) { - $this->id = $id; } } diff --git a/src/Resource/Vehicle.php b/src/Resource/Vehicle.php index f3b0046..51b7371 100644 --- a/src/Resource/Vehicle.php +++ b/src/Resource/Vehicle.php @@ -9,8 +9,6 @@ */ final class Vehicle { - public int $id; - public string $profile; public string $description; @@ -52,8 +50,7 @@ final class Vehicle public array $steps; - public function __construct(int $id) + public function __construct(public int $id) { - $this->id = $id; } } diff --git a/src/Serializer/Normalizer/LocationNormalizer.php b/src/Serializer/Normalizer/LocationNormalizer.php index 7048399..77e6fd7 100644 --- a/src/Serializer/Normalizer/LocationNormalizer.php +++ b/src/Serializer/Normalizer/LocationNormalizer.php @@ -21,9 +21,6 @@ public function normalize(mixed $object, ?string $format = null, array $context ]; } - /** - * @param Location $data - */ public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return $data instanceof Location; diff --git a/src/Serializer/Normalizer/TimeWindowNormalizer.php b/src/Serializer/Normalizer/TimeWindowNormalizer.php index 2c3b599..d4f8b1d 100644 --- a/src/Serializer/Normalizer/TimeWindowNormalizer.php +++ b/src/Serializer/Normalizer/TimeWindowNormalizer.php @@ -34,9 +34,6 @@ public function normalize(mixed $object, ?string $format = null, array $context return null; } - /** - * @param TimeWindowInterface $data - */ public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return $data instanceof TimeWindowInterface; diff --git a/src/Util/DateTimeUtil.php b/src/Util/DateTimeUtil.php index 6e04d81..df6f89b 100644 --- a/src/Util/DateTimeUtil.php +++ b/src/Util/DateTimeUtil.php @@ -13,9 +13,8 @@ public static function fromUTC(\DateTimeImmutable $dateTime): \DateTimeImmutable $localDateTime = new \DateTimeImmutable(); $localDateTime = $localDateTime->setDate((int) $dateTime->format('Y'), (int) $dateTime->format('m'), (int) $dateTime->format('d')); - $localDateTime = $localDateTime->seTTime((int) $dateTime->format('H'), (int) $dateTime->format('i'), (int) $dateTime->format('s')); - return $localDateTime; + return $localDateTime->seTTime((int) $dateTime->format('H'), (int) $dateTime->format('i'), (int) $dateTime->format('s')); } public static function toUTC(\DateTimeImmutable $dateTime): \DateTimeImmutable @@ -23,8 +22,7 @@ public static function toUTC(\DateTimeImmutable $dateTime): \DateTimeImmutable $localDateTime = new \DateTimeImmutable(); $localDateTime = $localDateTime->setTimezone(new \DateTimeZone('UTC')); $localDateTime = $localDateTime->setDate((int) $dateTime->format('Y'), (int) $dateTime->format('m'), (int) $dateTime->format('d')); - $localDateTime = $localDateTime->seTTime((int) $dateTime->format('H'), (int) $dateTime->format('i'), (int) $dateTime->format('s')); - return $localDateTime; + return $localDateTime->seTTime((int) $dateTime->format('H'), (int) $dateTime->format('i'), (int) $dateTime->format('s')); } } From eb7cfff83bf15cfc9a4bda3dccac0b913c69be10 Mon Sep 17 00:00:00 2001 From: Marco Wansinck Date: Tue, 16 Jun 2026 16:51:18 +0200 Subject: [PATCH 2/6] chore: bump PHPUnit to 13 (range) and drop unused reflection-docblock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PHPUnit: ^11.0 -> '^11.0 || ^12.0 || ^13.0'. PHPUnit 13 requires PHP 8.4+, 12 requires 8.3, 11 requires 8.2 — the range lets each matrix PHP version install a compatible major (8.2->11, 8.3->12, 8.4/8.5->13) while the package keeps its ^8.2 floor. Migrated phpunit.xml.dist to the 13.2 schema; verified it still loads on PHPUnit 11. Tests pass unchanged (3/3). Removed phpdocumentor/reflection-docblock: it was unused. The serializer wires ReflectionExtractor (reads native PHP type declarations), never PhpDocExtractor (the only consumer of reflection-docblock); no test references it and nothing else requires it. Symfony 8 even conflicts with reflection-docblock >=7, so it could not be bumped anyway. Tests/serialization stay green without it. phpstan.neon: dropped the now-unmatched missingType.generics ignore (the underlying error no longer occurs; reportUnmatchedIgnoredErrors would fail on it). Verified locally (PHP 8.5): phpunit 3/3, phpstan no errors, rector clean, php-cs-fixer 0 files. --- composer.json | 3 +-- phpstan.neon | 6 ++---- phpunit.xml.dist | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index a6a43dc..6289d22 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,6 @@ "require": { "php": "^8.2", "ext-json": "*", - "phpdocumentor/reflection-docblock": "^5.3", "symfony/debug-bundle": "^6.4|^7.4|^8.0", "symfony/http-client": "^6.4|^7.4|^8.0", "symfony/property-access": "^6.4|^7.4|^8.0", @@ -23,7 +22,7 @@ "require-dev" : { "friendsofphp/php-cs-fixer": "^3.15", "phpstan/phpstan": "^2.1", - "phpunit/phpunit": "^11.0", + "phpunit/phpunit": "^11.0 || ^12.0 || ^13.0", "rector/rector": "^2.0", "symfony/var-dumper": "^6.4|^7.4|^8.0" }, diff --git a/phpstan.neon b/phpstan.neon index 33cedb9..f58e532 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,8 +4,6 @@ parameters: - src - tests ignoreErrors: - # PHPStan 2.0 removed the `checkMissingIterableValueType` and - # `checkGenericClassInNonGenericObjectType` parameters; keep the - # project's original opt-out by ignoring the equivalent identifiers. + # PHPStan 2.0 removed the `checkMissingIterableValueType` parameter; + # keep the project's original opt-out via the equivalent identifier. - identifier: missingType.iterableValue - - identifier: missingType.generics diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 3c1331a..0ed11d5 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,5 +1,5 @@ - + ./tests/ From b8b25fddf70c57032e5178bf2d231901fd4bdf8d Mon Sep 17 00:00:00 2001 From: Marco Wansinck Date: Tue, 16 Jun 2026 17:10:12 +0200 Subject: [PATCH 3/6] fix(phpstan): specify ArrayObject generics in Serializer::normalize PHPStan 2.x (level 9) reports missingType.generics for the \ArrayObject in the normalize() return type. The error only surfaced on Symfony 6.4/7.4/8.0 (on 8.1 the parent signature suppressed it), so CI failed on almost every matrix job while passing locally on 8.1. Specify the generics via @return \ArrayObject|... so the type is complete on every Symfony version. Fixing it at the source is robust; a plain ignore would instead go unmatched on the 8.1 jobs and fail those. Verified phpstan green on Symfony 6.4, 7.4 and 8.1. --- src/Serializer.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Serializer.php b/src/Serializer.php index fc54bf1..0a2cc25 100644 --- a/src/Serializer.php +++ b/src/Serializer.php @@ -34,6 +34,8 @@ public function __construct() } /** + * @return \ArrayObject|array|string|int|float|bool|null + * * @throws SerializerExceptionInterface */ public function normalize(mixed $data, ?string $format = null, array $context = []): \ArrayObject|array|string|int|float|bool|null From b6f9cf5f01bd61f9ab6218761101b13efbe6f5aa Mon Sep 17 00:00:00 2001 From: Marco Wansinck Date: Tue, 16 Jun 2026 19:16:54 +0200 Subject: [PATCH 4/6] ci: bump GitHub Actions to latest majors - actions/checkout v4 -> v6 (Node 24; v4 was flagged as deprecated Node 20) - actions/cache v4 -> v5 - shivammathur/setup-php stays v2 (already the latest major) --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b1d0cc9..b51bc36 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: # —— Github Checkout 🔎 —————————————————————————————————————————————————— - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 @@ -55,7 +55,7 @@ jobs: steps: # —— Git Checkout PR branch —————————————————————————————————————————— - name: Git Checkout PR branch (${{ github.ref }}) - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 @@ -75,7 +75,7 @@ jobs: run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" - name: Cache composer dependencies - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ matrix.php-versions }}-${{ matrix.symfony }} From 5f17247cb927bfa26b433aec1285505577915333 Mon Sep 17 00:00:00 2001 From: Marco Wansinck Date: Tue, 16 Jun 2026 21:15:16 +0200 Subject: [PATCH 5/6] chore: use single-pipe constraint syntax for phpunit Match the single-pipe (no spaces) OR syntax used by the rest of the file's Symfony constraints (^6.4|^7.4|^8.0). Functionally identical to ||. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 6289d22..d9a64fa 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "require-dev" : { "friendsofphp/php-cs-fixer": "^3.15", "phpstan/phpstan": "^2.1", - "phpunit/phpunit": "^11.0 || ^12.0 || ^13.0", + "phpunit/phpunit": "^11.0|^12.0|^13.0", "rector/rector": "^2.0", "symfony/var-dumper": "^6.4|^7.4|^8.0" }, From 61d61288341c2be73f5020a6ad12fca08d0826ae Mon Sep 17 00:00:00 2001 From: Marco Wansinck Date: Tue, 16 Jun 2026 21:17:53 +0200 Subject: [PATCH 6/6] ci: drop explanatory PHPStan comment (review feedback) --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b51bc36..29dd245 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,7 +96,6 @@ jobs: run: php vendor/bin/phpunit # —— PHPStan ——————————————————————————————————————————————————————————— - # Run per Symfony/PHP combination so version-specific type issues surface. - name: PHPStan (static analysis) run: php vendor/bin/phpstan analyse --no-progress