Skip to content

Commit

Permalink
Use system cache for ComponentProperties + add CacheWarmer
Browse files Browse the repository at this point in the history
  • Loading branch information
smnandre committed Nov 1, 2024
1 parent 2a3ae63 commit 17ebd17
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 18 deletions.
23 changes: 23 additions & 0 deletions src/TwigComponent/config/cache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\UX\TwigComponent\DependencyInjection\Loader\Configurator;

use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

return static function (ContainerConfigurator $container): void {
$container->services()
->set('cache.ux.twig_component')
->parent('cache.system')
->private()
->tag('cache.pool')
;
};
55 changes: 55 additions & 0 deletions src/TwigComponent/src/CacheWarmer/TwigComponentCacheWarmer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\UX\TwigComponent\CacheWarmer;

use Psr\Container\ContainerInterface;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Symfony\UX\TwigComponent\ComponentProperties;

/**
* Warm the TwigComponent metadata caches.
*
* @author Simon André <[email protected]>
*
* @internal
*/
final class TwigComponentCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterface
{
/**
* As this cache warmer is optional, dependencies should be lazy-loaded, that's why a container should be injected.
*/
public function __construct(
private readonly ContainerInterface $container,
) {
}

public static function getSubscribedServices(): array
{
return [
'ux.twig_component.component_properties' => ComponentProperties::class,
];
}

public function warmUp(string $cacheDir, ?string $buildDir = null): array
{
$properties = $this->container->get('ux.twig_component.component_properties');
$properties->warmup();

return [];
}

public function isOptional(): bool
{
return true;
}
}
41 changes: 34 additions & 7 deletions src/TwigComponent/src/ComponentProperties.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Symfony\UX\TwigComponent;

use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\UX\TwigComponent\Attribute\ExposeInTemplate;

