From 8623c616cfe160155ffb6ca736b23c1515ab7889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Andr=C3=A9?= Date: Mon, 11 Nov 2024 02:01:41 +0100 Subject: [PATCH] [Twig] Cache template class resolution --- src/TwigComponent/src/ComponentFactory.php | 54 +++++++++---------- src/TwigComponent/src/ComponentRenderer.php | 18 +++++-- .../TwigComponentExtension.php | 1 + 3 files changed, 38 insertions(+), 35 deletions(-) diff --git a/src/TwigComponent/src/ComponentFactory.php b/src/TwigComponent/src/ComponentFactory.php index 8699fb10a3..664fb5b55f 100644 --- a/src/TwigComponent/src/ComponentFactory.php +++ b/src/TwigComponent/src/ComponentFactory.php @@ -25,12 +25,11 @@ */ final class ComponentFactory implements ResetInterface { - private static array $mountMethods = []; + private array $mountMethods = []; /** * @param array $config * @param array $classMap - * @param array $classMounts */ public function __construct( private ComponentTemplateFinderInterface $componentTemplateFinder, @@ -38,7 +37,7 @@ public function __construct( private PropertyAccessorInterface $propertyAccessor, private EventDispatcherInterface $eventDispatcher, private array $config, - private array $classMap, + private readonly array $classMap, ) { } @@ -88,22 +87,23 @@ public function create(string $name, array $data = []): MountedComponent public function mountFromObject(object $component, array $data, ComponentMetadata $componentMetadata): MountedComponent { $originalData = $data; - $data = $this->preMount($component, $data, $componentMetadata); + $event = $this->preMount($component, $data, $componentMetadata); + $data = $event->getData(); $this->mount($component, $data, $componentMetadata); - // set data that wasn't set in mount on the component directly - foreach ($data as $property => $value) { - if ($this->propertyAccessor->isWritable($component, $property)) { - $this->propertyAccessor->setValue($component, $property, $value); - - unset($data[$property]); + if (!$componentMetadata->isAnonymous()) { + // set data that wasn't set in mount on the component directly + foreach ($data as $property => $value) { + if ($this->propertyAccessor->isWritable($component, $property)) { + $this->propertyAccessor->setValue($component, $property, $value); + unset($data[$property]); + } } } $postMount = $this->postMount($component, $data, $componentMetadata); - $data = $postMount['data']; - $extraMetadata = $postMount['extraMetadata']; + $data = $postMount->getData(); // create attributes from "attributes" key if exists $attributesVar = $componentMetadata->getAttributesVar(); @@ -120,9 +120,9 @@ public function mountFromObject(object $component, array $data, ComponentMetadat return new MountedComponent( $componentMetadata->getName(), $component, - new ComponentAttributes(array_merge($attributes, $data)), + new ComponentAttributes([...$attributes, ...$data]), $originalData, - $extraMetadata, + $postMount->getExtraMetadata(), ); } @@ -154,7 +154,7 @@ private function mount(object $component, array &$data, ComponentMetadata $compo return; } - $mount = self::$mountMethods[$component::class] ??= (new \ReflectionClass($component))->getMethod('mount'); + $mount = $this->mountMethods[$component::class] ??= (new \ReflectionClass($component))->getMethod('mount'); $parameters = []; foreach ($mount->getParameters() as $refParameter) { @@ -172,40 +172,34 @@ private function mount(object $component, array &$data, ComponentMetadata $compo $mount->invoke($component, ...$parameters); } - private function preMount(object $component, array $data, ComponentMetadata $componentMetadata): array + private function preMount(object $component, array $data, ComponentMetadata $componentMetadata): PreMountEvent { $event = new PreMountEvent($component, $data, $componentMetadata); $this->eventDispatcher->dispatch($event); - $data = $event->getData(); + $data = $event->getData(); foreach ($componentMetadata->getPreMounts() as $preMount) { if (null !== $newData = $component->$preMount($data)) { - $data = $newData; + $event->setData($data = $newData); } } - return $data; + return $event; } - /** - * @return array{data: array, extraMetadata: array} - */ - private function postMount(object $component, array $data, ComponentMetadata $componentMetadata): array + private function postMount(object $component, array $data, ComponentMetadata $componentMetadata): PostMountEvent { $event = new PostMountEvent($component, $data, $componentMetadata); $this->eventDispatcher->dispatch($event); - $data = $event->getData(); + $data = $event->getData(); foreach ($componentMetadata->getPostMounts() as $postMount) { if (null !== $newData = $component->$postMount($data)) { - $data = $newData; + $event->setData($data = $newData); } } - return [ - 'data' => $data, - 'extraMetadata' => $event->getExtraMetadata(), - ]; + return $event; } /** @@ -244,6 +238,6 @@ private function throwUnknownComponentException(string $name): void public function reset(): void { - self::$mountMethods = []; + $this->mountMethods = []; } } diff --git a/src/TwigComponent/src/ComponentRenderer.php b/src/TwigComponent/src/ComponentRenderer.php index fb6a01ffda..2595d646bb 100644 --- a/src/TwigComponent/src/ComponentRenderer.php +++ b/src/TwigComponent/src/ComponentRenderer.php @@ -12,6 +12,7 @@ namespace Symfony\UX\TwigComponent; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\Service\ResetInterface; use Symfony\UX\TwigComponent\Event\PostRenderEvent; use Symfony\UX\TwigComponent\Event\PreCreateForRenderEvent; use Symfony\UX\TwigComponent\Event\PreRenderEvent; @@ -22,8 +23,10 @@ * * @internal */ -final class ComponentRenderer implements ComponentRendererInterface +final class ComponentRenderer implements ComponentRendererInterface, ResetInterface { + private array $templateClasses = []; + public function __construct( private Environment $twig, private EventDispatcherInterface $dispatcher, @@ -62,15 +65,15 @@ public function render(MountedComponent $mounted): string $variables = $event->getVariables(); // see ComponentNode. When rendering an individual embedded component, // *not* through its parent, we need to set the parent template. - if ($event->getTemplateIndex()) { + if ($templateIndex = $event->getTemplateIndex()) { $variables['__parent__'] = $event->getParentTemplateForEmbedded(); } try { return $this->twig->loadTemplate( - $this->twig->getTemplateClass($event->getTemplate()), - $event->getTemplate(), - $event->getTemplateIndex(), + $this->templateClasses[$template = $event->getTemplate()] ??= $this->twig->getTemplateClass($template), + $template, + $templateIndex, )->render($variables); } finally { $mounted = $this->componentStack->pop(); @@ -137,4 +140,9 @@ private function preRender(MountedComponent $mounted, array $context = []): PreR return $event; } + + public function reset(): void + { + $this->templateClasses = []; + } } diff --git a/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php b/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php index 19432f6305..ebe2bb1753 100644 --- a/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php +++ b/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php @@ -113,6 +113,7 @@ static function (ChildDefinition $definition, AsTwigComponent $attribute) { new Reference('ux.twig_component.component_properties'), new Reference('ux.twig_component.component_stack'), ]) + ->addTag('kernel.reset', ['method' => 'reset']) ; $container->register('ux.twig_component.twig.component_extension', ComponentExtension::class)