diff --git a/composer.json b/composer.json index c1c92405403..02dd37b9349 100644 --- a/composer.json +++ b/composer.json @@ -114,7 +114,7 @@ "psr/container": "^1.0 || ^2.0", "symfony/deprecation-contracts": "^3.1", "symfony/http-foundation": "^6.4.14 || ^7.0 || ^8.0", - "symfony/http-kernel": "^6.4 || ^7.0 || ^8.0", + "symfony/http-kernel": "^6.4.13 || ^7.0 || ^8.0", "symfony/property-access": "^6.4 || ^7.0 || ^8.0", "symfony/property-info": "^6.4 || ^7.1 || ^8.0", "symfony/serializer": "^6.4 || ^7.0 || ^8.0", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d8527fddfa3..9f177ef37f1 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -3,7 +3,8 @@ backupGlobals="false" bootstrap="tests/Fixtures/app/bootstrap.php" colors="true" - cacheDirectory=".phpunit.cache"> + cacheDirectory=".phpunit.cache" + failOnRisky="true"> diff --git a/src/Doctrine/Odm/Tests/AppKernel.php b/src/Doctrine/Odm/Tests/AppKernel.php index e33f36dced5..773a4e31592 100644 --- a/src/Doctrine/Odm/Tests/AppKernel.php +++ b/src/Doctrine/Odm/Tests/AppKernel.php @@ -18,7 +18,6 @@ use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\HttpKernel\Kernel; /** @@ -43,12 +42,6 @@ public function registerBundles(): array return [ new FrameworkBundle(), new DoctrineMongoDBBundle(), - new class extends Bundle { - public function shutdown(): void - { - restore_exception_handler(); - } - }, ]; } diff --git a/src/Doctrine/Odm/Tests/DoctrineMongoDbOdmFilterTestCase.php b/src/Doctrine/Odm/Tests/DoctrineMongoDbOdmFilterTestCase.php index 02f6a2f68fb..388b1bbade4 100644 --- a/src/Doctrine/Odm/Tests/DoctrineMongoDbOdmFilterTestCase.php +++ b/src/Doctrine/Odm/Tests/DoctrineMongoDbOdmFilterTestCase.php @@ -17,8 +17,11 @@ use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Repository\DocumentRepository; use Doctrine\Persistence\ManagerRegistry; +use PHPUnit\Framework\Attributes\After; +use PHPUnit\Framework\Attributes\Before; use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\Component\ErrorHandler\ErrorHandler; /** * @internal @@ -37,6 +40,8 @@ abstract class DoctrineMongoDbOdmFilterTestCase extends KernelTestCase protected string $filterClass; + private bool $symfonyErrorHandlerWasRegistered = false; + protected function setUp(): void { self::bootKernel(); @@ -46,6 +51,34 @@ protected function setUp(): void $this->repository = $this->manager->getRepository($this->resourceClass); } + /** + * Symfony\Bundle\FrameworkBundle\FrameworkBundle::boot() registers Symfony's ErrorHandler via + * set_exception_handler() but never unregisters it: each kernel boot leaks one entry on the + * exception handler stack, which PHPUnit flags as Risky. Track whether the handler was already + * present before the test so we only pop the entry our own test introduced. + */ + #[Before] + protected function captureExceptionHandlerStack(): void + { + $this->symfonyErrorHandlerWasRegistered = self::isSymfonyErrorHandlerRegistered(); + } + + #[After] + protected function restoreExceptionHandlerStack(): void + { + if (!$this->symfonyErrorHandlerWasRegistered && self::isSymfonyErrorHandlerRegistered()) { + restore_exception_handler(); + } + } + + private static function isSymfonyErrorHandlerRegistered(): bool + { + $current = set_exception_handler(static fn () => null); + restore_exception_handler(); + + return \is_array($current) && $current[0] instanceof ErrorHandler; + } + #[DataProvider('provideApplyTestData')] public function testApply(?array $properties, array $filterParameters, array $expectedPipeline, ?callable $factory = null, ?string $resourceClass = null): void { diff --git a/src/Doctrine/Orm/Tests/AppKernel.php b/src/Doctrine/Orm/Tests/AppKernel.php index 2037665337a..66c5948a28c 100644 --- a/src/Doctrine/Orm/Tests/AppKernel.php +++ b/src/Doctrine/Orm/Tests/AppKernel.php @@ -18,7 +18,6 @@ use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\HttpKernel\Kernel; /** @@ -44,12 +43,6 @@ public function registerBundles(): array new FrameworkBundle(), new DoctrineBundle(), new TestBundle(), - new class extends Bundle { - public function shutdown(): void - { - restore_exception_handler(); - } - }, ]; } diff --git a/src/Doctrine/Orm/Tests/DoctrineOrmFilterTestCase.php b/src/Doctrine/Orm/Tests/DoctrineOrmFilterTestCase.php index e588c48f124..d43e67b51f3 100644 --- a/src/Doctrine/Orm/Tests/DoctrineOrmFilterTestCase.php +++ b/src/Doctrine/Orm/Tests/DoctrineOrmFilterTestCase.php @@ -18,8 +18,11 @@ use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator; use Doctrine\ORM\EntityRepository; use Doctrine\Persistence\ManagerRegistry; +use PHPUnit\Framework\Attributes\After; +use PHPUnit\Framework\Attributes\Before; use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\Component\ErrorHandler\ErrorHandler; /** * @internal @@ -38,6 +41,8 @@ abstract class DoctrineOrmFilterTestCase extends KernelTestCase protected string $filterClass; + private bool $symfonyErrorHandlerWasRegistered = false; + protected function setUp(): void { self::bootKernel(); @@ -46,6 +51,34 @@ protected function setUp(): void $this->repository = $this->managerRegistry->getManagerForClass(Dummy::class)->getRepository(Dummy::class); } + /** + * Symfony\Bundle\FrameworkBundle\FrameworkBundle::boot() registers Symfony's ErrorHandler via + * set_exception_handler() but never unregisters it: each kernel boot leaks one entry on the + * exception handler stack, which PHPUnit flags as Risky. Track whether the handler was already + * present before the test so we only pop the entry our own test introduced. + */ + #[Before] + protected function captureExceptionHandlerStack(): void + { + $this->symfonyErrorHandlerWasRegistered = self::isSymfonyErrorHandlerRegistered(); + } + + #[After] + protected function restoreExceptionHandlerStack(): void + { + if (!$this->symfonyErrorHandlerWasRegistered && self::isSymfonyErrorHandlerRegistered()) { + restore_exception_handler(); + } + } + + private static function isSymfonyErrorHandlerRegistered(): bool + { + $current = set_exception_handler(static fn () => null); + restore_exception_handler(); + + return \is_array($current) && $current[0] instanceof ErrorHandler; + } + #[DataProvider('provideApplyTestData')] public function testApply(?array $properties, array $filterParameters, string $expectedDql, ?array $expectedParameters = null, ?callable $factory = null, ?string $resourceClass = null): void { diff --git a/src/State/composer.json b/src/State/composer.json index 7f8e4250337..9e383a3b0af 100644 --- a/src/State/composer.json +++ b/src/State/composer.json @@ -30,7 +30,7 @@ "php": ">=8.2", "api-platform/metadata": "^4.3", "psr/container": "^1.0 || ^2.0", - "symfony/http-kernel": "^6.4 || ^7.0 || ^8.0", + "symfony/http-kernel": "^6.4.13 || ^7.0 || ^8.0", "symfony/serializer": "^6.4 || ^7.0 || ^8.0", "symfony/translation-contracts": "^3.0", "symfony/deprecation-contracts": "^3.1" diff --git a/src/Symfony/Bundle/Test/ApiTestCase.php b/src/Symfony/Bundle/Test/ApiTestCase.php index 211ed631528..891b5f09ab9 100644 --- a/src/Symfony/Bundle/Test/ApiTestCase.php +++ b/src/Symfony/Bundle/Test/ApiTestCase.php @@ -14,9 +14,12 @@ namespace ApiPlatform\Symfony\Bundle\Test; use ApiPlatform\Metadata\IriConverterInterface; +use PHPUnit\Framework\Attributes\After; +use PHPUnit\Framework\Attributes\Before; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\BrowserKit\AbstractBrowser; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\ErrorHandler\ErrorHandler; use Symfony\Component\HttpClient\HttpClientTrait; /** @@ -38,6 +41,37 @@ abstract class ApiTestCase extends KernelTestCase */ protected static ?bool $alwaysBootKernel = null; + private bool $symfonyErrorHandlerWasRegistered = false; + + /** + * Symfony\Bundle\FrameworkBundle\FrameworkBundle::boot() registers Symfony's ErrorHandler via + * set_exception_handler() but never unregisters it: each kernel boot leaks one entry on the + * exception handler stack, which PHPUnit flags as Risky. Track whether the handler was already + * present before the test (e.g. the kernel was booted from setUpBeforeClass) so we only pop + * the entry our own test introduced. + */ + #[Before] + protected function captureExceptionHandlerStack(): void + { + $this->symfonyErrorHandlerWasRegistered = self::isSymfonyErrorHandlerRegistered(); + } + + #[After] + protected function restoreExceptionHandlerStack(): void + { + if (!$this->symfonyErrorHandlerWasRegistered && self::isSymfonyErrorHandlerRegistered()) { + restore_exception_handler(); + } + } + + private static function isSymfonyErrorHandlerRegistered(): bool + { + $current = set_exception_handler(static fn () => null); + restore_exception_handler(); + + return \is_array($current) && $current[0] instanceof ErrorHandler; + } + /** * Creates a Client. * diff --git a/src/Symfony/Tests/EventListener/ErrorListenerTest.php b/src/Symfony/Tests/EventListener/ErrorListenerTest.php index effa85bb103..d9e0a97fffe 100644 --- a/src/Symfony/Tests/EventListener/ErrorListenerTest.php +++ b/src/Symfony/Tests/EventListener/ErrorListenerTest.php @@ -30,11 +30,6 @@ class ErrorListenerTest extends TestCase { - protected function tearDown(): void - { - restore_exception_handler(); - } - public function testDuplicateException(): void { $exception = new \Exception(); diff --git a/src/Symfony/composer.json b/src/Symfony/composer.json index 53e3a939885..820533edcb2 100644 --- a/src/Symfony/composer.json +++ b/src/Symfony/composer.json @@ -41,6 +41,7 @@ "api-platform/openapi": "^4.3", "symfony/asset": "^6.4 || ^7.0 || ^8.0", "symfony/finder": "^6.4 || ^7.0 || ^8.0", + "symfony/http-kernel": "^6.4.13 || ^7.0 || ^8.0", "symfony/property-info": "^6.4 || ^7.0 || ^8.0", "symfony/property-access": "^6.4 || ^7.0 || ^8.0", "symfony/serializer": "^6.4 || ^7.0 || ^8.0", diff --git a/src/Validator/composer.json b/src/Validator/composer.json index 136b82b4194..392e1f5a8bd 100644 --- a/src/Validator/composer.json +++ b/src/Validator/composer.json @@ -25,7 +25,7 @@ "php": ">=8.2", "api-platform/metadata": "^4.3", "symfony/type-info": "^7.3 || ^8.0", - "symfony/http-kernel": "^6.4 || ^7.1 || ^8.0", + "symfony/http-kernel": "^6.4.13 || ^7.1 || ^8.0", "symfony/serializer": "^6.4 || ^7.1 || ^8.0", "symfony/validator": "^6.4.11 || ^7.1 || ^8.0", "symfony/web-link": "^6.4 || ^7.1 || ^8.0" diff --git a/tests/Fixtures/app/AppKernel.php b/tests/Fixtures/app/AppKernel.php index 0f0a235c468..92ec2c4411e 100644 --- a/tests/Fixtures/app/AppKernel.php +++ b/tests/Fixtures/app/AppKernel.php @@ -98,12 +98,6 @@ public function registerBundles(): array return $bundles; } - public function shutdown(): void - { - parent::shutdown(); - restore_exception_handler(); - } - public function getProjectDir(): string { return __DIR__;