diff --git a/src/LiveComponent/src/DependencyInjection/LiveComponentExtension.php b/src/LiveComponent/src/DependencyInjection/LiveComponentExtension.php index 97c48d75d5e..8683214ad3b 100644 --- a/src/LiveComponent/src/DependencyInjection/LiveComponentExtension.php +++ b/src/LiveComponent/src/DependencyInjection/LiveComponentExtension.php @@ -256,7 +256,11 @@ function (ChildDefinition $definition, AsLiveComponent $attribute) { ->setArguments(['%kernel.cache_dir%/'.self::TEMPLATES_MAP_FILENAME]); $container->register('ux.live_component.twig.cache_warmer', TemplateCacheWarmer::class) - ->setArguments([new Reference('twig.template_iterator'), self::TEMPLATES_MAP_FILENAME]) + ->setArguments([ + new Reference('twig.template_iterator'), + self::TEMPLATES_MAP_FILENAME, + '%kernel.secret%', + ]) ->addTag('kernel.cache_warmer'); } diff --git a/src/LiveComponent/src/Twig/TemplateCacheWarmer.php b/src/LiveComponent/src/Twig/TemplateCacheWarmer.php index 18b33be2801..3c9d18ac2f6 100644 --- a/src/LiveComponent/src/Twig/TemplateCacheWarmer.php +++ b/src/LiveComponent/src/Twig/TemplateCacheWarmer.php @@ -22,15 +22,18 @@ */ final class TemplateCacheWarmer implements CacheWarmerInterface { - public function __construct(private \IteratorAggregate $templateIterator, private readonly string $cacheFilename) - { + public function __construct( + private readonly \IteratorAggregate $templateIterator, + private readonly string $cacheFilename, + private readonly string $secret, + ) { } public function warmUp(string $cacheDir, ?string $buildDir = null): array { $map = []; foreach ($this->templateIterator as $item) { - $map[bin2hex(random_bytes(16))] = $item; + $map[hash('xxh128', $item.$this->secret)] = $item; } (new PhpArrayAdapter($cacheDir.'/'.$this->cacheFilename, new NullAdapter()))->warmUp(['map' => $map]); diff --git a/src/LiveComponent/src/Twig/TemplateMap.php b/src/LiveComponent/src/Twig/TemplateMap.php index 75b8f289d68..03dd2ffdf0b 100644 --- a/src/LiveComponent/src/Twig/TemplateMap.php +++ b/src/LiveComponent/src/Twig/TemplateMap.php @@ -28,7 +28,7 @@ public function __construct(string $cacheFile) $this->map = (new PhpArrayAdapter($cacheFile, new NullAdapter()))->getItem('map')->get(); } - public function resolve(string $obscuredName) + public function resolve(string $obscuredName): string { return $this->map[$obscuredName] ?? throw new \RuntimeException(sprintf('Cannot find a template matching "%s". Cache may be corrupt.', $obscuredName)); } diff --git a/src/LiveComponent/tests/Unit/Twig/TemplateCacheWarmerTest.php b/src/LiveComponent/tests/Unit/Twig/TemplateCacheWarmerTest.php new file mode 100644 index 00000000000..5e52311b60e --- /dev/null +++ b/src/LiveComponent/tests/Unit/Twig/TemplateCacheWarmerTest.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\LiveComponent\Tests\Unit\Twig; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Cache\Adapter\NullAdapter; +use Symfony\Component\Cache\Adapter\PhpArrayAdapter; +use Symfony\UX\LiveComponent\Twig\TemplateCacheWarmer; + +/** + * @author Simon André + */ +final class TemplateCacheWarmerTest extends TestCase +{ + private string $cacheDir; + private string $cacheFile; + private TemplateCacheWarmer $templateCacheWarmer; + + protected function setUp(): void + { + $this->cacheDir ??= sys_get_temp_dir(); + $this->cacheFile ??= $this->cacheDir.'/cache_file'; + if (file_exists($this->cacheFile)) { + unlink($this->cacheFile); + } + $this->templateCacheWarmer ??= new TemplateCacheWarmer( + new \ArrayObject(['template1', 'template2']), + 'cache_file', + 'secret' + ); + } + + public function testWarmUpCreatesCacheFile(): void + { + $this->assertFileDoesNotExist($this->cacheFile); + + $this->templateCacheWarmer->warmUp($this->cacheDir); + + $this->assertFileExists($this->cacheFile); + } + + public function testWarmUpCreatesCorrectCacheContent(): void + { + $this->templateCacheWarmer->warmUp($this->cacheDir); + $adapter = new PhpArrayAdapter($this->cacheFile, new NullAdapter()); + $item = $adapter->getItem('map'); + + $this->assertSame( + [ + hash('xxh128', 'template1secret') => 'template1', + hash('xxh128', 'template2secret') => 'template2', + ], + $item->get() + ); + } + + public function testWarmUpCreatesReproductibleTemplateMap(): void + { + $this->templateCacheWarmer->warmUp($this->cacheDir); + $adapter = new PhpArrayAdapter($this->cacheFile, new NullAdapter()); + $map1 = $adapter->getItem('map')->get(); + + $this->templateCacheWarmer->warmUp($this->cacheDir); + $adapter = new PhpArrayAdapter($this->cacheFile, new NullAdapter()); + $map2 = $adapter->getItem('map')->get(); + + $this->assertSame($map1, $map2); + } +}