From dcb134b1327e9df88d5e7960d58342484435ed1a Mon Sep 17 00:00:00 2001 From: Dariusz Gafka Date: Wed, 12 Aug 2026 08:00:00 +0200 Subject: [PATCH 01/13] fix: avoid null array offset deprecation in DbaBusinessMethodModule PHP 8.5 deprecates using null as an array offset. Fall back to the interface parameter name when the DbalParameter attribute has no explicit name, matching the key already used to register its header. --- packages/Dbal/src/DbaBusinessMethod/DbaBusinessMethodModule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/Dbal/src/DbaBusinessMethod/DbaBusinessMethodModule.php b/packages/Dbal/src/DbaBusinessMethod/DbaBusinessMethodModule.php index 4f92b1fd2..de2b1bb4f 100644 --- a/packages/Dbal/src/DbaBusinessMethod/DbaBusinessMethodModule.php +++ b/packages/Dbal/src/DbaBusinessMethod/DbaBusinessMethodModule.php @@ -223,7 +223,7 @@ private static function getParameterConverters(DbalWrite|DbalQuery $attribute, I /** @var DbalParameter $dbalParameterAttribute */ $dbalParameterAttribute = $annotationsOfType[0]; - Assert::isFalse(isset($parameterConverters[$dbalParameterAttribute->getName()]), "Parameter {$dbalParameterAttribute->getName()} is defined twice"); + Assert::isFalse(isset($parameterConverters[$dbalParameterAttribute->getName() ?? $interfaceParameter->getName()]), "Parameter {$dbalParameterAttribute->getName()} is defined twice"); $parameterConverters[] = GatewayHeaderValueBuilder::create( DbalBusinessMethodHandler::HEADER_PARAMETER_TYPE_PREFIX . $interfaceParameter->getName(), self::dbalParameterConfig($dbalParameterAttribute, $interface, $interface->getMethodName(), $interfaceParameter->getName(), 0) From 47fb49e43d216497ceed051f6954bcc6ce9fb335 Mon Sep 17 00:00:00 2001 From: Dariusz Gafka Date: Wed, 12 Aug 2026 08:00:00 +0200 Subject: [PATCH 02/13] fix: avoid deprecated PDO::MYSQL_ATTR_SSL_CA constant on PHP 8.5 PDO::MYSQL_ATTR_SSL_CA is deprecated since PHP 8.5 in favour of Pdo\Mysql::ATTR_SSL_CA. Guard on class_exists() so the test config still works on PHP 8.2/8.3, where Pdo\Mysql does not exist yet. --- packages/Laravel/tests/Application/config/database.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/Laravel/tests/Application/config/database.php b/packages/Laravel/tests/Application/config/database.php index 69c54e41a..029ea29a5 100755 --- a/packages/Laravel/tests/Application/config/database.php +++ b/packages/Laravel/tests/Application/config/database.php @@ -88,7 +88,7 @@ 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ - PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + (class_exists(Pdo\Mysql::class) ? Pdo\Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], From d81b4947f3082d3c7b2180d081267de5f439539f Mon Sep 17 00:00:00 2001 From: Dariusz Gafka Date: Wed, 12 Aug 2026 08:00:00 +0200 Subject: [PATCH 03/13] fix: migrate deprecated PHPUnit XML schema in remaining packages JmsConverter, LiteApplication, OpenTelemetry, Redis and Sqs still validated against the old PHPUnit 9.5 schema, which PHPUnit 11 reports as a runtime deprecation. Migrated via vendor/bin/phpunit --migrate-configuration to the 11.5 schema, matching the other packages. --- packages/JmsConverter/phpunit.xml.dist | 34 ++++++------ packages/LiteApplication/phpunit.xml.dist | 67 +++++++++++------------ packages/OpenTelemetry/phpunit.xml | 34 ++++++------ packages/Redis/phpunit.xml.dist | 34 ++++++------ packages/Sqs/phpunit.xml.dist | 34 ++++++------ 5 files changed, 95 insertions(+), 108 deletions(-) diff --git a/packages/JmsConverter/phpunit.xml.dist b/packages/JmsConverter/phpunit.xml.dist index fc3ebe4f7..f46cbff43 100644 --- a/packages/JmsConverter/phpunit.xml.dist +++ b/packages/JmsConverter/phpunit.xml.dist @@ -1,20 +1,18 @@ - - - - ./src - - - - - - - - tests - - + + + + + + + + + tests + + + + + ./src + + diff --git a/packages/LiteApplication/phpunit.xml.dist b/packages/LiteApplication/phpunit.xml.dist index ded5bce80..02a9fbad9 100644 --- a/packages/LiteApplication/phpunit.xml.dist +++ b/packages/LiteApplication/phpunit.xml.dist @@ -1,39 +1,34 @@ - - - - - - - - - - - - - - - - - - - - - - ./src - - - - - - - - tests - - + + + + + + + + + + + + + + + + + + + + + + + + tests + + + + + ./src + + diff --git a/packages/OpenTelemetry/phpunit.xml b/packages/OpenTelemetry/phpunit.xml index fc3ebe4f7..f46cbff43 100644 --- a/packages/OpenTelemetry/phpunit.xml +++ b/packages/OpenTelemetry/phpunit.xml @@ -1,20 +1,18 @@ - - - - ./src - - - - - - - - tests - - + + + + + + + + + tests + + + + + ./src + + diff --git a/packages/Redis/phpunit.xml.dist b/packages/Redis/phpunit.xml.dist index fc3ebe4f7..f46cbff43 100644 --- a/packages/Redis/phpunit.xml.dist +++ b/packages/Redis/phpunit.xml.dist @@ -1,20 +1,18 @@ - - - - ./src - - - - - - - - tests - - + + + + + + + + + tests + + + + + ./src + + diff --git a/packages/Sqs/phpunit.xml.dist b/packages/Sqs/phpunit.xml.dist index fc3ebe4f7..f46cbff43 100644 --- a/packages/Sqs/phpunit.xml.dist +++ b/packages/Sqs/phpunit.xml.dist @@ -1,20 +1,18 @@ - - - - ./src - - - - - - - - tests - - + + + + + + + + + tests + + + + + ./src + + From 18e8b136c6e7a2e2a8a58db340e148525d15cf7f Mon Sep 17 00:00:00 2001 From: Dariusz Gafka Date: Wed, 12 Aug 2026 08:00:00 +0200 Subject: [PATCH 04/13] fix: allow predis/predis v2 to clear PHP 8.5 nullable-param deprecations predis/predis 1.1.10 triggers several "implicitly nullable parameter" deprecations on PHP 8.5. It is a require-dev (test-only) dependency, so widen the constraint to allow 2.x; the test suite passes unchanged against 2.4.1. --- packages/Redis/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/Redis/composer.json b/packages/Redis/composer.json index 4ad1b6051..b846eb8fe 100644 --- a/packages/Redis/composer.json +++ b/packages/Redis/composer.json @@ -57,7 +57,7 @@ "require-dev": { "phpstan/phpstan": "^1.8", "phpunit/phpunit": "^10.5|^11.0", - "predis/predis": "^1.1.10" + "predis/predis": "^1.1.10|^2.0" }, "scripts": { "tests:phpstan": "vendor/bin/phpstan", From 6ac2a865e73c2e8050d6570f7c671baf7c775a97 Mon Sep 17 00:00:00 2001 From: Dariusz Gafka Date: Wed, 12 Aug 2026 08:00:00 +0200 Subject: [PATCH 05/13] fix: allow guzzlehttp/promises v2 to clear PHP 8.5 nullable-param deprecations guzzlehttp/promises 1.5.3 triggers several "implicitly nullable parameter" deprecations on PHP 8.5. It is a require-dev (test-only) dependency, so widen the constraint to allow 2.x; the test suite passes unchanged against 2.5.2. --- packages/OpenTelemetry/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/OpenTelemetry/composer.json b/packages/OpenTelemetry/composer.json index f1dd2d4af..3dc93e68b 100644 --- a/packages/OpenTelemetry/composer.json +++ b/packages/OpenTelemetry/composer.json @@ -56,7 +56,7 @@ "open-telemetry/transport-grpc": "^1.0.0", "open-telemetry/exporter-otlp": "^1.0.0", "symfony/http-client": "^6.4|^7.0|^8.0", - "guzzlehttp/promises": "^1.5", + "guzzlehttp/promises": "^1.5|^2.0", "php-http/message-factory": "^1.0", "nyholm/psr7": "^1.5", "psr/http-client": "^1.0.1", From 4eac18161ace3a70bb568da0a32335634383f6e3 Mon Sep 17 00:00:00 2001 From: Dariusz Gafka Date: Wed, 12 Aug 2026 08:00:00 +0200 Subject: [PATCH 06/13] build: allow newer major versions of symfony/uid, guzzlehttp/psr7, kwn/php-rdkafka-stubs These were pinned below their latest major even though nothing in the root project requires the old major. symfony/uid resolves to 8.1.4 and kwn/php-rdkafka-stubs to 3.0.1 with this change; guzzlehttp/psr7 stays on 2.x for now since guzzlehttp/guzzle 7.x itself caps it to ^2.13. --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index b65591abd..259cc63f4 100644 --- a/composer.json +++ b/composer.json @@ -164,14 +164,14 @@ "open-telemetry/sdk": "^1.0.0", "psr/container": "^1.1.1|^2.0.1", "psr/clock": "^1.0", - "symfony/uid": "^7.4" + "symfony/uid": "^7.4|^8.0" }, "require-dev": { "doctrine/annotations": "^1.13|^2.0", "doctrine/cache": "^1.0.0|^2.0", "doctrine/orm": "^2.11|^3.0", "friendsofphp/php-cs-fixer": "^3.9", - "guzzlehttp/psr7": "^2.0", + "guzzlehttp/psr7": "^2.0|^3.0", "orchestra/testbench": "^8.0|^9.0|^10.0|^11.0", "php-coveralls/php-coveralls": "^2.5", "phpstan/phpstan": "^1.8|^2.0", @@ -193,7 +193,7 @@ "moneyphp/money": "^4.1.0", "timacdonald/log-fake": "^2.0", "symfony/monolog-bundle": "^3.10", - "kwn/php-rdkafka-stubs": "^2.2", + "kwn/php-rdkafka-stubs": "^2.2|^3.0", "symfony/var-exporter": "^6.4|^7.0|^8.0", "enqueue/dsn": "^0.10.27", "yoast/phpunit-polyfills": "^4.0.0", From 1e7fe061a9ed93bc65d5b402a0409f22bde7b0d0 Mon Sep 17 00:00:00 2001 From: Dariusz Gafka Date: Wed, 12 Aug 2026 08:00:00 +0200 Subject: [PATCH 07/13] build: allow kwn/php-rdkafka-stubs v3 in Kafka package IDE-stub-only package with no runtime code, resolves to 3.0.1. --- packages/Kafka/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/Kafka/composer.json b/packages/Kafka/composer.json index b58bde679..f363e744b 100644 --- a/packages/Kafka/composer.json +++ b/packages/Kafka/composer.json @@ -54,7 +54,7 @@ "phpunit/phpunit": "^10.5|^11.0", "phpstan/phpstan": "^1.8", "psr/container": "^1.1.1|^2.0.1", - "kwn/php-rdkafka-stubs": "^2.2", + "kwn/php-rdkafka-stubs": "^2.2|^3.0", "ecotone/dbal": "~1.325.0", "symfony/expression-language": "^6.4|^7.0|^8.0" }, From a98bfa6c99b0175c570029a0d060adecdf6745b4 Mon Sep 17 00:00:00 2001 From: Dariusz Gafka Date: Wed, 12 Aug 2026 08:00:00 +0200 Subject: [PATCH 08/13] build: allow guzzlehttp/psr7 v3 in Laravel and LiteApplication packages No-op today since guzzlehttp/guzzle 7.x still caps psr7 to ^2.13, but removes the constraint as a blocker once guzzle itself is bumped. --- packages/Laravel/composer.json | 2 +- packages/LiteApplication/composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/Laravel/composer.json b/packages/Laravel/composer.json index 34dc8c0e1..e251f0780 100644 --- a/packages/Laravel/composer.json +++ b/packages/Laravel/composer.json @@ -58,7 +58,7 @@ }, "require-dev": { "phpunit/phpunit": "^10.5|^11.0", - "guzzlehttp/psr7": "^2.0", + "guzzlehttp/psr7": "^2.0|^3.0", "phpstan/phpstan": "^1.8", "orchestra/testbench": "^8.0|^9.0|^10.0|^11.0", "symfony/expression-language": "^6.4|^7.0|^8.0", diff --git a/packages/LiteApplication/composer.json b/packages/LiteApplication/composer.json index 7fd3d821d..622a9d45d 100644 --- a/packages/LiteApplication/composer.json +++ b/packages/LiteApplication/composer.json @@ -53,7 +53,7 @@ }, "require-dev": { "phpunit/phpunit": "^10.5|^11.0", - "guzzlehttp/psr7": "^2.0", + "guzzlehttp/psr7": "^2.0|^3.0", "phpstan/phpstan": "^1.8", "orchestra/testbench": "^8.0|^9.0|^10.0|^11.0" }, From e6392117a6bcafa612d73e921636226acbb81bc9 Mon Sep 17 00:00:00 2001 From: Dariusz Gafka Date: Wed, 12 Aug 2026 08:00:00 +0200 Subject: [PATCH 09/13] build: allow phpstan/phpstan v2 across remaining packages Root composer.json already allowed ^1.8|^2.0; sync the 13 package-level composer.json files to match so they aren't stuck resolving 1.12.x. All 13 packages resolve to 2.2.8 and pass with zero new errors. --- packages/Amqp/composer.json | 2 +- packages/Dbal/composer.json | 2 +- packages/Ecotone/composer.json | 2 +- packages/Enqueue/composer.json | 2 +- packages/JmsConverter/composer.json | 2 +- packages/Kafka/composer.json | 2 +- packages/Laravel/composer.json | 2 +- packages/LiteApplication/composer.json | 2 +- packages/OpenTelemetry/composer.json | 2 +- packages/PdoEventSourcing/composer.json | 2 +- packages/Redis/composer.json | 2 +- packages/Sqs/composer.json | 2 +- packages/Symfony/composer.json | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/Amqp/composer.json b/packages/Amqp/composer.json index 0219fb5e8..dec71cd79 100644 --- a/packages/Amqp/composer.json +++ b/packages/Amqp/composer.json @@ -59,7 +59,7 @@ }, "require-dev": { "phpunit/phpunit": "^10.5|^11.0", - "phpstan/phpstan": "^1.8", + "phpstan/phpstan": "^1.8|^2.0", "doctrine/annotations": "^1.13|^2.0", "ext-amqp": "*", "enqueue/amqp-ext": "^0.10.18", diff --git a/packages/Dbal/composer.json b/packages/Dbal/composer.json index 10b63d9e5..7c7ebc196 100644 --- a/packages/Dbal/composer.json +++ b/packages/Dbal/composer.json @@ -57,7 +57,7 @@ "require-dev": { "phpunit/phpunit": "^10.5|^11.0", "doctrine/persistence": "^2.5|^3.4|^4.0", - "phpstan/phpstan": "^1.8", + "phpstan/phpstan": "^1.8|^2.0", "doctrine/orm": "^2.11|^3.0", "doctrine/cache": "^1.0.0|^2.0", "doctrine/annotations": "^1.13|^2.0", diff --git a/packages/Ecotone/composer.json b/packages/Ecotone/composer.json index bfea4ec23..0511c1b65 100644 --- a/packages/Ecotone/composer.json +++ b/packages/Ecotone/composer.json @@ -63,7 +63,7 @@ "require-dev": { "phpunit/phpunit": "^10.5|^11.0", "ramsey/uuid": "^4.0", - "phpstan/phpstan": "^1.8", + "phpstan/phpstan": "^1.8|^2.0", "symfony/console": "^6.4|^7.0|^8.0", "symfony/expression-language": "^6.4|^7.0|^8.0" }, diff --git a/packages/Enqueue/composer.json b/packages/Enqueue/composer.json index 38ddf0fd0..c16001f67 100644 --- a/packages/Enqueue/composer.json +++ b/packages/Enqueue/composer.json @@ -53,7 +53,7 @@ }, "require-dev": { "phpunit/phpunit": "^10.5|^11.0", - "phpstan/phpstan": "^1.8", + "phpstan/phpstan": "^1.8|^2.0", "enqueue/null": "^0.10.18" }, "scripts": { diff --git a/packages/JmsConverter/composer.json b/packages/JmsConverter/composer.json index 4fc8a49f9..d6fe3b64a 100644 --- a/packages/JmsConverter/composer.json +++ b/packages/JmsConverter/composer.json @@ -56,7 +56,7 @@ "require-dev": { "ecotone/pdo-event-sourcing": "~1.325.0", "phpunit/phpunit": "^10.5|^11.0", - "phpstan/phpstan": "^1.8" + "phpstan/phpstan": "^1.8|^2.0" }, "scripts": { "tests:phpstan": "vendor/bin/phpstan", diff --git a/packages/Kafka/composer.json b/packages/Kafka/composer.json index f363e744b..30efc2953 100644 --- a/packages/Kafka/composer.json +++ b/packages/Kafka/composer.json @@ -52,7 +52,7 @@ }, "require-dev": { "phpunit/phpunit": "^10.5|^11.0", - "phpstan/phpstan": "^1.8", + "phpstan/phpstan": "^1.8|^2.0", "psr/container": "^1.1.1|^2.0.1", "kwn/php-rdkafka-stubs": "^2.2|^3.0", "ecotone/dbal": "~1.325.0", diff --git a/packages/Laravel/composer.json b/packages/Laravel/composer.json index e251f0780..3d42a19b4 100644 --- a/packages/Laravel/composer.json +++ b/packages/Laravel/composer.json @@ -59,7 +59,7 @@ "require-dev": { "phpunit/phpunit": "^10.5|^11.0", "guzzlehttp/psr7": "^2.0|^3.0", - "phpstan/phpstan": "^1.8", + "phpstan/phpstan": "^1.8|^2.0", "orchestra/testbench": "^8.0|^9.0|^10.0|^11.0", "symfony/expression-language": "^6.4|^7.0|^8.0", "nesbot/carbon": "^2.71|^3.0", diff --git a/packages/LiteApplication/composer.json b/packages/LiteApplication/composer.json index 622a9d45d..77e7bcc3e 100644 --- a/packages/LiteApplication/composer.json +++ b/packages/LiteApplication/composer.json @@ -54,7 +54,7 @@ "require-dev": { "phpunit/phpunit": "^10.5|^11.0", "guzzlehttp/psr7": "^2.0|^3.0", - "phpstan/phpstan": "^1.8", + "phpstan/phpstan": "^1.8|^2.0", "orchestra/testbench": "^8.0|^9.0|^10.0|^11.0" }, "extra": { diff --git a/packages/OpenTelemetry/composer.json b/packages/OpenTelemetry/composer.json index 3dc93e68b..3dd0525a0 100644 --- a/packages/OpenTelemetry/composer.json +++ b/packages/OpenTelemetry/composer.json @@ -52,7 +52,7 @@ }, "require-dev": { "phpunit/phpunit": "^10.5|^11.0", - "phpstan/phpstan": "^1.8", + "phpstan/phpstan": "^1.8|^2.0", "open-telemetry/transport-grpc": "^1.0.0", "open-telemetry/exporter-otlp": "^1.0.0", "symfony/http-client": "^6.4|^7.0|^8.0", diff --git a/packages/PdoEventSourcing/composer.json b/packages/PdoEventSourcing/composer.json index 34df0990e..b0c9119ae 100644 --- a/packages/PdoEventSourcing/composer.json +++ b/packages/PdoEventSourcing/composer.json @@ -56,7 +56,7 @@ }, "require-dev": { "phpunit/phpunit": "^10.5|^11.0", - "phpstan/phpstan": "^1.8", + "phpstan/phpstan": "^1.8|^2.0", "symfony/process": "^6.4|^7.0|^8.0", "symfony/console": "^6.4|^7.0|^8.0", "doctrine/orm": "^2.11|^3.0", diff --git a/packages/Redis/composer.json b/packages/Redis/composer.json index b846eb8fe..006b17fb9 100644 --- a/packages/Redis/composer.json +++ b/packages/Redis/composer.json @@ -55,7 +55,7 @@ "enqueue/redis": "^0.10.9" }, "require-dev": { - "phpstan/phpstan": "^1.8", + "phpstan/phpstan": "^1.8|^2.0", "phpunit/phpunit": "^10.5|^11.0", "predis/predis": "^1.1.10|^2.0" }, diff --git a/packages/Sqs/composer.json b/packages/Sqs/composer.json index b51f365e7..4b9661dc4 100644 --- a/packages/Sqs/composer.json +++ b/packages/Sqs/composer.json @@ -55,7 +55,7 @@ }, "require-dev": { "phpunit/phpunit": "^10.5|^11.0", - "phpstan/phpstan": "^1.8" + "phpstan/phpstan": "^1.8|^2.0" }, "conflict": { "guzzlehttp/promises": "<1.4.0" diff --git a/packages/Symfony/composer.json b/packages/Symfony/composer.json index ac014625e..1d145de6e 100644 --- a/packages/Symfony/composer.json +++ b/packages/Symfony/composer.json @@ -53,7 +53,7 @@ "ecotone/kafka": "~1.325.0", "ext-rdkafka": "*", "monolog/monolog": "^2.9|^3.3.1", - "phpstan/phpstan": "^1.8", + "phpstan/phpstan": "^1.8|^2.0", "phpunit/phpunit": "^10.5|^11.0", "symfony/amqp-messenger": "^6.4|^7.0|^8.0", "symfony/doctrine-messenger": "^6.4|^7.0|^8.0", From ec99b13ca4014dba82eb32a2a7b281e82a0b9fb5 Mon Sep 17 00:00:00 2001 From: Dariusz Gafka Date: Wed, 12 Aug 2026 08:00:00 +0200 Subject: [PATCH 10/13] chore: bump symplify/monorepo-builder to ^12.0 Resolves to 12.7.2, which fixes the tool's own PHP 8.5 deprecation noise from its bundled (namespaced) Symfony DI/Console components. Custom release workers in Monorepo/ keep working unchanged - the internal classes they depend on (DependencyUpdater, ComposerJsonProvider, PackageNamesProvider, VersionUtils) only moved from packages/ to src/ internally, same namespaces. Also syncs monorepo-builder.php's dataToAppend phpstan/phpstan constraint with the ^1.8|^2.0 already used across packages since the Tier 2 bump. --- composer.json | 2 +- monorepo-builder.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 259cc63f4..b1d9a274e 100644 --- a/composer.json +++ b/composer.json @@ -178,7 +178,7 @@ "phpunit/phpunit": "^11.0", "predis/predis": "^1.1.10", "symfony/expression-language": "^6.4|^7.0|^8.0", - "symplify/monorepo-builder": "11.1.21", + "symplify/monorepo-builder": "^12.0", "aws/aws-sdk-php": ">=3.340.5", "symfony/messenger": "^6.4|^7.0|^8.0", "symfony/amqp-messenger": "^6.4|^7.0|^8.0", diff --git a/monorepo-builder.php b/monorepo-builder.php index 82aeaf76f..ca2e38841 100644 --- a/monorepo-builder.php +++ b/monorepo-builder.php @@ -29,10 +29,10 @@ "behat/behat" => "^3.10", "friendsofphp/php-cs-fixer" => "^3.9", "php-coveralls/php-coveralls" => "^2.5", - "phpstan/phpstan" => "^1.8", + "phpstan/phpstan" => "^1.8|^2.0", "phpunit/phpunit" => "^9.6|^10.5|^11.0", "symfony/expression-language" => "^6.0|^7.0", - "symplify/monorepo-builder" => "11.1.21" + "symplify/monorepo-builder" => "^12.0" ], ]); $containerConfigurator->defaultBranch('main'); From d246564adc57a31659e96872a0f54a904ea9f6d0 Mon Sep 17 00:00:00 2001 From: Dariusz Gafka Date: Wed, 12 Aug 2026 08:00:00 +0200 Subject: [PATCH 11/13] chore: widen phpunit/phpunit to allow ^12.0 across all packages Verified per-package on fresh installs (PHP 8.5): all 15 packages' full suites pass with phpunit resolving to 12.5.33, zero new failures. On PHP 8.2 the same widened constraint keeps resolving 11.5.x automatically (phpunit 12 requires php >=8.3) - spot-checked via the symlink:false isolation technique on packages/Ecotone. Fixes found and applied along the way, all confirmed unrelated to which phpunit major is in use: - RequiresPhp('>= 8.5') attributes in three Dbal/Ecotone tests were flagged as "incomplete version requirement" by phpunit 12's stricter parser; PharIo's version comparator wants full semver. - GracefulShutdownOnSignalTest sends itself a raw SIGTERM after a hardcoded 300ms sleep, racing bootstrapFlowTesting() + Ecotone's consumer registering its signal handler. Under phpunit 12's added per-test overhead the race lost reliably, killing the whole process before the handler was installed. Bumped the pre-signal delay to 1s; the timing assertion itself is unaffected since it's measured from run() start, not process start. - Two tests (Enqueue's InboundMessageConverterTest, Symfony's SymfonyMessengerMessageChannelUnitTest) passed unused dependencies via createMock() with no expectations ever set - exactly what phpunit 12's new "no expectations configured" notice flags. Switched to createStub(). phpunit/phpunit stays capped below ^12.0 as the floor (not raised to ^12.0 only) because PHP 8.2 is still an explicitly supported/tested target (app8_2 container, split-testing.yml CI matrix) - phpunit 13 requires php>=8.4.1 so it's excluded entirely, unlike 12 which only needs php>=8.3 and simply won't be selected under 8.2. Also syncs monorepo-builder.php's dataToAppend phpunit constraint to match. --- composer.json | 2 +- monorepo-builder.php | 2 +- packages/Amqp/composer.json | 2 +- packages/DataProtection/composer.json | 2 +- packages/Dbal/composer.json | 2 +- .../ClosureInAttribute/ClosureExpressionDbalTest.php | 2 +- .../Consumer/GracefulShutdownOnSignalTest.php | 2 +- packages/Ecotone/composer.json | 2 +- .../Messaging/Unit/Handler/ClosureExpressionTest.php | 2 +- .../tests/SymfonyContainer/ClosureInAttributeTest.php | 2 +- packages/Enqueue/composer.json | 2 +- packages/Enqueue/tests/InboundMessageConverterTest.php | 2 +- packages/JmsConverter/composer.json | 2 +- packages/Kafka/composer.json | 2 +- packages/Laravel/composer.json | 2 +- packages/LiteApplication/composer.json | 2 +- packages/OpenTelemetry/composer.json | 2 +- packages/PdoEventSourcing/composer.json | 2 +- packages/Redis/composer.json | 2 +- packages/Sqs/composer.json | 2 +- packages/Symfony/composer.json | 2 +- .../phpunit/SymfonyMessengerMessageChannelUnitTest.php | 10 +++++----- packages/Tempest/composer.json | 2 +- 23 files changed, 27 insertions(+), 27 deletions(-) diff --git a/composer.json b/composer.json index b1d9a274e..c48a9168e 100644 --- a/composer.json +++ b/composer.json @@ -175,7 +175,7 @@ "orchestra/testbench": "^8.0|^9.0|^10.0|^11.0", "php-coveralls/php-coveralls": "^2.5", "phpstan/phpstan": "^1.8|^2.0", - "phpunit/phpunit": "^11.0", + "phpunit/phpunit": "^11.0|^12.0", "predis/predis": "^1.1.10", "symfony/expression-language": "^6.4|^7.0|^8.0", "symplify/monorepo-builder": "^12.0", diff --git a/monorepo-builder.php b/monorepo-builder.php index ca2e38841..4944c2a00 100644 --- a/monorepo-builder.php +++ b/monorepo-builder.php @@ -30,7 +30,7 @@ "friendsofphp/php-cs-fixer" => "^3.9", "php-coveralls/php-coveralls" => "^2.5", "phpstan/phpstan" => "^1.8|^2.0", - "phpunit/phpunit" => "^9.6|^10.5|^11.0", + "phpunit/phpunit" => "^9.6|^10.5|^11.0|^12.0", "symfony/expression-language" => "^6.0|^7.0", "symplify/monorepo-builder" => "^12.0" ], diff --git a/packages/Amqp/composer.json b/packages/Amqp/composer.json index dec71cd79..2467808e8 100644 --- a/packages/Amqp/composer.json +++ b/packages/Amqp/composer.json @@ -58,7 +58,7 @@ "enqueue/enqueue": "^0.10.0" }, "require-dev": { - "phpunit/phpunit": "^10.5|^11.0", + "phpunit/phpunit": "^10.5|^11.0|^12.0", "phpstan/phpstan": "^1.8|^2.0", "doctrine/annotations": "^1.13|^2.0", "ext-amqp": "*", diff --git a/packages/DataProtection/composer.json b/packages/DataProtection/composer.json index a171b22a1..ac702d7fb 100644 --- a/packages/DataProtection/composer.json +++ b/packages/DataProtection/composer.json @@ -57,7 +57,7 @@ }, "require-dev": { "ecotone/pdo-event-sourcing": "~1.325.0", - "phpunit/phpunit": "^11.0", + "phpunit/phpunit": "^11.0|^12.0", "phpstan/phpstan": "^2.1" }, "suggest": { diff --git a/packages/Dbal/composer.json b/packages/Dbal/composer.json index 7c7ebc196..51b6ab8d1 100644 --- a/packages/Dbal/composer.json +++ b/packages/Dbal/composer.json @@ -55,7 +55,7 @@ "enqueue/dsn": "^0.10.4" }, "require-dev": { - "phpunit/phpunit": "^10.5|^11.0", + "phpunit/phpunit": "^10.5|^11.0|^12.0", "doctrine/persistence": "^2.5|^3.4|^4.0", "phpstan/phpstan": "^1.8|^2.0", "doctrine/orm": "^2.11|^3.0", diff --git a/packages/Dbal/tests/Integration/ClosureInAttribute/ClosureExpressionDbalTest.php b/packages/Dbal/tests/Integration/ClosureInAttribute/ClosureExpressionDbalTest.php index 3b271ab78..f9c5be164 100644 --- a/packages/Dbal/tests/Integration/ClosureInAttribute/ClosureExpressionDbalTest.php +++ b/packages/Dbal/tests/Integration/ClosureInAttribute/ClosureExpressionDbalTest.php @@ -32,7 +32,7 @@ * licence Enterprise * @internal */ -#[RequiresPhp('>= 8.5')] +#[RequiresPhp('>= 8.5.0')] final class ClosureExpressionDbalTest extends DbalMessagingTestCase { public function test_deduplication_with_closure_expression(): void diff --git a/packages/Dbal/tests/Integration/Consumer/GracefulShutdownOnSignalTest.php b/packages/Dbal/tests/Integration/Consumer/GracefulShutdownOnSignalTest.php index e76555167..0f161e475 100644 --- a/packages/Dbal/tests/Integration/Consumer/GracefulShutdownOnSignalTest.php +++ b/packages/Dbal/tests/Integration/Consumer/GracefulShutdownOnSignalTest.php @@ -37,7 +37,7 @@ public function test_consumer_stops_promptly_on_sigterm_despite_large_execution_ // this PHP process would duplicate the already-open DB connection's socket fd // (opened in DbalMessagingTestCase::setUp()) into the child and corrupt it. $parentPid = posix_getpid(); - exec(sprintf('(sleep 0.3 && kill -TERM %d) > /dev/null 2>&1 &', $parentPid)); + exec(sprintf('(sleep 1 && kill -TERM %d) > /dev/null 2>&1 &', $parentPid)); $ecotoneLite = EcotoneLite::bootstrapFlowTesting( containerOrAvailableServices: [ diff --git a/packages/Ecotone/composer.json b/packages/Ecotone/composer.json index 0511c1b65..e0022838b 100644 --- a/packages/Ecotone/composer.json +++ b/packages/Ecotone/composer.json @@ -61,7 +61,7 @@ "symfony/uid": "^6.4|^7.0|^8.0" }, "require-dev": { - "phpunit/phpunit": "^10.5|^11.0", + "phpunit/phpunit": "^10.5|^11.0|^12.0", "ramsey/uuid": "^4.0", "phpstan/phpstan": "^1.8|^2.0", "symfony/console": "^6.4|^7.0|^8.0", diff --git a/packages/Ecotone/tests/Messaging/Unit/Handler/ClosureExpressionTest.php b/packages/Ecotone/tests/Messaging/Unit/Handler/ClosureExpressionTest.php index 9cab078f0..5939b6df3 100644 --- a/packages/Ecotone/tests/Messaging/Unit/Handler/ClosureExpressionTest.php +++ b/packages/Ecotone/tests/Messaging/Unit/Handler/ClosureExpressionTest.php @@ -27,7 +27,7 @@ * licence Enterprise * @internal */ -#[RequiresPhp('>= 8.5')] +#[RequiresPhp('>= 8.5.0')] final class ClosureExpressionTest extends TestCase { public function test_header_closure_expression_resolves_parameters_like_message_handler(): void diff --git a/packages/Ecotone/tests/SymfonyContainer/ClosureInAttributeTest.php b/packages/Ecotone/tests/SymfonyContainer/ClosureInAttributeTest.php index 8f5f09add..0043dd855 100644 --- a/packages/Ecotone/tests/SymfonyContainer/ClosureInAttributeTest.php +++ b/packages/Ecotone/tests/SymfonyContainer/ClosureInAttributeTest.php @@ -18,7 +18,7 @@ * licence Apache-2.0 * @internal */ -#[RequiresPhp('>= 8.5')] +#[RequiresPhp('>= 8.5.0')] final class ClosureInAttributeTest extends TestCase { public function test_intercepting_handler_with_attribute_containing_closure(): void diff --git a/packages/Enqueue/composer.json b/packages/Enqueue/composer.json index c16001f67..513ff6f8c 100644 --- a/packages/Enqueue/composer.json +++ b/packages/Enqueue/composer.json @@ -52,7 +52,7 @@ "enqueue/dsn": "^0.10.4" }, "require-dev": { - "phpunit/phpunit": "^10.5|^11.0", + "phpunit/phpunit": "^10.5|^11.0|^12.0", "phpstan/phpstan": "^1.8|^2.0", "enqueue/null": "^0.10.18" }, diff --git a/packages/Enqueue/tests/InboundMessageConverterTest.php b/packages/Enqueue/tests/InboundMessageConverterTest.php index 6368743b7..1c3b2ec0b 100644 --- a/packages/Enqueue/tests/InboundMessageConverterTest.php +++ b/packages/Enqueue/tests/InboundMessageConverterTest.php @@ -45,7 +45,7 @@ public function test_it_will_map_automatically_core_headers(): void ), new NullConsumer(new NullQueue('some')), InMemoryConversionService::createWithoutConversion(), - $this->createMock(CachedConnectionFactory::class), + $this->createStub(CachedConnectionFactory::class), ) ->build(); diff --git a/packages/JmsConverter/composer.json b/packages/JmsConverter/composer.json index d6fe3b64a..952b17275 100644 --- a/packages/JmsConverter/composer.json +++ b/packages/JmsConverter/composer.json @@ -55,7 +55,7 @@ }, "require-dev": { "ecotone/pdo-event-sourcing": "~1.325.0", - "phpunit/phpunit": "^10.5|^11.0", + "phpunit/phpunit": "^10.5|^11.0|^12.0", "phpstan/phpstan": "^1.8|^2.0" }, "scripts": { diff --git a/packages/Kafka/composer.json b/packages/Kafka/composer.json index 30efc2953..9e96603ed 100644 --- a/packages/Kafka/composer.json +++ b/packages/Kafka/composer.json @@ -51,7 +51,7 @@ "ecotone/ecotone": "~1.325.0" }, "require-dev": { - "phpunit/phpunit": "^10.5|^11.0", + "phpunit/phpunit": "^10.5|^11.0|^12.0", "phpstan/phpstan": "^1.8|^2.0", "psr/container": "^1.1.1|^2.0.1", "kwn/php-rdkafka-stubs": "^2.2|^3.0", diff --git a/packages/Laravel/composer.json b/packages/Laravel/composer.json index 3d42a19b4..2c11192b4 100644 --- a/packages/Laravel/composer.json +++ b/packages/Laravel/composer.json @@ -57,7 +57,7 @@ "laravel/framework": "^9.5.2|^10.0|^11.0|^12.0|^13.0" }, "require-dev": { - "phpunit/phpunit": "^10.5|^11.0", + "phpunit/phpunit": "^10.5|^11.0|^12.0", "guzzlehttp/psr7": "^2.0|^3.0", "phpstan/phpstan": "^1.8|^2.0", "orchestra/testbench": "^8.0|^9.0|^10.0|^11.0", diff --git a/packages/LiteApplication/composer.json b/packages/LiteApplication/composer.json index 77e7bcc3e..c4532c174 100644 --- a/packages/LiteApplication/composer.json +++ b/packages/LiteApplication/composer.json @@ -52,7 +52,7 @@ "ecotone/jms-converter": "~1.325.0" }, "require-dev": { - "phpunit/phpunit": "^10.5|^11.0", + "phpunit/phpunit": "^10.5|^11.0|^12.0", "guzzlehttp/psr7": "^2.0|^3.0", "phpstan/phpstan": "^1.8|^2.0", "orchestra/testbench": "^8.0|^9.0|^10.0|^11.0" diff --git a/packages/OpenTelemetry/composer.json b/packages/OpenTelemetry/composer.json index 3dd0525a0..1ad59fe7a 100644 --- a/packages/OpenTelemetry/composer.json +++ b/packages/OpenTelemetry/composer.json @@ -51,7 +51,7 @@ "open-telemetry/sdk": "^1.0.0" }, "require-dev": { - "phpunit/phpunit": "^10.5|^11.0", + "phpunit/phpunit": "^10.5|^11.0|^12.0", "phpstan/phpstan": "^1.8|^2.0", "open-telemetry/transport-grpc": "^1.0.0", "open-telemetry/exporter-otlp": "^1.0.0", diff --git a/packages/PdoEventSourcing/composer.json b/packages/PdoEventSourcing/composer.json index b0c9119ae..064f69629 100644 --- a/packages/PdoEventSourcing/composer.json +++ b/packages/PdoEventSourcing/composer.json @@ -55,7 +55,7 @@ "prooph/pdo-event-store": "^1.16.3" }, "require-dev": { - "phpunit/phpunit": "^10.5|^11.0", + "phpunit/phpunit": "^10.5|^11.0|^12.0", "phpstan/phpstan": "^1.8|^2.0", "symfony/process": "^6.4|^7.0|^8.0", "symfony/console": "^6.4|^7.0|^8.0", diff --git a/packages/Redis/composer.json b/packages/Redis/composer.json index 006b17fb9..4f6bde983 100644 --- a/packages/Redis/composer.json +++ b/packages/Redis/composer.json @@ -56,7 +56,7 @@ }, "require-dev": { "phpstan/phpstan": "^1.8|^2.0", - "phpunit/phpunit": "^10.5|^11.0", + "phpunit/phpunit": "^10.5|^11.0|^12.0", "predis/predis": "^1.1.10|^2.0" }, "scripts": { diff --git a/packages/Sqs/composer.json b/packages/Sqs/composer.json index 4b9661dc4..c58fc10a9 100644 --- a/packages/Sqs/composer.json +++ b/packages/Sqs/composer.json @@ -54,7 +54,7 @@ "aws/aws-sdk-php": ">=3.340.5" }, "require-dev": { - "phpunit/phpunit": "^10.5|^11.0", + "phpunit/phpunit": "^10.5|^11.0|^12.0", "phpstan/phpstan": "^1.8|^2.0" }, "conflict": { diff --git a/packages/Symfony/composer.json b/packages/Symfony/composer.json index 1d145de6e..303c834dc 100644 --- a/packages/Symfony/composer.json +++ b/packages/Symfony/composer.json @@ -54,7 +54,7 @@ "ext-rdkafka": "*", "monolog/monolog": "^2.9|^3.3.1", "phpstan/phpstan": "^1.8|^2.0", - "phpunit/phpunit": "^10.5|^11.0", + "phpunit/phpunit": "^10.5|^11.0|^12.0", "symfony/amqp-messenger": "^6.4|^7.0|^8.0", "symfony/doctrine-messenger": "^6.4|^7.0|^8.0", "symfony/expression-language": "^6.4|^7.0|^8.0", diff --git a/packages/Symfony/tests/phpunit/SymfonyMessengerMessageChannelUnitTest.php b/packages/Symfony/tests/phpunit/SymfonyMessengerMessageChannelUnitTest.php index da3d6a6d6..62257bde4 100644 --- a/packages/Symfony/tests/phpunit/SymfonyMessengerMessageChannelUnitTest.php +++ b/packages/Symfony/tests/phpunit/SymfonyMessengerMessageChannelUnitTest.php @@ -36,12 +36,12 @@ public function internalDisableErrorHandler(): void public function setUp(): void { $this->messageConverter = new SymfonyMessageConverter( - $this->createMock(HeaderMapper::class), + $this->createStub(HeaderMapper::class), 'mode', - $this->createMock(ConversionService::class) + $this->createStub(ConversionService::class) ); $this->envelope = new Envelope( - $this->createMock(stdClass::class), + $this->createStub(stdClass::class), [ new MetadataStamp([]), ], @@ -50,7 +50,7 @@ public function setUp(): void public function test_symfony_envelope_returns_array(): void { - $transport = $this->createMock(DoctrineTransport::class); + $transport = $this->createStub(DoctrineTransport::class); $transport->method('get')->willReturn([$this->envelope]); $transport->method('getMessageCount')->willReturn(1); $messageChannel = new SymfonyMessengerMessageChannel($transport, $this->messageConverter); @@ -65,7 +65,7 @@ private function singleMessageGenerator(): iterable public function test_symfony_envelope_returns_generator(): void { - $transport = $this->createMock(AmqpTransport::class); + $transport = $this->createStub(AmqpTransport::class); $transport->method('get')->willReturn($this->singleMessageGenerator()); $transport->method('getMessageCount')->willReturn(1); $messageChannel = new SymfonyMessengerMessageChannel($transport, $this->messageConverter); diff --git a/packages/Tempest/composer.json b/packages/Tempest/composer.json index b4d19dcba..0706defe3 100644 --- a/packages/Tempest/composer.json +++ b/packages/Tempest/composer.json @@ -59,7 +59,7 @@ "enqueue/dbal": "DBAL-backed message queue transport; required if you want durable async messaging over a relational database using ecotone/dbal." }, "require-dev": { - "phpunit/phpunit": "^11.0", + "phpunit/phpunit": "^11.0|^12.0", "phpstan/phpstan": "^1.8|^2.0", "symfony/expression-language": "^6.4|^7.0|^8.0", "ecotone/dbal": "~1.325.0", From c14add13abc3040bc01b829dba512eadbd4775a1 Mon Sep 17 00:00:00 2001 From: Dariusz Gafka Date: Sun, 16 Aug 2026 08:00:00 +0200 Subject: [PATCH 12/13] fix: split FullAppBenchmarkCase so PHPBench subjects don't extend TestCase PHPUnit 12 made TestCase::__construct() final with a mandatory string argument, so FullAppBenchmarkCase could no longer override it to supply a default benchmark name. But PHPBench instantiates benchmark classes with `new $class()` (no arguments, see phpbench's remote.template), so any zero-arg instantiation of a class ultimately extending TestCase now fatals with an ArgumentCountError. FullAppBenchmarkCase served two different roles: a shared base for real PHPUnit tests (FullAppTestCase, ProxyCacheGenerationTest, run via vendor/bin/phpunit) and a shared base for PHPBench-only benchmark subjects (AsynchronousStackBenchmark, HttpStackBenchmark, EventSourcingBenchmark, BootingEcotoneBenchmark, KernelBootBenchmark). Extract the shared behavior into FullAppBenchmarkCaseTrait. The PHPUnit-facing FullAppBenchmarkCase class keeps extending TestCase (PHPUnit constructs its own TestCase subclasses, passing the test method name itself, so the final constructor is not an issue there). The PHPBench-only benchmark classes now extend PHPUnit\Framework\Assert instead (for their assertCount/assertSame helpers) and use the trait directly, avoiding TestCase's constructor entirely. --- .../Benchmark/AsynchronousStackBenchmark.php | 4 +++- .../Benchmark/BootingEcotoneBenchmark.php | 4 +++- Monorepo/Benchmark/EventSourcingBenchmark.php | 3 ++- Monorepo/Benchmark/FullAppBenchmarkCase.php | 22 ++++++++++++++----- Monorepo/Benchmark/HttpStackBenchmark.php | 3 ++- Monorepo/Benchmark/KernelBootBenchmark.php | 4 +++- 6 files changed, 30 insertions(+), 10 deletions(-) diff --git a/Monorepo/Benchmark/AsynchronousStackBenchmark.php b/Monorepo/Benchmark/AsynchronousStackBenchmark.php index e33203412..9ddbf9fa7 100644 --- a/Monorepo/Benchmark/AsynchronousStackBenchmark.php +++ b/Monorepo/Benchmark/AsynchronousStackBenchmark.php @@ -12,12 +12,14 @@ use Monorepo\ExampleApp\Common\Domain\Order\ShippingAddress; use Monorepo\ExampleApp\Common\Infrastructure\Configuration; use Monorepo\ExampleApp\ExampleAppCaseTrait; +use PHPUnit\Framework\Assert; use Psr\Container\ContainerInterface; use Ramsey\Uuid\Uuid; #[Warmup(1), Revs(10), Iterations(5)] -final class AsynchronousStackBenchmark extends FullAppBenchmarkCase +final class AsynchronousStackBenchmark extends Assert { + use FullAppBenchmarkCaseTrait; use ExampleAppCaseTrait; public function executeForSymfony(ContainerInterface $container, \Symfony\Component\HttpKernel\Kernel $kernel): void diff --git a/Monorepo/Benchmark/BootingEcotoneBenchmark.php b/Monorepo/Benchmark/BootingEcotoneBenchmark.php index f5a6e2a9b..c9511c6c5 100644 --- a/Monorepo/Benchmark/BootingEcotoneBenchmark.php +++ b/Monorepo/Benchmark/BootingEcotoneBenchmark.php @@ -8,11 +8,13 @@ use PhpBench\Attributes\Iterations; use PhpBench\Attributes\Revs; use PhpBench\Attributes\Warmup; +use PHPUnit\Framework\Assert; use Psr\Container\ContainerInterface; #[Warmup(1), Revs(10), Iterations(5)] -class BootingEcotoneBenchmark extends FullAppBenchmarkCase +class BootingEcotoneBenchmark extends Assert { + use FullAppBenchmarkCaseTrait; use ExampleAppCaseTrait; public function executeForSymfony(ContainerInterface $container, \Symfony\Component\HttpKernel\Kernel $kernel): void diff --git a/Monorepo/Benchmark/EventSourcingBenchmark.php b/Monorepo/Benchmark/EventSourcingBenchmark.php index 16d1b3b03..f5192507c 100644 --- a/Monorepo/Benchmark/EventSourcingBenchmark.php +++ b/Monorepo/Benchmark/EventSourcingBenchmark.php @@ -19,8 +19,9 @@ use Ramsey\Uuid\Uuid; #[Warmup(1), Revs(10), Iterations(5)] -class EventSourcingBenchmark extends FullAppBenchmarkCase +class EventSourcingBenchmark extends Assert { + use FullAppBenchmarkCaseTrait; use ExampleAppEventSourcingCaseTrait; public static function skippedPackages(): array diff --git a/Monorepo/Benchmark/FullAppBenchmarkCase.php b/Monorepo/Benchmark/FullAppBenchmarkCase.php index afa6b2598..0e4c1b764 100644 --- a/Monorepo/Benchmark/FullAppBenchmarkCase.php +++ b/Monorepo/Benchmark/FullAppBenchmarkCase.php @@ -25,12 +25,8 @@ /** * @BeforeClassMethods("clearAllCaches") */ -abstract class FullAppBenchmarkCase extends TestCase +trait FullAppBenchmarkCaseTrait { - public function __construct(?string $name = 'benchmark') - { - parent::__construct($name); - } public function bench_symfony_prod() { self::productionEnvironments(); @@ -268,3 +264,19 @@ public static function runConsumerForMessaging(string $consumerName, ConfiguredM ); } } + +/** + * Base for real PHPUnit test cases (run via `vendor/bin/phpunit`) that need the shared + * app-booting/cache-clearing/benchmark helpers above. PHPUnit constructs its TestCase + * subclasses itself (passing the test method name into TestCase::__construct(), which + * PHPUnit 12 made final), so extending TestCase here is safe. + * + * Pure PHPBench subjects (executed via `new $class()` with no constructor arguments, + * see phpbench's remote.template) must NOT extend this class — TestCase::__construct() + * requires a mandatory argument PHPBench never supplies. They should extend + * PHPUnit\Framework\Assert directly and `use FullAppBenchmarkCaseTrait;` instead. + */ +abstract class FullAppBenchmarkCase extends TestCase +{ + use FullAppBenchmarkCaseTrait; +} diff --git a/Monorepo/Benchmark/HttpStackBenchmark.php b/Monorepo/Benchmark/HttpStackBenchmark.php index f83689fc6..b217b6697 100644 --- a/Monorepo/Benchmark/HttpStackBenchmark.php +++ b/Monorepo/Benchmark/HttpStackBenchmark.php @@ -19,8 +19,9 @@ use Symfony\Component\HttpFoundation\Request as SymfonyRequest; #[Warmup(1), Revs(10), Iterations(5)] -class HttpStackBenchmark extends FullAppBenchmarkCase +class HttpStackBenchmark extends Assert { + use FullAppBenchmarkCaseTrait; use ExampleAppCaseTrait; public function executeForSymfony(ContainerInterface $container, \Symfony\Component\HttpKernel\Kernel $kernel): void { diff --git a/Monorepo/Benchmark/KernelBootBenchmark.php b/Monorepo/Benchmark/KernelBootBenchmark.php index 81953069a..50f3ff61c 100644 --- a/Monorepo/Benchmark/KernelBootBenchmark.php +++ b/Monorepo/Benchmark/KernelBootBenchmark.php @@ -8,11 +8,13 @@ use PhpBench\Attributes\Iterations; use PhpBench\Attributes\Revs; use PhpBench\Attributes\Warmup; +use PHPUnit\Framework\Assert; use Psr\Container\ContainerInterface; #[Warmup(1), Revs(10), Iterations(5)] -class KernelBootBenchmark extends FullAppBenchmarkCase +class KernelBootBenchmark extends Assert { + use FullAppBenchmarkCaseTrait; use ExampleAppCaseTrait; public function executeForSymfony(ContainerInterface $container, \Symfony\Component\HttpKernel\Kernel $kernel): void From 189193d36193a7ceae16d021979d804f80adcdce Mon Sep 17 00:00:00 2001 From: Dariusz Gafka Date: Sun, 16 Aug 2026 08:00:00 +0200 Subject: [PATCH 13/13] fix: move FullAppBenchmarkCaseTrait to its own file for PSR-4 autoloading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FullAppBenchmarkCase.php defined both the FullAppBenchmarkCase class and a same-file FullAppBenchmarkCaseTrait. Composer's default (non-optimized) PSR-4 autoloader only resolves a symbol whose name matches its containing file, so the trait was unresolvable at runtime unless the containing file happened to be required first — this repo doesn't set optimize-autoloader, and CI's composer update doesn't pass -o, so PHPBench's reflector process (which autoloads the benchmark classes fresh) fataled with "Trait ... not found" for every PHPBench-only subclass (AsynchronousStackBenchmark, HttpStackBenchmark, EventSourcingBenchmark, BootingEcotoneBenchmark, KernelBootBenchmark). Move the trait into its own FullAppBenchmarkCaseTrait.php, matching this codebase's one-symbol-per-file convention everywhere else. --- Monorepo/Benchmark/FullAppBenchmarkCase.php | 267 +----------------- .../Benchmark/FullAppBenchmarkCaseTrait.php | 265 +++++++++++++++++ 2 files changed, 268 insertions(+), 264 deletions(-) create mode 100644 Monorepo/Benchmark/FullAppBenchmarkCaseTrait.php diff --git a/Monorepo/Benchmark/FullAppBenchmarkCase.php b/Monorepo/Benchmark/FullAppBenchmarkCase.php index 0e4c1b764..0f486e5ae 100644 --- a/Monorepo/Benchmark/FullAppBenchmarkCase.php +++ b/Monorepo/Benchmark/FullAppBenchmarkCase.php @@ -2,274 +2,13 @@ namespace Monorepo\Benchmark; -use Ecotone\Messaging\Config\ConfiguredMessagingSystem; -use Ecotone\Messaging\Config\ModulePackageList; -use Ecotone\Messaging\Endpoint\ExecutionPollingMetadata; -use Illuminate\Foundation\Application; -use Illuminate\Foundation\Http\Kernel as LaravelKernel; -use Illuminate\Support\Facades\Artisan; - -use function json_encode; - -use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; -use Psr\Container\ContainerInterface; - -use function putenv; - -use Symfony\Bundle\FrameworkBundle\Console\Application as SymfonyConsoleApplication; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\NullOutput; -use Symfony\Component\HttpKernel\Kernel as SymfonyKernel; - -/** - * @BeforeClassMethods("clearAllCaches") - */ -trait FullAppBenchmarkCaseTrait -{ - public function bench_symfony_prod() - { - self::productionEnvironments(); - $kernel = self::bootSymfonyKernel(environment: 'prod', debug: false); - $container = $kernel->getContainer(); - - $this->executeForSymfony($container, $kernel); - } - - public function bench_symfony_dev() - { - self::developmentEnvironments(); - $kernel = self::bootSymfonyKernel(environment: 'dev', debug: true); - $container = $kernel->getContainer(); - - $this->executeForSymfony($container, $kernel); - } - - public function bench_laravel_prod(): void - { - self::productionEnvironments(); - $app = self::createLaravelApplication(); - - $this->executeForLaravel($app, $app->get(LaravelKernel::class)); - } - - public function bench_laravel_dev(): void - { - self::developmentEnvironments(); - $app = self::createLaravelApplication(); - - $this->executeForLaravel($app, $app->get(LaravelKernel::class)); - } - - public function bench_lite_application_prod() - { - self::productionEnvironments(); - $bootstrap = require static::getProjectDir() . '/LiteApplication/app.php'; - $messagingSystem = $bootstrap(true); - $this->executeForLiteApplication(new LiteContainerAccessor($messagingSystem)); - } - - public function bench_lite_application_dev() - { - self::developmentEnvironments(); - $bootstrap = require static::getProjectDir() . '/LiteApplication/app.php'; - $messagingSystem = $bootstrap(false); - $this->executeForLiteApplication(new LiteContainerAccessor($messagingSystem)); - } - - public function bench_lite_prod() - { - self::productionEnvironments(); - $bootstrap = require static::getProjectDir() . '/Lite/app.php'; - $messagingSystem = $bootstrap(true); - $this->executeForLite($messagingSystem); - } - - public function bench_lite_dev() - { - self::developmentEnvironments(); - $bootstrap = require static::getProjectDir() . '/Lite/app.php'; - $messagingSystem = $bootstrap(false); - $this->executeForLite($messagingSystem); - } - - public static function clearAllCaches(): void - { - self::clearLaravelCache(); - self::clearSymfonyCache(); - self::clearLiteApplicationCache(); - self::clearLiteCache(); - } - - public static function clearLiteCache(): void - { - self::deleteFiles( - static::getProjectDir() . '/Lite/var/cache', - false - ); - } - - public static function clearLiteApplicationCache(): void - { - self::deleteFiles( - static::getProjectDir() . '/LiteApplication/var/cache', - false - ); - } - - /** - * Calling config:cache always dumps the cache, - * this means we need to do it before the benchmark - */ - public function dumpLaravelCache(): void - { - self::productionEnvironments(); - self::createLaravelApplication(); - Artisan::call('route:cache'); - Artisan::call('config:cache'); - } - - public static function clearLaravelCache(): void - { - self::deleteFiles( - static::getProjectDir() . '/Laravel/storage/framework/cache/data', - false - ); - } - - public static function clearSymfonyCache(): void - { - self::deleteFiles( - static::getProjectDir() . '/Symfony/var/cache', - false - ); - } - - private static function deleteFiles(string $target, bool $deleteDirectory): void - { - if (is_dir($target)) { - \Ecotone\Messaging\Support\Assert::isTrue( - is_writable($target), - "Not enough permissions to delete from cache directory {$target}" - ); - $files = glob($target . '*', GLOB_MARK); - - foreach ($files as $file) { - self::deleteFiles($file, true); - } - - if ($deleteDirectory) { - rmdir($target); - } - } elseif (is_file($target)) { - Assert::isTrue( - is_writable($target), - "Not enough permissions to delete cache file {$target}" - ); - unlink($target); - } - } - - abstract public function executeForSymfony( - ContainerInterface $container, - SymfonyKernel $kernel - ): void; - - abstract public function executeForLaravel( - ContainerInterface $container, - LaravelKernel $kernel - ): void; - - abstract public function executeForLiteApplication( - ContainerInterface $container - ): void; - - abstract public function executeForLite( - ConfiguredMessagingSystem $messagingSystem - ): void; - - abstract protected static function getSymfonyKernelClass(): string; - abstract protected static function getProjectDir(): string; - - private static function productionEnvironments(): void - { - putenv('APP_ENV=prod'); - putenv('APP_DEBUG=false'); - putenv(sprintf('APP_SKIPPED_PACKAGES=%s', json_encode(static::skippedPackages(), JSON_THROW_ON_ERROR))); - } - - private static function developmentEnvironments(): void - { - putenv('APP_ENV=dev'); - putenv('APP_DEBUG=true'); - putenv(sprintf('APP_SKIPPED_PACKAGES=%s', json_encode(static::skippedPackages(), JSON_THROW_ON_ERROR))); - } - - public static function skippedPackages(): array - { - return ModulePackageList::allPackagesExcept([ModulePackageList::ASYNCHRONOUS_PACKAGE]); - } - - private static function createLaravelApplication(): Application - { - $app = require static::getProjectDir() . '/Laravel/bootstrap/app.php'; - - $app->make(LaravelKernel::class)->bootstrap(); - - return $app; - } - - public static function bootSymfonyKernel(string $environment, bool $debug): SymfonyKernel - { - $kernelClass = static::getSymfonyKernelClass(); - $kernel = new $kernelClass($environment, $debug); - $kernel->boot(); - return $kernel; - } - - public static function executeSymfonyConsoleCommand(SymfonyKernel $kernel, string $commandName, array $input): void - { - $application = new SymfonyConsoleApplication($kernel); - $result = $application->find($commandName)->run( - new ArrayInput($input), - new NullOutput() - ); - - Assert::assertSame(0, $result); - } - - public static function runConsumerForSymfony(string $consumerName, SymfonyKernel $kernel, bool $stopOnFailure = true): void - { - self::executeSymfonyConsoleCommand( - $kernel, - 'ecotone:run', - ['consumerName' => $consumerName, '--stopOnFailure' => $stopOnFailure, '--executionTimeLimit' => 2000, '--finishWhenNoMessages' => true] - ); - } - - public static function runConsumerForLaravel(string $consumerName, bool $stopOnFailure = true): void - { - Artisan::call( - 'ecotone:run', - ['consumerName' => $consumerName, '--stopOnFailure' => $stopOnFailure, '--executionTimeLimit' => 2000, '--finishWhenNoMessages' => true] - ); - } - - public static function runConsumerForMessaging(string $consumerName, ConfiguredMessagingSystem $messagingSystem, bool $stopOnFailure = true): void - { - $messagingSystem->run( - $consumerName, - ExecutionPollingMetadata::createWithFinishWhenNoMessages($stopOnFailure) - ->withExecutionTimeLimitInMilliseconds(2000) - ); - } -} /** * Base for real PHPUnit test cases (run via `vendor/bin/phpunit`) that need the shared - * app-booting/cache-clearing/benchmark helpers above. PHPUnit constructs its TestCase - * subclasses itself (passing the test method name into TestCase::__construct(), which - * PHPUnit 12 made final), so extending TestCase here is safe. + * app-booting/cache-clearing/benchmark helpers in FullAppBenchmarkCaseTrait. PHPUnit + * constructs its TestCase subclasses itself (passing the test method name into + * TestCase::__construct(), which PHPUnit 12 made final), so extending TestCase here is safe. * * Pure PHPBench subjects (executed via `new $class()` with no constructor arguments, * see phpbench's remote.template) must NOT extend this class — TestCase::__construct() diff --git a/Monorepo/Benchmark/FullAppBenchmarkCaseTrait.php b/Monorepo/Benchmark/FullAppBenchmarkCaseTrait.php new file mode 100644 index 000000000..51a848b3e --- /dev/null +++ b/Monorepo/Benchmark/FullAppBenchmarkCaseTrait.php @@ -0,0 +1,265 @@ +getContainer(); + + $this->executeForSymfony($container, $kernel); + } + + public function bench_symfony_dev() + { + self::developmentEnvironments(); + $kernel = self::bootSymfonyKernel(environment: 'dev', debug: true); + $container = $kernel->getContainer(); + + $this->executeForSymfony($container, $kernel); + } + + public function bench_laravel_prod(): void + { + self::productionEnvironments(); + $app = self::createLaravelApplication(); + + $this->executeForLaravel($app, $app->get(LaravelKernel::class)); + } + + public function bench_laravel_dev(): void + { + self::developmentEnvironments(); + $app = self::createLaravelApplication(); + + $this->executeForLaravel($app, $app->get(LaravelKernel::class)); + } + + public function bench_lite_application_prod() + { + self::productionEnvironments(); + $bootstrap = require static::getProjectDir() . '/LiteApplication/app.php'; + $messagingSystem = $bootstrap(true); + $this->executeForLiteApplication(new LiteContainerAccessor($messagingSystem)); + } + + public function bench_lite_application_dev() + { + self::developmentEnvironments(); + $bootstrap = require static::getProjectDir() . '/LiteApplication/app.php'; + $messagingSystem = $bootstrap(false); + $this->executeForLiteApplication(new LiteContainerAccessor($messagingSystem)); + } + + public function bench_lite_prod() + { + self::productionEnvironments(); + $bootstrap = require static::getProjectDir() . '/Lite/app.php'; + $messagingSystem = $bootstrap(true); + $this->executeForLite($messagingSystem); + } + + public function bench_lite_dev() + { + self::developmentEnvironments(); + $bootstrap = require static::getProjectDir() . '/Lite/app.php'; + $messagingSystem = $bootstrap(false); + $this->executeForLite($messagingSystem); + } + + public static function clearAllCaches(): void + { + self::clearLaravelCache(); + self::clearSymfonyCache(); + self::clearLiteApplicationCache(); + self::clearLiteCache(); + } + + public static function clearLiteCache(): void + { + self::deleteFiles( + static::getProjectDir() . '/Lite/var/cache', + false + ); + } + + public static function clearLiteApplicationCache(): void + { + self::deleteFiles( + static::getProjectDir() . '/LiteApplication/var/cache', + false + ); + } + + /** + * Calling config:cache always dumps the cache, + * this means we need to do it before the benchmark + */ + public function dumpLaravelCache(): void + { + self::productionEnvironments(); + self::createLaravelApplication(); + Artisan::call('route:cache'); + Artisan::call('config:cache'); + } + + public static function clearLaravelCache(): void + { + self::deleteFiles( + static::getProjectDir() . '/Laravel/storage/framework/cache/data', + false + ); + } + + public static function clearSymfonyCache(): void + { + self::deleteFiles( + static::getProjectDir() . '/Symfony/var/cache', + false + ); + } + + private static function deleteFiles(string $target, bool $deleteDirectory): void + { + if (is_dir($target)) { + \Ecotone\Messaging\Support\Assert::isTrue( + is_writable($target), + "Not enough permissions to delete from cache directory {$target}" + ); + $files = glob($target . '*', GLOB_MARK); + + foreach ($files as $file) { + self::deleteFiles($file, true); + } + + if ($deleteDirectory) { + rmdir($target); + } + } elseif (is_file($target)) { + Assert::isTrue( + is_writable($target), + "Not enough permissions to delete cache file {$target}" + ); + unlink($target); + } + } + + abstract public function executeForSymfony( + ContainerInterface $container, + SymfonyKernel $kernel + ): void; + + abstract public function executeForLaravel( + ContainerInterface $container, + LaravelKernel $kernel + ): void; + + abstract public function executeForLiteApplication( + ContainerInterface $container + ): void; + + abstract public function executeForLite( + ConfiguredMessagingSystem $messagingSystem + ): void; + + abstract protected static function getSymfonyKernelClass(): string; + abstract protected static function getProjectDir(): string; + + private static function productionEnvironments(): void + { + putenv('APP_ENV=prod'); + putenv('APP_DEBUG=false'); + putenv(sprintf('APP_SKIPPED_PACKAGES=%s', json_encode(static::skippedPackages(), JSON_THROW_ON_ERROR))); + } + + private static function developmentEnvironments(): void + { + putenv('APP_ENV=dev'); + putenv('APP_DEBUG=true'); + putenv(sprintf('APP_SKIPPED_PACKAGES=%s', json_encode(static::skippedPackages(), JSON_THROW_ON_ERROR))); + } + + public static function skippedPackages(): array + { + return ModulePackageList::allPackagesExcept([ModulePackageList::ASYNCHRONOUS_PACKAGE]); + } + + private static function createLaravelApplication(): Application + { + $app = require static::getProjectDir() . '/Laravel/bootstrap/app.php'; + + $app->make(LaravelKernel::class)->bootstrap(); + + return $app; + } + + public static function bootSymfonyKernel(string $environment, bool $debug): SymfonyKernel + { + $kernelClass = static::getSymfonyKernelClass(); + $kernel = new $kernelClass($environment, $debug); + $kernel->boot(); + return $kernel; + } + + public static function executeSymfonyConsoleCommand(SymfonyKernel $kernel, string $commandName, array $input): void + { + $application = new SymfonyConsoleApplication($kernel); + $result = $application->find($commandName)->run( + new ArrayInput($input), + new NullOutput() + ); + + Assert::assertSame(0, $result); + } + + public static function runConsumerForSymfony(string $consumerName, SymfonyKernel $kernel, bool $stopOnFailure = true): void + { + self::executeSymfonyConsoleCommand( + $kernel, + 'ecotone:run', + ['consumerName' => $consumerName, '--stopOnFailure' => $stopOnFailure, '--executionTimeLimit' => 2000, '--finishWhenNoMessages' => true] + ); + } + + public static function runConsumerForLaravel(string $consumerName, bool $stopOnFailure = true): void + { + Artisan::call( + 'ecotone:run', + ['consumerName' => $consumerName, '--stopOnFailure' => $stopOnFailure, '--executionTimeLimit' => 2000, '--finishWhenNoMessages' => true] + ); + } + + public static function runConsumerForMessaging(string $consumerName, ConfiguredMessagingSystem $messagingSystem, bool $stopOnFailure = true): void + { + $messagingSystem->run( + $consumerName, + ExecutionPollingMetadata::createWithFinishWhenNoMessages($stopOnFailure) + ->withExecutionTimeLimitInMilliseconds(2000) + ); + } +}