Skip to content

Commit 0880687

Browse files
committed
Use system cache for ComponentProperties + add CacheWarmer
1 parent 94f4b20 commit 0880687

File tree

6 files changed

+140
-18
lines changed

6 files changed

+140
-18
lines changed

src/TwigComponent/config/cache.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\UX\TwigComponent\DependencyInjection\Loader\Configurator;
13+
14+
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
15+
16+
return static function (ContainerConfigurator $container): void {
17+
$container->services()
18+
->set('cache.ux.twig_component')
19+
->parent('cache.system')
20+
->private()
21+
->tag('cache.pool')
22+
;
23+
};
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\UX\TwigComponent\CacheWarmer;
13+
14+
use Psr\Container\ContainerInterface;
15+
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
16+
use Symfony\Contracts\Service\ServiceSubscriberInterface;
17+
use Symfony\UX\TwigComponent\ComponentProperties;
18+
19+
/**
20+
* Warm the TwigComponent metadata caches.
21+
*
22+
* @author Simon André <smn.andre@gmail.com>
23+
*
24+
* @internal
25+
*/
26+
final class TwigComponentCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterface
27+
{
28+
/**
29+
* As this cache warmer is optional, dependencies should be lazy-loaded, that's why a container should be injected.
30+
*/
31+
public function __construct(
32+
private readonly ContainerInterface $container,
33+
) {
34+
}
35+
36+
public static function getSubscribedServices(): array
37+
{
38+
return [
39+
'ux.twig_component.component_properties' => ComponentProperties::class,
40+
];
41+
}
42+
43+
public function warmUp(string $cacheDir, ?string $buildDir = null): array
44+
{
45+
$properties = $this->container->get('ux.twig_component.component_properties');
46+
$properties->warmup();
47+
48+
return [];
49+
}
50+
51+
public function isOptional(): bool
52+
{
53+
return true;
54+
}
55+
}

src/TwigComponent/src/ComponentProperties.php

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\UX\TwigComponent;
1313

14+
use Symfony\Component\Cache\Adapter\AdapterInterface;
1415
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
1516
use Symfony\UX\TwigComponent\Attribute\ExposeInTemplate;
1617

