Skip to content

Commit fb68b3c

Browse files
committed
[TwigBundle] Use kernel.build_dir to store the templates known at build time
Signed-off-by: Quentin Devos <[email protected]>
1 parent b5ee977 commit fb68b3c

File tree

12 files changed

+163
-12
lines changed

12 files changed

+163
-12
lines changed

src/Symfony/Bundle/TwigBundle/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ CHANGELOG
55
---
66

77
* Mark class `TemplateCacheWarmer` as `final`
8+
* Create `ChainCache` to store warmed-up cache in `kernel.build_dir` and runtime cache in `cache_dir`
9+
* Make `TemplateCacheWarmer` use `kernel.build_dir` instead of `cache_dir`
810

911
7.0
1012
---
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Bundle\TwigBundle\Cache;
13+
14+
use Twig\Cache\CacheInterface;
15+
16+
/**
17+
* Chains several caches together.
18+
*
19+
* Cached items are fetched from the first cache having them in its data store.
20+
* They are saved and deleted in all adapters at once.
21+
*/
22+
class ChainCache implements CacheInterface
23+
{
24+
/**
25+
* @param CacheInterface[] $caches The ordered list of caches used to store and fetch cached items
26+
*/
27+
public function __construct(private array $caches = [])
28+
{
29+
if (!$caches) {
30+
throw new \InvalidArgumentException('At least one cache must be specified.');
31+
}
32+
33+
foreach ($caches as $cache) {
34+
if (!$cache instanceof CacheInterface) {
35+
throw new \InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', get_debug_type($cache), CacheInterface::class));
36+
}
37+
}
38+
}
39+
40+
public function generateKey(string $name, string $className): string
41+
{
42+
return $name.'#'.$className;
43+
}
44+
45+
public function write(string $key, string $content): void
46+
{
47+
foreach ($this->caches as $cache) {
48+
[$name, $className] = explode('#', $key, 2);
49+
50+
$cacheKey = $cache->generateKey($name, $className);
51+
$cache->write($cacheKey, $content);
52+
}
53+
}
54+
55+
public function load(string $key): void
56+
{
57+
foreach ($this->caches as $cache) {
58+
[$name, $className] = explode('#', $key, 2);
59+
60+
$cacheKey = $cache->generateKey($name, $className);
61+
$cache->load($cacheKey);
62+
63+
if (class_exists($className, false)) {
64+
break;
65+
}
66+
}
67+
}
68+
69+
public function getTimestamp(string $key): int
70+
{
71+
foreach ($this->caches as $cache) {
72+
[$name, $className] = explode('#', $key, 2);
73+
74+
$cacheKey = $cache->generateKey($name, $className);
75+
$timestamp = $cache->getTimestamp($cacheKey);
76+
if ($timestamp > 0) {
77+
return $timestamp;
78+
}
79+
}
80+
81+
return 0;
82+
}
83+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Bundle\TwigBundle\Cache;
13+
14+
use Twig\Cache\FilesystemCache;
15+
16+
/**
17+
* Implements a cache on the filesystem that can only be read, not written to.
18+
*/
19+
class ReadOnlyFilesystemCache extends FilesystemCache
20+
{
21+
public function write(string $key, string $content): void
22+
{
23+
// Do nothing with the content, it's a read-only filesystem.
24+
}
25+
}

src/Symfony/Bundle/TwigBundle/CacheWarmer/TemplateCacheWarmer.php

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,36 @@
2626
*/
2727
class TemplateCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterface
2828
{
29-
private ContainerInterface $container;
3029
private Environment $twig;
31-
private iterable $iterator;
30+
private string $cacheFolder;
3231

33-
public function __construct(ContainerInterface $container, iterable $iterator)
32+
/**
33+
* @param string $cacheFolder
34+
*/
35+
public function __construct(private ContainerInterface $container, private iterable $iterator /* , private string $cacheFolder */)
3436
{
3537
// As this cache warmer is optional, dependencies should be lazy-loaded, that's why a container should be injected.
36-
$this->container = $container;
37-
$this->iterator = $iterator;
38+
if (\func_num_args() < 3) {
39+
trigger_deprecation('symfony/twig-bundle', '7.1', 'The "%s()" method will have a new "string $cacheFolder" argument in version 8.0, not defining it is deprecated.', __METHOD__);
40+
41+
$cacheFolder = 'twig';
42+
} else {
43+
$cacheFolder = func_get_arg(2);
44+
}
45+
$this->cacheFolder = $cacheFolder;
3846
}
3947

4048
public function warmUp(string $cacheDir, ?string $buildDir = null): array
4149
{
50+
if (!$buildDir) {
51+
return [];
52+
}
53+
4254
$this->twig ??= $this->container->get('twig');
4355

56+
$originalCache = $this->twig->getCache();
57+
$this->twig->setCache($buildDir.\DIRECTORY_SEPARATOR.$this->cacheFolder);
58+
4459
foreach ($this->iterator as $template) {
4560
try {
4661
$this->twig->load($template);
@@ -56,6 +71,8 @@ public function warmUp(string $cacheDir, ?string $buildDir = null): array
5671
}
5772
}
5873

74+
$this->twig->setCache($originalCache);
75+
5976
return [];
6077
}
6178

src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,9 @@ private function addTwigOptions(ArrayNodeDefinition $rootNode): void
130130
->scalarNode('autoescape_service')->defaultNull()->end()
131131
->scalarNode('autoescape_service_method')->defaultNull()->end()
132132
->scalarNode('base_template_class')->example('Twig\Template')->cannotBeEmpty()->end()
133-
->scalarNode('cache')->defaultValue('%kernel.cache_dir%/twig')->end()
133+
->scalarNode('cache')
134+
->setDeprecated('symfony/twig-bundle', '7.1', 'Setting the "%path%.%node%" configuration option is deprecated. It will be removed in version 8.0.')
135+
->end()
134136
->scalarNode('charset')->defaultValue('%kernel.charset%')->end()
135137
->booleanNode('debug')->defaultValue('%kernel.debug%')->end()
136138
->booleanNode('strict_variables')->defaultValue('%kernel.debug%')->end()

src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use Symfony\Component\Translation\LocaleSwitcher;
2626
use Symfony\Component\Translation\Translator;
2727
use Symfony\Contracts\Service\ResetInterface;
28+
use Twig\Cache\FilesystemCache;
2829
use Twig\Extension\ExtensionInterface;
2930
use Twig\Extension\RuntimeExtensionInterface;
3031
use Twig\Loader\LoaderInterface;
@@ -153,6 +154,17 @@ public function load(array $configs, ContainerBuilder $container): void
153154
}
154155
}
155156