Expand All @@ -21,11 +22,24 @@
*/
final class ComponentProperties
{
private array $classMetadata = [];
private const CACHE_KEY = 'ux.twig_component.component_properties';

/**
* @var array<class-string, array{
* properties: array<class-string, array{string, array{string, string, bool}, bool}>,
* methods: array<class-string, array{string, array{string, bool}}>,
* }|null>
*/
private array $classMetadata;

public function __construct(
private readonly PropertyAccessorInterface $propertyAccessor,
?array $classMetadata = [],
private readonly ?AdapterInterface $cache = null,
) {
$cacheItem = $this->cache?->getItem(self::CACHE_KEY);

$this->classMetadata = $cacheItem?->isHit() ? [...$cacheItem->get(), ...$classMetadata] : $classMetadata;
}

/**
Expand All @@ -36,7 +50,25 @@ public function getProperties(object $component, bool $publicProps = false): arr
return iterator_to_array($this->extractProperties($component, $publicProps));
}

private function extractProperties(object $component, bool $publicProps): iterable
public function warmup(): void
{
if (!$this->cache) {
return;
}

foreach ($this->classMetadata as $class => $metadata) {
if (null === $metadata) {
$this->classMetadata[$class] = $this->loadClassMetadata($class);
}
}

$this->cache->save($this->cache->getItem(self::CACHE_KEY)->set($this->classMetadata));
}

/**
* @return \Generator<string, mixed>
*/
private function extractProperties(object $component, bool $publicProps): \Generator
{
yield from $publicProps ? get_object_vars($component) : [];

Expand Down Expand Up @@ -85,12 +117,10 @@ private function loadClassMetadata(string $class): array
continue;
}
$attribute = $attributes[0]->newInstance();

$properties[$property->name] = [
'name' => $attribute->name ?? $property->name,
'getter' => $attribute->getter ? rtrim($attribute->getter, '()') : null,
];

if ($attribute->destruct) {
unset($properties[$property->name]['name']);
$properties[$property->name]['destruct'] = true;
Expand All @@ -105,11 +135,8 @@ private function loadClassMetadata(string $class): array
if ($method->getNumberOfRequiredParameters()) {
throw new \LogicException(\sprintf('Cannot use "%s" on methods with required parameters (%s::%s).', ExposeInTemplate::class, $class, $method->name));
}

$attribute = $attributes[0]->newInstance();

$name = $attribute->name ?? (str_starts_with($method->name, 'get') ? lcfirst(substr($method->name, 3)) : $method->name);

$methods[$method->name] = $attribute->destruct ? ['destruct' => true] : ['name' => $name];
}

Expand Down
7 changes: 1 addition & 6 deletions src/TwigComponent/src/ComponentRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

namespace Symfony\UX\TwigComponent;

use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\UX\TwigComponent\Event\PostRenderEvent;
use Symfony\UX\TwigComponent\Event\PreCreateForRenderEvent;
Expand All @@ -25,17 +24,13 @@
*/
final class ComponentRenderer implements ComponentRendererInterface
{
// TODO update DI
private readonly ComponentProperties $componentProperties;

public function __construct(
private Environment $twig,
private EventDispatcherInterface $dispatcher,
private ComponentFactory $factory,
PropertyAccessorInterface $propertyAccessor,
private ComponentProperties $componentProperties,
private ComponentStack $componentStack,
) {
$this->componentProperties = new ComponentProperties($propertyAccessor);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ public function process(ContainerBuilder $container): void
$factoryDefinition->setArgument(4, $componentConfig);
$factoryDefinition->setArgument(5, $componentClassMap);

$componentPropertiesDefinition = $container->findDefinition('ux.twig_component.component_properties');
$componentPropertiesDefinition->setArgument(1, array_fill_keys(array_keys($componentClassMap), null));

$debugCommandDefinition = $container->findDefinition('ux.twig_component.command.debug');
$debugCommandDefinition->setArgument(3, $componentClassMap);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,17 @@
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
use Symfony\UX\TwigComponent\CacheWarmer\TwigComponentCacheWarmer;
use Symfony\UX\TwigComponent\Command\TwigComponentDebugCommand;
use Symfony\UX\TwigComponent\ComponentFactory;
use Symfony\UX\TwigComponent\ComponentProperties;
use Symfony\UX\TwigComponent\ComponentRenderer;
use Symfony\UX\TwigComponent\ComponentRendererInterface;
use Symfony\UX\TwigComponent\ComponentStack;
Expand Down Expand Up @@ -84,21 +87,29 @@ static function (ChildDefinition $definition, AsTwigComponent $attribute) {
$container->register('ux.twig_component.component_factory', ComponentFactory::class)
->setArguments([
new Reference('ux.twig_component.component_template_finder'),
class_exists(AbstractArgument::class) ? new AbstractArgument(\sprintf('Added in %s.', TwigComponentPass::class)) : null,
new AbstractArgument(\sprintf('Added in %s.', TwigComponentPass::class)),
new Reference('property_accessor'),
new Reference('event_dispatcher'),
class_exists(AbstractArgument::class) ? new AbstractArgument(\sprintf('Added in %s.', TwigComponentPass::class)) : [],
new AbstractArgument(\sprintf('Added in %s.', TwigComponentPass::class)),
])
;

$container->register('ux.twig_component.component_stack', ComponentStack::class);

$container->register('ux.twig_component.component_properties', ComponentProperties::class)
->setArguments([
new Reference('property_accessor'),
new AbstractArgument(\sprintf('Added in %s.', TwigComponentPass::class)),
new Reference('cache.ux.twig_component', ContainerInterface::IGNORE_ON_INVALID_REFERENCE),
])
;

$container->register('ux.twig_component.component_renderer', ComponentRenderer::class)
->setArguments([
new Reference('twig'),
new Reference('event_dispatcher'),
new Reference('ux.twig_component.component_factory'),
new Reference('property_accessor'),
new Reference('ux.twig_component.component_properties'),
new Reference('ux.twig_component.component_stack'),
])
;
Expand All @@ -107,7 +118,7 @@ class_exists(AbstractArgument::class) ? new AbstractArgument(\sprintf('Added in
->addTag('twig.extension')
;

$container->register('.ux.twig_component.twig.component_runtime', ComponentRuntime::class)
$container->register('ux.twig_component.twig.component_runtime', ComponentRuntime::class)
->setArguments([
new Reference('ux.twig_component.component_renderer'),
new ServiceLocatorArgument(new TaggedIteratorArgument('ux.twig_component.twig_renderer', indexAttribute: 'key', needsIndexes: true)),
Expand All @@ -126,7 +137,7 @@ class_exists(AbstractArgument::class) ? new AbstractArgument(\sprintf('Added in
new Parameter('twig.default_path'),
new Reference('ux.twig_component.component_factory'),
new Reference('twig'),
class_exists(AbstractArgument::class) ? new AbstractArgument(\sprintf('Added in %s.', TwigComponentPass::class)) : [],
new AbstractArgument(\sprintf('Added in %s.', TwigComponentPass::class)),
$config['anonymous_template_directory'],
])
->addTag('console.command')
Expand All @@ -138,6 +149,14 @@ class_exists(AbstractArgument::class) ? new AbstractArgument(\sprintf('Added in
if ($container->getParameter('kernel.debug') && $config['profiler']) {
$loader->load('debug.php');
}

$loader->load('cache.php');

$container->register('ux.twig_component.cache_warmer', TwigComponentCacheWarmer::class)
->setArguments([new Reference(\Psr\Container\ContainerInterface::class)])
->addTag('kernel.cache_warmer')
->addTag('container.service_subscriber', ['id' => 'ux.twig_component.component_properties'])
;
}

public function getConfigTreeBuilder(): TreeBuilder
Expand Down

0 comments on commit 17ebd17

Please sign in to comment.