@@ -21,11 +22,24 @@
2122
*/
2223
final class ComponentProperties
2324
{
24-
private array $classMetadata = [];
25+
private const CACHE_KEY = 'ux.twig_component.component_properties';
26+
27+
/**
28+
* @var array<class-string, array{
29+
* properties: array<class-string, array{string, array{string, string, bool}, bool}>,
30+
* methods: array<class-string, array{string, array{string, bool}}>,
31+
* }|null>
32+
*/
33+
private array $classMetadata;
2534

2635
public function __construct(
2736
private readonly PropertyAccessorInterface $propertyAccessor,
37+
?array $classMetadata = [],
38+
private readonly ?AdapterInterface $cache = null,
2839
) {
40+
$cacheItem = $this->cache?->getItem(self::CACHE_KEY);
41+
42+
$this->classMetadata = $cacheItem?->isHit() ? [...$cacheItem->get(), ...$classMetadata] : $classMetadata;
2943
}
3044

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

39-
private function extractProperties(object $component, bool $publicProps): iterable
53+
public function warmup(): void
54+
{
55+
if (!$this->cache) {
56+
return;
57+
}
58+
59+
foreach ($this->classMetadata as $class => $metadata) {
60+
if (null === $metadata) {
61+
$this->classMetadata[$class] = $this->loadClassMetadata($class);
62+
}
63+
}
64+
65+
$this->cache->save($this->cache->getItem(self::CACHE_KEY)->set($this->classMetadata));
66+
}
67+
68+
/**
69+
* @return \Generator<string, mixed>
70+
*/
71+
private function extractProperties(object $component, bool $publicProps): \Generator
4072
{
4173
yield from $publicProps ? get_object_vars($component) : [];
4274

@@ -85,12 +117,10 @@ private function loadClassMetadata(string $class): array
85117
continue;
86118
}
87119
$attribute = $attributes[0]->newInstance();
88-
89120
$properties[$property->name] = [
90121
'name' => $attribute->name ?? $property->name,
91122
'getter' => $attribute->getter ? rtrim($attribute->getter, '()') : null,
92123
];
93-
94124
if ($attribute->destruct) {
95125
unset($properties[$property->name]['name']);
96126
$properties[$property->name]['destruct'] = true;
@@ -105,11 +135,8 @@ private function loadClassMetadata(string $class): array
105135
if ($method->getNumberOfRequiredParameters()) {
106136
throw new \LogicException(\sprintf('Cannot use "%s" on methods with required parameters (%s::%s).', ExposeInTemplate::class, $class, $method->name));
107137
}
108-
109138
$attribute = $attributes[0]->newInstance();
110-
111139
$name = $attribute->name ?? (str_starts_with($method->name, 'get') ? lcfirst(substr($method->name, 3)) : $method->name);
112-
113140
$methods[$method->name] = $attribute->destruct ? ['destruct' => true] : ['name' => $name];
114141
}
115142

src/TwigComponent/src/ComponentRenderer.php

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
namespace Symfony\UX\TwigComponent;
1313

14-
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
1514
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
1615
use Symfony\UX\TwigComponent\Event\PostRenderEvent;
1716
use Symfony\UX\TwigComponent\Event\PreCreateForRenderEvent;
@@ -25,17 +24,13 @@
2524
*/
2625
final class ComponentRenderer implements ComponentRendererInterface
2726
{
28-
// TODO update DI
29-
private readonly ComponentProperties $componentProperties;
30-
3127
public function __construct(
3228
private Environment $twig,
3329
private EventDispatcherInterface $dispatcher,
3430
private ComponentFactory $factory,
35-
PropertyAccessorInterface $propertyAccessor,
31+
private ComponentProperties $componentProperties,
3632
private ComponentStack $componentStack,
3733
) {
38-
$this->componentProperties = new ComponentProperties($propertyAccessor);
3934
}
4035

4136
/**

src/TwigComponent/src/DependencyInjection/Compiler/TwigComponentPass.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ public function process(ContainerBuilder $container): void
8080
$factoryDefinition->setArgument(4, $componentConfig);
8181
$factoryDefinition->setArgument(5, $componentClassMap);
8282

83+
$componentPropertiesDefinition = $container->findDefinition('ux.twig_component.component_properties');
84+
$componentPropertiesDefinition->setArgument(1, array_fill_keys(array_keys($componentClassMap), null));
85+
8386
$debugCommandDefinition = $container->findDefinition('ux.twig_component.command.debug');
8487
$debugCommandDefinition->setArgument(3, $componentClassMap);
8588
}

src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,17 @@
2121
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
2222
use Symfony\Component\DependencyInjection\ChildDefinition;
2323
use Symfony\Component\DependencyInjection\ContainerBuilder;
24+
use Symfony\Component\DependencyInjection\ContainerInterface;
2425
use Symfony\Component\DependencyInjection\Exception\LogicException;
2526
use Symfony\Component\DependencyInjection\Extension\Extension;
2627
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
2728
use Symfony\Component\DependencyInjection\Parameter;
2829
use Symfony\Component\DependencyInjection\Reference;
2930
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
31+
use Symfony\UX\TwigComponent\CacheWarmer\TwigComponentCacheWarmer;
3032
use Symfony\UX\TwigComponent\Command\TwigComponentDebugCommand;
3133
use Symfony\UX\TwigComponent\ComponentFactory;
34+
use Symfony\UX\TwigComponent\ComponentProperties;
3235
use Symfony\UX\TwigComponent\ComponentRenderer;
3336
use Symfony\UX\TwigComponent\ComponentRendererInterface;
3437
use Symfony\UX\TwigComponent\ComponentStack;
@@ -84,21 +87,29 @@ static function (ChildDefinition $definition, AsTwigComponent $attribute) {
8487
$container->register('ux.twig_component.component_factory', ComponentFactory::class)
8588
->setArguments([
8689
new Reference('ux.twig_component.component_template_finder'),
87-
class_exists(AbstractArgument::class) ? new AbstractArgument(\sprintf('Added in %s.', TwigComponentPass::class)) : null,
90+
new AbstractArgument(\sprintf('Added in %s.', TwigComponentPass::class)),
8891
new Reference('property_accessor'),
8992
new Reference('event_dispatcher'),
90-
class_exists(AbstractArgument::class) ? new AbstractArgument(\sprintf('Added in %s.', TwigComponentPass::class)) : [],
93+
new AbstractArgument(\sprintf('Added in %s.', TwigComponentPass::class)),
9194
])
9295
;
9396

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

99+
$container->register('ux.twig_component.component_properties', ComponentProperties::class)
100+
->setArguments([
101+
new Reference('property_accessor'),
102+
new AbstractArgument(\sprintf('Added in %s.', TwigComponentPass::class)),
103+
new Reference('cache.ux.twig_component', ContainerInterface::IGNORE_ON_INVALID_REFERENCE),
104+
])
105+
;
106+
96107
$container->register('ux.twig_component.component_renderer', ComponentRenderer::class)
97108
->setArguments([
98109
new Reference('twig'),
99110
new Reference('event_dispatcher'),
100111
new Reference('ux.twig_component.component_factory'),
101-
new Reference('property_accessor'),
112+
new Reference('ux.twig_component.component_properties'),
102113
new Reference('ux.twig_component.component_stack'),
103114
])
104115
;
@@ -107,7 +118,7 @@ class_exists(AbstractArgument::class) ? new AbstractArgument(\sprintf('Added in
107118
->addTag('twig.extension')
108119
;
109120

110-
$container->register('.ux.twig_component.twig.component_runtime', ComponentRuntime::class)
121+
$container->register('ux.twig_component.twig.component_runtime', ComponentRuntime::class)
111122
->setArguments([
112123
new Reference('ux.twig_component.component_renderer'),
113124
new ServiceLocatorArgument(new TaggedIteratorArgument('ux.twig_component.twig_renderer', indexAttribute: 'key', needsIndexes: true)),
@@ -126,7 +137,7 @@ class_exists(AbstractArgument::class) ? new AbstractArgument(\sprintf('Added in
126137
new Parameter('twig.default_path'),
127138
new Reference('ux.twig_component.component_factory'),
128139
new Reference('twig'),
129-
class_exists(AbstractArgument::class) ? new AbstractArgument(\sprintf('Added in %s.', TwigComponentPass::class)) : [],
140+
new AbstractArgument(\sprintf('Added in %s.', TwigComponentPass::class)),
130141
$config['anonymous_template_directory'],
131142
])
132143
->addTag('console.command')
@@ -138,6 +149,14 @@ class_exists(AbstractArgument::class) ? new AbstractArgument(\sprintf('Added in
138149
if ($container->getParameter('kernel.debug') && $config['profiler']) {
139150
$loader->load('debug.php');
140151
}
152+
153+
$loader->load('cache.php');
154+
155+
$container->register('ux.twig_component.cache_warmer', TwigComponentCacheWarmer::class)
156+
->setArguments([new Reference(\Psr\Container\ContainerInterface::class)])
157+
->addTag('kernel.cache_warmer')
158+
->addTag('container.service_subscriber', ['id' => 'ux.twig_component.component_properties'])
159+
;
141160
}
142161

143162
public function getConfigTreeBuilder(): TreeBuilder

0 commit comments

Comments
 (0)