157+
if (!isset($config['cache'])) {
158+
$config['cache'] = new Reference('twig.template_cache.chain');
159+
160+
$cacheOptions = ($config['auto_reload'] ?? false) ? FilesystemCache::FORCE_BYTECODE_INVALIDATION : 0;
161+
$container->getDefinition('twig.template_cache.runtime_cache')->replaceArgument(1, $cacheOptions);
162+
} else {
163+
$container->removeDefinition('twig.template_cache.chain');
164+
$container->removeDefinition('twig.template_cache.runtime_cache');
165+
$container->removeDefinition('twig.template_cache.readonly_cache');
166+
}
167+
156168
if (isset($config['autoescape_service'])) {
157169
$config['autoescape'] = [new Reference($config['autoescape_service']), $config['autoescape_service_method'] ?? '__invoke'];
158170
} else {

src/Symfony/Bundle/TwigBundle/Resources/config/twig.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
use Symfony\Bridge\Twig\Extension\WorkflowExtension;
3333
use Symfony\Bridge\Twig\Extension\YamlExtension;
3434
use Symfony\Bridge\Twig\Translation\TwigExtractor;
35+
use Symfony\Bundle\TwigBundle\Cache\ChainCache;
36+
use Symfony\Bundle\TwigBundle\Cache\ReadOnlyFilesystemCache;
3537
use Symfony\Bundle\TwigBundle\CacheWarmer\TemplateCacheWarmer;
3638
use Symfony\Bundle\TwigBundle\DependencyInjection\Configurator\EnvironmentConfigurator;
3739
use Symfony\Bundle\TwigBundle\TemplateIterator;
@@ -78,8 +80,17 @@
7880
->set('twig.template_iterator', TemplateIterator::class)
7981
->args([service('kernel'), abstract_arg('Twig paths'), param('twig.default_path'), abstract_arg('File name pattern')])
8082

83+
->set('twig.template_cache.runtime_cache', FilesystemCache::class)
84+
->args([param('kernel.cache_dir').'/twig', abstract_arg('Twig filesystem cache options')])
85+
86+
->set('twig.template_cache.readonly_cache', ReadOnlyFilesystemCache::class)
87+
->args([param('kernel.build_dir').'/twig'])
88+
89+
->set('twig.template_cache.chain', ChainCache::class)
90+
->args([[service('twig.template_cache.runtime_cache'), service('twig.template_cache.readonly_cache')]])
91+
8192
->set('twig.template_cache_warmer', TemplateCacheWarmer::class)
82-
->args([service(ContainerInterface::class), service('twig.template_iterator')])
93+
->args([service(ContainerInterface::class), service('twig.template_iterator'), 'twig'])
8394
->tag('kernel.cache_warmer')
8495
->tag('container.service_subscriber', ['id' => 'twig'])
8596

src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/full.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
],
1313
'auto_reload' => true,
1414
'base_template_class' => 'stdClass',
15-
'cache' => '/tmp',
1615
'charset' => 'ISO-8859-1',
1716
'debug' => true,
1817
'strict_variables' => true,

