diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index eab898b8829fd..897341c554c45 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -580,6 +580,7 @@ private function addAssetsSection(ArrayNodeDefinition $rootNode) ->scalarNode('version')->defaultNull()->end() ->scalarNode('version_format')->defaultValue('%%s?%%s')->end() ->scalarNode('json_manifest_path')->defaultNull()->end() + ->booleanNode('cache')->defaultFalse()->end() ->scalarNode('base_path')->defaultValue('')->end() ->arrayNode('base_urls') ->requiresAtLeastOneElement() @@ -622,6 +623,7 @@ private function addAssetsSection(ArrayNodeDefinition $rootNode) ->end() ->scalarNode('version_format')->defaultNull()->end() ->scalarNode('json_manifest_path')->defaultNull()->end() + ->scalarNode('cache')->defaultFalse()->end() ->scalarNode('base_path')->defaultValue('')->end() ->arrayNode('base_urls') ->requiresAtLeastOneElement() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 666797f2039c3..ffa5ae4eec5b7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -996,6 +996,10 @@ private function registerAssetsConfiguration(array $config, ContainerBuilder $co $defaultVersion = $this->createVersion($container, $config['version'], $config['version_format'], $config['json_manifest_path'], '_default'); } + if ($config['cache']) { + $defaultVersion = $this->decorateVersionStrategyWithCache($container, $defaultVersion); + } + $defaultPackage = $this->createPackageDefinition($config['base_path'], $config['base_urls'], $defaultVersion); $container->setDefinition('assets._default_package', $defaultPackage); @@ -1011,6 +1015,9 @@ private function registerAssetsConfiguration(array $config, ContainerBuilder $co $format = $package['version_format'] ?: $config['version_format']; $version = isset($package['version']) ? $package['version'] : null; $version = $this->createVersion($container, $version, $format, $package['json_manifest_path'], $name); + if ($package['cache']) { + $version = $this->decorateVersionStrategyWithCache($container, $version); + } } $container->setDefinition('assets._package_'.$name, $this->createPackageDefinition($package['base_path'], $package['base_urls'], $version)); @@ -1073,6 +1080,16 @@ private function createVersion(ContainerBuilder $container, ?string $version, ?s return new Reference('assets.empty_version_strategy'); } + private function decorateVersionStrategyWithCache(ContainerBuilder $container, Reference $reference): Reference + { + $cachedReference = new Reference($reference.'.cached'); + $cached = new ChildDefinition('assets.cached_version_strategy'); + $cached->replaceArgument(0, $reference); + $container->setDefinition($cachedReference, $cached); + + return $cachedReference; + } + private function registerTranslatorConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader, string $defaultLocale) { if (!$this->isConfigEnabled($container, $config)) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/assets.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/assets.xml index 73ec21ab429e0..0613bf6141c45 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/assets.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/assets.xml @@ -55,5 +55,10 @@ + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml index 9959debfa94a5..956582b9a2756 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml @@ -15,6 +15,10 @@ + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 99ffbabb82cdc..c99cda3f50274 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -144,6 +144,7 @@ + @@ -157,6 +158,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 3ba4c3ecfecf8..13168dd3bb60e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -86,6 +86,7 @@ public function testAssetsCanBeEnabled() 'base_urls' => [], 'packages' => [], 'json_manifest_path' => null, + 'cache' => false, ]; $this->assertEquals($defaultConfig, $config['assets']); @@ -439,6 +440,7 @@ protected static function getBundleDefaultConfig() 'base_urls' => [], 'packages' => [], 'json_manifest_path' => null, + 'cache' => false, ], 'cache' => [ 'pools' => [], diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/assets.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/assets.php index ef2fd77013f85..20b2f03851f1d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/assets.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/assets.php @@ -30,6 +30,10 @@ 'remote_manifest' => [ 'json_manifest_path' => 'https://cdn.example.com/manifest.json', ], + 'cached' => [ + 'json_manifest_path' => 'https://cdn.example.com/manifest.json', + 'cache' => true, + ], ], ], ]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/assets.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/assets.xml index 24bfdc6456185..0ab7d2c7d487e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/assets.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/assets.xml @@ -23,6 +23,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/assets.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/assets.yml index 4a4a57bc43a79..72260201a777f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/assets.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/assets.yml @@ -21,3 +21,6 @@ framework: json_manifest_path: '/path/to/manifest.json' remote_manifest: json_manifest_path: 'https://cdn.example.com/manifest.json' + cached: + json_manifest_path: 'https://cdn.example.com/manifest.json' + cache: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index f17597589683d..c8a373d192a59 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -535,7 +535,7 @@ public function testAssets() // packages $packages = $packages->getArgument(1); - $this->assertCount(7, $packages); + $this->assertCount(8, $packages); $package = $container->getDefinition((string) $packages['images_path']); $this->assertPathPackage($container, $package, '/foo', 'SomeVersionScheme', '%%s?version=%%s'); @@ -561,6 +561,10 @@ public function testAssets() $versionStrategy = $container->getDefinition($package->getArgument(1)); $this->assertSame('assets.remote_json_manifest_version_strategy', $versionStrategy->getParent()); $this->assertSame('https://cdn.example.com/manifest.json', $versionStrategy->getArgument(0)); + + $package = $container->getDefinition($packages['cached']); + $versionStrategy = $container->getDefinition($package->getArgument(1)); + $this->assertSame('assets.cached_version_strategy', $versionStrategy->getParent()); } public function testAssetsDefaultVersionStrategyAsService() diff --git a/src/Symfony/Component/Asset/Tests/VersionStrategy/CachedVersionStrategyTest.php b/src/Symfony/Component/Asset/Tests/VersionStrategy/CachedVersionStrategyTest.php new file mode 100644 index 0000000000000..3caf78d9bb0de --- /dev/null +++ b/src/Symfony/Component/Asset/Tests/VersionStrategy/CachedVersionStrategyTest.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Asset\Tests\VersionStrategy; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Asset\VersionStrategy\CachedVersionStrategy; +use Symfony\Component\Asset\VersionStrategy\VersionStrategyInterface; +use Symfony\Component\Cache\Adapter\ArrayAdapter; + +class CachedVersionStrategyTest extends TestCase +{ + public function testGetVersion() + { + $value = '1.0.0'; + $strategy = $this->getMockBuilder(VersionStrategyInterface::class)->getMock(); + $strategy + ->expects($this->once()) + ->method('getVersion') + ->willReturn($value); + + $cache = new ArrayAdapter(); + $cachedVersionStrategy = new CachedVersionStrategy($strategy, $cache); + $path = 'test-path'; + + $this->assertSame($value, $cachedVersionStrategy->getVersion($path)); + $this->assertSame($value, $cachedVersionStrategy->getVersion($path), '2nd call is cached'); + } + + public function testApplyVersion() + { + $value = 'test/path/1.0.0'; + $strategy = $this->getMockBuilder(VersionStrategyInterface::class)->getMock(); + $strategy + ->expects($this->once()) + ->method('applyVersion') + ->willReturn($value); + + $cache = new ArrayAdapter(); + $cachedVersionStrategy = new CachedVersionStrategy($strategy, $cache); + $path = 'test/path'; + + $this->assertSame($value, $cachedVersionStrategy->applyVersion($path)); + $this->assertSame($value, $cachedVersionStrategy->applyVersion($path), '2nd call is cached'); + } +} diff --git a/src/Symfony/Component/Asset/VersionStrategy/CachedVersionStrategy.php b/src/Symfony/Component/Asset/VersionStrategy/CachedVersionStrategy.php new file mode 100644 index 0000000000000..63e6c015b6e56 --- /dev/null +++ b/src/Symfony/Component/Asset/VersionStrategy/CachedVersionStrategy.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Asset\VersionStrategy; + +use Symfony\Contracts\Cache\CacheInterface; + +/** + * Cache generated versions and path of any version strategy. + * + * @author Jérôme TAMARELLE + */ +class CachedVersionStrategy implements VersionStrategyInterface +{ + private $strategy; + + private $cache; + + public function __construct(VersionStrategyInterface $strategy, CacheInterface $cache) + { + $this->strategy = $strategy; + $this->cache = $cache; + } + + /** + * {@inheritdoc} + */ + public function getVersion(string $path) + { + return $this->cache->get('v_'.rawurlencode($path), function () use ($path) { + return $this->strategy->getVersion($path); + }); + } + + /** + * {@inheritdoc} + */ + public function applyVersion(string $path) + { + return $this->cache->get('p_'.rawurlencode($path), function () use ($path) { + return $this->strategy->applyVersion($path); + }); + } +} diff --git a/src/Symfony/Component/Asset/composer.json b/src/Symfony/Component/Asset/composer.json index 79fac112628d3..b65533000c42c 100644 --- a/src/Symfony/Component/Asset/composer.json +++ b/src/Symfony/Component/Asset/composer.json @@ -24,7 +24,8 @@ "require-dev": { "symfony/http-client": "^4.4|^5.0", "symfony/http-foundation": "^4.4|^5.0", - "symfony/http-kernel": "^4.4|^5.0" + "symfony/http-kernel": "^4.4|^5.0", + "symfony/cache": "^4.4|^5.0" }, "autoload": { "psr-4": { "Symfony\\Component\\Asset\\": "" },