From da0537d96182b40f60ac9d1dd4b961fa3c605011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Andr=C3=A9?= Date: Tue, 24 Sep 2024 01:45:55 +0200 Subject: [PATCH] [TwigComponent] Optimize ComponentFactory --- src/TwigComponent/src/ComponentFactory.php | 73 +++++++-------- src/TwigComponent/src/ComponentMetadata.php | 5 + .../tests/Unit/ComponentFactoryTest.php | 92 +++++++++++++++++++ 3 files changed, 131 insertions(+), 39 deletions(-) create mode 100644 src/TwigComponent/tests/Unit/ComponentFactoryTest.php diff --git a/src/TwigComponent/src/ComponentFactory.php b/src/TwigComponent/src/ComponentFactory.php index 2a677d38803..d797c399add 100644 --- a/src/TwigComponent/src/ComponentFactory.php +++ b/src/TwigComponent/src/ComponentFactory.php @@ -41,20 +41,28 @@ public function __construct( public function metadataFor(string $name): ComponentMetadata { - $name = $this->classMap[$name] ?? $name; - - if (!$config = $this->config[$name] ?? null) { - if (($template = $this->componentTemplateFinder->findAnonymousComponentTemplate($name)) !== null) { - return new ComponentMetadata([ - 'key' => $name, - 'template' => $template, - ]); + if ($config = $this->config[$name] ?? null) { + return new ComponentMetadata($config); + } + + if ($template = $this->componentTemplateFinder->findAnonymousComponentTemplate($name)) { + $this->config[$name] = [ + 'key' => $name, + 'template' => $template, + ]; + + return new ComponentMetadata($this->config[$name]); + } + + if ($mappedName = $this->classMap[$name] ?? null) { + if ($config = $this->config[$mappedName] ?? null) { + return new ComponentMetadata($config); } - $this->throwUnknownComponentException($name); + throw new \InvalidArgumentException(\sprintf('Unknown component "%s".', $name)); } - return new ComponentMetadata($config); + $this->throwUnknownComponentException($name); } /** @@ -62,11 +70,13 @@ public function metadataFor(string $name): ComponentMetadata */ public function create(string $name, array $data = []): MountedComponent { - return $this->mountFromObject( - $this->getComponent($name), - $data, - $this->metadataFor($name) - ); + $metadata = $this->metadataFor($name); + + if ($metadata->isAnonymous()) { + return $this->mountFromObject(new AnonymousComponent(), $data, $metadata); + } + + return $this->mountFromObject($this->components->get($metadata->getName()), $data, $metadata); } /** @@ -101,10 +111,7 @@ public function mountFromObject(object $component, array $data, ComponentMetadat foreach ($data as $key => $value) { if ($value instanceof \Stringable) { $data[$key] = (string) $value; - continue; } - - $data[$key] = $value; } return new MountedComponent( @@ -118,10 +125,18 @@ public function mountFromObject(object $component, array $data, ComponentMetadat /** * Returns the "unmounted" component. + * + * @internal */ public function get(string $name): object { - return $this->getComponent($name); + $metadata = $this->metadataFor($name); + + if ($metadata->isAnonymous()) { + return new AnonymousComponent(); + } + + return $this->components->get($metadata->getName()); } private function mount(object $component, array &$data): void @@ -159,21 +174,6 @@ private function mount(object $component, array &$data): void $component->mount(...$parameters); } - private function getComponent(string $name): object - { - $name = $this->classMap[$name] ?? $name; - - if (!$this->components->has($name)) { - if ($this->isAnonymousComponent($name)) { - return new AnonymousComponent(); - } - - $this->throwUnknownComponentException($name); - } - - return $this->components->get($name); - } - private function preMount(object $component, array $data, ComponentMetadata $componentMetadata): array { $event = new PreMountEvent($component, $data, $componentMetadata); @@ -215,11 +215,6 @@ private function postMount(object $component, array $data, ComponentMetadata $co ]; } - private function isAnonymousComponent(string $name): bool - { - return null !== $this->componentTemplateFinder->findAnonymousComponentTemplate($name); - } - /** * @return never */ diff --git a/src/TwigComponent/src/ComponentMetadata.php b/src/TwigComponent/src/ComponentMetadata.php index a152417a8e7..b71e349161c 100644 --- a/src/TwigComponent/src/ComponentMetadata.php +++ b/src/TwigComponent/src/ComponentMetadata.php @@ -57,6 +57,11 @@ public function isPublicPropsExposed(): bool return $this->get('expose_public_props', false); } + public function isAnonymous(): bool + { + return !isset($this->config['service_id']); + } + public function getAttributesVar(): string { return $this->get('attributes_var', 'attributes'); diff --git a/src/TwigComponent/tests/Unit/ComponentFactoryTest.php b/src/TwigComponent/tests/Unit/ComponentFactoryTest.php new file mode 100644 index 00000000000..fd1fd337c89 --- /dev/null +++ b/src/TwigComponent/tests/Unit/ComponentFactoryTest.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\TwigComponent\Tests\Unit; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\UX\TwigComponent\ComponentFactory; +use Symfony\UX\TwigComponent\ComponentTemplateFinderInterface; + +/** + * @author Simon André + */ +class ComponentFactoryTest extends TestCase +{ + public function testMetadataForConfig(): void + { + $factory = new ComponentFactory( + $this->createMock(ComponentTemplateFinderInterface::class), + $this->createMock(ServiceLocator::class), + $this->createMock(PropertyAccessorInterface::class), + $this->createMock(EventDispatcherInterface::class), + ['foo' => ['key' => 'foo', 'template' => 'bar.html.twig']], + [] + ); + + $metadata = $factory->metadataFor('foo'); + + $this->assertSame('foo', $metadata->getName()); + $this->assertSame('bar.html.twig', $metadata->getTemplate()); + } + + public function testMetadataForResolveAlias(): void + { + $factory = new ComponentFactory( + $this->createMock(ComponentTemplateFinderInterface::class), + $this->createMock(ServiceLocator::class), + $this->createMock(PropertyAccessorInterface::class), + $this->createMock(EventDispatcherInterface::class), + [ + 'bar' => ['key' => 'bar', 'template' => 'bar.html.twig'], + 'foo' => ['key' => 'foo', 'template' => 'foo.html.twig'], + ], + ['Foo\\Bar' => 'bar'], + ); + + $metadata = $factory->metadataFor('Foo\\Bar'); + + $this->assertSame('bar', $metadata->getName()); + $this->assertSame('bar.html.twig', $metadata->getTemplate()); + } + + public function testMetadataForReuseAnonymousConfig(): void + { + $templateFinder = $this->createMock(ComponentTemplateFinderInterface::class); + $templateFinder->expects($this->atLeastOnce()) + ->method('findAnonymousComponentTemplate') + ->with('foo') + ->willReturnOnConsecutiveCalls('foo.html.twig', 'bar.html.twig', 'bar.html.twig'); + + $factory = new ComponentFactory( + $templateFinder, + $this->createMock(ServiceLocator::class), + $this->createMock(PropertyAccessorInterface::class), + $this->createMock(EventDispatcherInterface::class), + [], + [] + ); + + $metadata = $factory->metadataFor('foo'); + $this->assertSame('foo', $metadata->getName()); + $this->assertSame('foo.html.twig', $metadata->getTemplate()); + + $metadata = $factory->metadataFor('foo'); + $this->assertSame('foo', $metadata->getName()); + $this->assertSame('foo.html.twig', $metadata->getTemplate()); + + $metadata = $factory->metadataFor('foo'); + $this->assertSame('foo', $metadata->getName()); + $this->assertSame('foo.html.twig', $metadata->getTemplate()); + } +}