From 69de4702983088d19fb45aa90c52e44635d00d30 Mon Sep 17 00:00:00 2001 From: soyuka Date: Fri, 5 Jun 2026 12:32:00 +0200 Subject: [PATCH] fix(symfony): skip ErrorResourceAttributeLoaderPass on Symfony 6.4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pass introduced in #8231 wires AttributeLoader with the Symfony 7.x constructor signature (bool $allowAnyClass, array $mappedClasses). On Symfony 6.4 the constructor is (?Doctrine\Common\Annotations\Reader), so cache warmup crashed with a TypeError on every container build. Guard the pass via class_exists(AnnotationLoader::class) — that class only exists on the 6.4 branch (removed in 7.0). Also skip the pass when an AttributeLoader is already present in the chain (enable_attributes: true) to avoid duplicating work the framework has already wired. Fixes #8244 --- .../ErrorResourceAttributeLoaderPass.php | 22 ++++++++++-- .../ErrorResourceAttributeLoaderPassTest.php | 35 +++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bundle/DependencyInjection/Compiler/ErrorResourceAttributeLoaderPass.php b/src/Symfony/Bundle/DependencyInjection/Compiler/ErrorResourceAttributeLoaderPass.php index df424a96b9..f3e9f3f9cc 100644 --- a/src/Symfony/Bundle/DependencyInjection/Compiler/ErrorResourceAttributeLoaderPass.php +++ b/src/Symfony/Bundle/DependencyInjection/Compiler/ErrorResourceAttributeLoaderPass.php @@ -18,6 +18,7 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; /** @@ -43,6 +44,25 @@ public function process(ContainerBuilder $container): void return; } + // Symfony 6.4 ships an AttributeLoader with a different constructor signature + // (`?Doctrine\Common\Annotations\Reader`), incompatible with the `allowAnyClass`/`mappedClasses` + // arguments below. The `AnnotationLoader` class only exists on the 6.4 branch + // (removed in 7.0), so its presence is a reliable marker for that signature. See #8244. + if (class_exists(AnnotationLoader::class)) { + return; + } + + $chainLoader = $container->getDefinition('serializer.mapping.chain_loader'); + $loaders = $chainLoader->getArgument(0); + + // Skip when Symfony already wired an AttributeLoader (i.e. `enable_attributes: true`). + // Adding another one would duplicate work and re-process every class twice. + foreach ($loaders as $loader) { + if ($loader instanceof Definition && is_a($loader->getClass(), AttributeLoader::class, true)) { + return; + } + } + $mappedClasses = [ Error::class => [Error::class], ValidationException::class => [ValidationException::class], @@ -50,8 +70,6 @@ public function process(ContainerBuilder $container): void $loaderDefinition = new Definition(AttributeLoader::class, [true, $mappedClasses]); - $chainLoader = $container->getDefinition('serializer.mapping.chain_loader'); - $loaders = $chainLoader->getArgument(0); $loaders[] = $loaderDefinition; $chainLoader->replaceArgument(0, $loaders); diff --git a/tests/Symfony/Bundle/DependencyInjection/Compiler/ErrorResourceAttributeLoaderPassTest.php b/tests/Symfony/Bundle/DependencyInjection/Compiler/ErrorResourceAttributeLoaderPassTest.php index 80694b7151..5705624b12 100644 --- a/tests/Symfony/Bundle/DependencyInjection/Compiler/ErrorResourceAttributeLoaderPassTest.php +++ b/tests/Symfony/Bundle/DependencyInjection/Compiler/ErrorResourceAttributeLoaderPassTest.php @@ -78,6 +78,41 @@ public function testDoesNothingWhenChainLoaderIsAbsent(): void $this->assertFalse($container->hasDefinition('serializer.mapping.chain_loader')); } + public function testDoesNothingWhenChainAlreadyContainsAnAttributeLoader(): void + { + $container = new ContainerBuilder(); + $existing = new Definition(AttributeLoader::class); + $container->setDefinition('serializer.mapping.chain_loader', new Definition(LoaderChain::class, [[$existing]])); + $container->setDefinition('serializer.mapping.cache_warmer', new Definition(\stdClass::class, [[$existing]])); + + (new ErrorResourceAttributeLoaderPass())->process($container); + + $loaders = $container->getDefinition('serializer.mapping.chain_loader')->getArgument(0); + $this->assertCount(1, $loaders, 'pass must not add another AttributeLoader when one is already wired (enable_attributes: true).'); + + $warmerLoaders = $container->getDefinition('serializer.mapping.cache_warmer')->getArgument(0); + $this->assertCount(1, $warmerLoaders); + } + + /** + * @see https://github.com/api-platform/core/issues/8244 + */ + public function testSkipsOnSymfony64SerializerSignature(): void + { + if (!class_exists(\Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader::class)) { + $this->markTestSkipped('Only relevant when running against symfony/serializer 6.4 (AnnotationLoader still present).'); + } + + $container = new ContainerBuilder(); + $container->setDefinition('serializer.mapping.chain_loader', new Definition(LoaderChain::class, [[]])); + $container->setDefinition('serializer.mapping.cache_warmer', new Definition(\stdClass::class, [[]])); + + (new ErrorResourceAttributeLoaderPass())->process($container); + + $this->assertSame([], $container->getDefinition('serializer.mapping.chain_loader')->getArgument(0)); + $this->assertSame([], $container->getDefinition('serializer.mapping.cache_warmer')->getArgument(0)); + } + /** * Mirrors the runtime behavior with `framework.serializer.enable_attributes: false`: * Symfony builds the `AttributeLoader` with `allowAnyClass = false` and no mapped classes,