diff --git a/src/TwigComponent/src/ComponentFactory.php b/src/TwigComponent/src/ComponentFactory.php index d797c399add..c191bc1bb4a 100644 --- a/src/TwigComponent/src/ComponentFactory.php +++ b/src/TwigComponent/src/ComponentFactory.php @@ -14,6 +14,7 @@ use Psr\EventDispatcher\EventDispatcherInterface; use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; +use Symfony\Contracts\Service\ResetInterface; use Symfony\UX\TwigComponent\Attribute\AsTwigComponent; use Symfony\UX\TwigComponent\Event\PostMountEvent; use Symfony\UX\TwigComponent\Event\PreMountEvent; @@ -23,8 +24,12 @@ * * @internal */ -final class ComponentFactory +final class ComponentFactory implements ResetInterface { + private static $mountMethods = []; + private static $preMountMethods = []; + private static $postMountMethods = []; + /** * @param array $config * @param array $classMap @@ -141,37 +146,40 @@ public function get(string $name): object private function mount(object $component, array &$data): void { - try { - $method = (new \ReflectionClass($component))->getMethod('mount'); - } catch (\ReflectionException) { - // no hydrate method - return; - } - if ($component instanceof AnonymousComponent) { $component->mount($data); return; } - $parameters = []; + if (null === (self::$mountMethods[$component::class] ?? null)) { + try { + $mountMethod = self::$mountMethods[$component::class] = (new \ReflectionClass($component))->getMethod('mount'); + } catch (\ReflectionException) { + self::$mountMethods[$component::class] = false; - foreach ($method->getParameters() as $refParameter) { - $name = $refParameter->getName(); + return; + } + } - if (\array_key_exists($name, $data)) { - $parameters[] = $data[$name]; + if (false === $mountMethod ??= self::$mountMethods[$component::class]) { + return; + } + $parameters = []; + foreach ($mountMethod->getParameters() as $refParameter) { + if (\array_key_exists($name = $refParameter->getName(), $data)) { + $parameters[] = $data[$name]; // remove the data element so it isn't used to set the property directly. unset($data[$name]); } elseif ($refParameter->isDefaultValueAvailable()) { $parameters[] = $refParameter->getDefaultValue(); } else { - throw new \LogicException(\sprintf('%s::mount() has a required $%s parameter. Make sure this is passed or make give a default value.', $component::class, $refParameter->getName())); + throw new \LogicException(\sprintf('%s::mount() has a required $%s parameter. Make sure to pass it or give it a default value.', $component::class, $name)); } } - $component->mount(...$parameters); + $mountMethod->invoke($component, ...$parameters); } private function preMount(object $component, array $data, ComponentMetadata $componentMetadata): array @@ -180,10 +188,9 @@ private function preMount(object $component, array $data, ComponentMetadata $com $this->eventDispatcher->dispatch($event); $data = $event->getData(); - foreach (AsTwigComponent::preMountMethods($component) as $method) { - $newData = $component->{$method->name}($data); - - if (null !== $newData) { + $methods = self::$preMountMethods[$component::class] ??= AsTwigComponent::preMountMethods($component::class); + foreach ($methods as $method) { + if (null !== $newData = $method->invoke($component, $data)) { $data = $newData; } } @@ -199,19 +206,17 @@ private function postMount(object $component, array $data, ComponentMetadata $co $event = new PostMountEvent($component, $data, $componentMetadata); $this->eventDispatcher->dispatch($event); $data = $event->getData(); - $extraMetadata = $event->getExtraMetadata(); - foreach (AsTwigComponent::postMountMethods($component) as $method) { - $newData = $component->{$method->name}($data); - - if (null !== $newData) { + $methods = self::$postMountMethods[$component::class] ??= AsTwigComponent::postMountMethods($component::class); + foreach ($methods as $method) { + if (null !== $newData = $method->invoke($component, $data)) { $data = $newData; } } return [ 'data' => $data, - 'extraMetadata' => $extraMetadata, + 'extraMetadata' => $event->getExtraMetadata(), ]; } @@ -248,4 +253,11 @@ private function throwUnknownComponentException(string $name): void throw new \InvalidArgumentException($message); } + + public function reset(): void + { + self::$mountMethods = []; + self::$preMountMethods = []; + self::$postMountMethods = []; + } } diff --git a/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php b/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php index 0ee7230600a..19432f6305a 100644 --- a/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php +++ b/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php @@ -92,6 +92,7 @@ static function (ChildDefinition $definition, AsTwigComponent $attribute) { new Reference('event_dispatcher'), new AbstractArgument(\sprintf('Added in %s.', TwigComponentPass::class)), ]) + ->addTag('kernel.reset', ['method' => 'reset']) ; $container->register('ux.twig_component.component_stack', ComponentStack::class); diff --git a/src/TwigComponent/tests/Integration/ComponentFactoryTest.php b/src/TwigComponent/tests/Integration/ComponentFactoryTest.php index 72064f47669..bf5dd88675d 100644 --- a/src/TwigComponent/tests/Integration/ComponentFactoryTest.php +++ b/src/TwigComponent/tests/Integration/ComponentFactoryTest.php @@ -87,7 +87,7 @@ public function testMountCanHaveOptionalParameters(): void public function testExceptionThrownIfRequiredMountParameterIsMissingFromPassedData(): void { $this->expectException(\LogicException::class); - $this->expectExceptionMessage('Symfony\UX\TwigComponent\Tests\Fixtures\Component\ComponentC::mount() has a required $propA parameter. Make sure this is passed or make give a default value.'); + $this->expectExceptionMessage('Symfony\UX\TwigComponent\Tests\Fixtures\Component\ComponentC::mount() has a required $propA parameter. Make sure to pass it or give it a default value.'); $this->createComponent('component_c'); }