src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/xml/full.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
77
http://symfony.com/schema/dic/twig https://symfony.com/schema/dic/twig/twig-1.0.xsd">
88

9-
<twig:config auto-reload="true" base-template-class="stdClass" cache="/tmp" charset="ISO-8859-1" debug="true" strict-variables="true" default-path="%kernel.project_dir%/Fixtures/templates">
9+
<twig:config auto-reload="true" base-template-class="stdClass" charset="ISO-8859-1" debug="true" strict-variables="true" default-path="%kernel.project_dir%/Fixtures/templates">
1010
<twig:form-theme>MyBundle::form.html.twig</twig:form-theme>
1111
<twig:global key="foo" id="bar" type="service" />
1212
<twig:global key="baz">@@qux</twig:global>

src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/yml/full.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ twig:
88
bad: {key: foo}
99
auto_reload: true
1010
base_template_class: stdClass
11-
cache: /tmp
1211
charset: ISO-8859-1
1312
debug: true
1413
strict_variables: true

src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public function testLoadEmptyConfiguration()
4444

4545
// Twig options
4646
$options = $container->getDefinition('twig')->getArgument(1);
47-
$this->assertEquals('%kernel.cache_dir%/twig', $options['cache'], '->load() sets default value for cache option');
47+
$this->assertEquals(new Reference('twig.template_cache.chain'), $options['cache'], '->load() sets default value for cache option');
4848
$this->assertEquals('%kernel.charset%', $options['charset'], '->load() sets default value for charset option');
4949
$this->assertEquals('%kernel.debug%', $options['debug'], '->load() sets default value for debug option');
5050

@@ -92,7 +92,7 @@ public function testLoadFullConfiguration($format)
9292
$this->assertTrue($options['auto_reload'], '->load() sets the auto_reload option');
9393
$this->assertSame('name', $options['autoescape'], '->load() sets the autoescape option');
9494
$this->assertEquals('stdClass', $options['base_template_class'], '->load() sets the base_template_class option');
95-
$this->assertEquals('/tmp', $options['cache'], '->load() sets the cache option');
95+
$this->assertEquals(new Reference('twig.template_cache.chain'), $options['cache'], '->load() sets the cache option');
9696
$this->assertEquals('ISO-8859-1', $options['charset'], '->load() sets the charset option');
9797
$this->assertTrue($options['debug'], '->load() sets the debug option');
9898
$this->assertTrue($options['strict_variables'], '->load() sets the strict_variables option');

src/Symfony/Bundle/TwigBundle/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"composer-runtime-api": ">=2.1",
2121
"symfony/config": "^6.4|^7.0",
2222
"symfony/dependency-injection": "^6.4|^7.0",
23+
"symfony/deprecation-contracts": "^2.5|^3",
2324
"symfony/twig-bridge": "^6.4|^7.0",
2425
"symfony/http-foundation": "^6.4|^7.0",
2526
"symfony/http-kernel": "^6.4|^7.0",

0 commit comments

Comments
 (0)