From 173db46f94ffa7f9f2abcd702273bd738f107f79 Mon Sep 17 00:00:00 2001 From: Jano Paetzold Date: Fri, 28 Nov 2025 23:49:59 +0100 Subject: [PATCH 1/2] [TwigComponent] Prove #3195 occurs (by unit test) --- .../tests/Unit/ComponentFactoryTest.php | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/TwigComponent/tests/Unit/ComponentFactoryTest.php b/src/TwigComponent/tests/Unit/ComponentFactoryTest.php index 0d2dafc01c0..d803ad9ef6f 100644 --- a/src/TwigComponent/tests/Unit/ComponentFactoryTest.php +++ b/src/TwigComponent/tests/Unit/ComponentFactoryTest.php @@ -93,4 +93,32 @@ public function testMetadataForReuseAnonymousConfig() $this->assertSame('foo', $metadata->getName()); $this->assertSame('foo.html.twig', $metadata->getTemplate()); } + + /** + * @testWith ["bar"] + * ["Foo\\Bar"] + */ + public function testPrefersClassComponentOverAnonymous(string $name) + { + $templateFinder = $this->createMock(ComponentTemplateFinderInterface::class); + $templateFinder->expects($this->never()) + ->method('findAnonymousComponentTemplate'); + + $factory = new ComponentFactory( + $templateFinder, + $this->createMock(ServiceLocator::class), + $this->createMock(PropertyAccessorInterface::class), + $this->createMock(EventDispatcherInterface::class), + [ + 'bar' => ['key' => 'bar', 'template' => 'bar.html.twig'], + ], + ['Foo\\Bar' => 'bar'], + $this->createMock(Environment::class), + ); + + $metadata = $factory->metadataFor($name); + + $this->assertSame('bar', $metadata->getName()); + $this->assertSame('bar.html.twig', $metadata->getTemplate()); + } } From 3f5d78576acf077aeb87a466cd6be6668bceec77 Mon Sep 17 00:00:00 2001 From: Jano Paetzold Date: Sat, 29 Nov 2025 00:11:16 +0100 Subject: [PATCH 2/2] [TwigComponent] Fix #3195 This fixes the issue that when relying on the classMap, even components with classes would be loaded as anonymous components, because loading an anonymous template was tried before even checking the classMap. This fixes the issue by resolving the class map right at the beginning, if possible. This also allows us to reuse the code that loads the metadata: As we make sure `$name` holds the real config key right at the beginning, we don't have to care anymore about whether the caller provided the internal config key or a class name. The special case for class names can go. As a side effect, we get a much better error message when attempting to load using an FQCN, because of the code reuse. --- src/TwigComponent/src/ComponentFactory.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/TwigComponent/src/ComponentFactory.php b/src/TwigComponent/src/ComponentFactory.php index 37a757ccb7b..d673de45f7d 100644 --- a/src/TwigComponent/src/ComponentFactory.php +++ b/src/TwigComponent/src/ComponentFactory.php @@ -46,6 +46,8 @@ public function __construct( public function metadataFor(string $name): ComponentMetadata { + $name = $this->classMap[$name] ?? $name; + if ($config = $this->config[$name] ?? null) { return new ComponentMetadata($config); } @@ -59,14 +61,6 @@ public function metadataFor(string $name): ComponentMetadata return new ComponentMetadata($this->config[$name]); } - if ($mappedName = $this->classMap[$name] ?? null) { - if ($config = $this->config[$mappedName] ?? null) { - return new ComponentMetadata($config); - } - - throw new \InvalidArgumentException(\sprintf('Unknown component "%s".', $name)); - } - $this->throwUnknownComponentException($name); }