diff --git a/DependencyInjection/Compiler/AssetsVersionCompilerPass.php b/DependencyInjection/Compiler/AssetsVersionCompilerPass.php
index fba063ec..afd34dc3 100644
--- a/DependencyInjection/Compiler/AssetsVersionCompilerPass.php
+++ b/DependencyInjection/Compiler/AssetsVersionCompilerPass.php
@@ -11,6 +11,7 @@
namespace Liip\ImagineBundle\DependencyInjection\Compiler;
+use Symfony\Component\Asset\VersionStrategy\JsonManifestVersionStrategy;
use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -18,14 +19,16 @@
* Inject the Symfony framework assets version parameter to the
* LiipImagineBundle twig extension if possible.
*
- * We extract the version parameter from the StaticVersionStrategy service
- * definition. If anything is not as expected, we log a warning and do nothing.
+ * We extract either:
+ * - the version parameter from the StaticVersionStrategy service
+ * - the json manifest from the JsonManifestVersionStrategy service
+ * If anything is not as expected, we log a warning and do nothing.
*
* The expectation is for the user to configure the assets version in liip
* imagine for custom setups.
*
- * Anything other than StaticVersionStrategy needs to be implemented by the
- * user in CacheResolveEvent event listeners.
+ * Anything other than StaticVersionStrategy or JsonManifestVersionStrategy needs
+ * to be implemented by the user in CacheResolveEvent event listeners.
*/
class AssetsVersionCompilerPass extends AbstractCompilerPass
{
@@ -46,7 +49,7 @@ public function process(ContainerBuilder $container): void
}
$versionStrategyDefinition = $container->findDefinition('assets._version__default');
- if (!is_a($versionStrategyDefinition->getClass(), StaticVersionStrategy::class, true)) {
+ if (!is_a($versionStrategyDefinition->getClass(), StaticVersionStrategy::class, true) && !is_a($versionStrategyDefinition->getClass(), JsonManifestVersionStrategy::class, true)) {
$this->log($container, 'Symfony assets versioning strategy "'.$versionStrategyDefinition->getClass().'" not automatically supported. Configure liip_imagine.twig.assets_version if you have problems with assets versioning');
return;
@@ -61,6 +64,24 @@ public function process(ContainerBuilder $container): void
}
$runtimeDefinition->setArgument(1, $version);
+
+ if (is_a($versionStrategyDefinition->getClass(), JsonManifestVersionStrategy::class, true)) {
+ $jsonManifestString = file_get_contents($version);
+
+ if (!\is_string($jsonManifestString)) {
+ $this->log($container, 'Can not handle assets versioning with "'.$versionStrategyDefinition->getClass().'". The manifest file at "'.$version.' " could not be read');
+
+ return;
+ }
+ $jsonManifest = json_decode($jsonManifestString, true);
+ if (!\is_array($jsonManifest)) {
+ $this->log($container, 'Can not handle assets versioning with "'.$versionStrategyDefinition->getClass().'". The manifest file at "'.$version.' " does not contain valid JSON');
+
+ return;
+ }
+ $runtimeDefinition->setArgument(1, null);
+ $runtimeDefinition->setArgument(2, $jsonManifest);
+ }
}
/**
diff --git a/Resources/config/imagine_twig_mode_lazy.xml b/Resources/config/imagine_twig_mode_lazy.xml
index 9e4a9f1b..ac9ec6f1 100644
--- a/Resources/config/imagine_twig_mode_lazy.xml
+++ b/Resources/config/imagine_twig_mode_lazy.xml
@@ -14,6 +14,7 @@
null
+ null
diff --git a/Resources/doc/asset-versioning.rst b/Resources/doc/asset-versioning.rst
index 997ce44f..b7331599 100644
--- a/Resources/doc/asset-versioning.rst
+++ b/Resources/doc/asset-versioning.rst
@@ -17,6 +17,11 @@ setting for ``framework.assets.version``. It strips the version from the file
name and appends it to the resulting image URL so that the file is found and
cache busting is used.
+Since LiipImagineBundle version 2.12, we integrate with the configuration
+setting for ``framework.assets.json_manifest_path``. The manifest file is used
+to lookup the location of the actual file, and append the versioning string to
+the resulting image URL so that cache busting is used.
+
Cache Busting
~~~~~~~~~~~~~
@@ -25,6 +30,12 @@ versioning to bust the cache of your images. This can help for example after
you changed the settings of a filter set.
If you use ``framework.assets.version``, change the asset version in that case.
+If you use ``framework.assets.json_manifest_path``, then rebuild the manifest
+in your asset pipeline. Note that your versioning string might be calculated
+using a content hash. Changing a filter setting in these cases will *not* bust
+the previous cache. Either rename your filter in that case or use a different
+versioning strategy within your asset pipeline that ensures a new hash for each
+build.
If you do not use Symfony asset versioning, set the
``liip_imagine.twig.assets_version`` parameter. Note that you still need to
clear/refresh the cached images to have them updated, the asset version is only
diff --git a/Templating/LazyFilterRuntime.php b/Templating/LazyFilterRuntime.php
index 59a300e5..cc41e414 100644
--- a/Templating/LazyFilterRuntime.php
+++ b/Templating/LazyFilterRuntime.php
@@ -29,10 +29,22 @@ final class LazyFilterRuntime implements RuntimeExtensionInterface
*/
private $assetVersion;
- public function __construct(CacheManager $cache, string $assetVersion = null)
+ /**
+ * @var array|null
+ */
+ private $jsonManifest;
+
+ /**
+ * @var array|null
+ */
+ private $jsonManifestLookup;
+
+ public function __construct(CacheManager $cache, string $assetVersion = null, array $jsonManifest = null)
{
$this->cache = $cache;
$this->assetVersion = $assetVersion;
+ $this->jsonManifest = $jsonManifest;
+ $this->jsonManifestLookup = $jsonManifest ? array_flip($jsonManifest) : null;
}
/**
@@ -41,9 +53,9 @@ public function __construct(CacheManager $cache, string $assetVersion = null)
public function filter(string $path, string $filter, array $config = [], string $resolver = null, int $referenceType = UrlGeneratorInterface::ABSOLUTE_URL): string
{
$path = $this->cleanPath($path);
- $path = $this->cache->getBrowserPath($path, $filter, $config, $resolver, $referenceType);
+ $resolvedPath = $this->cache->getBrowserPath($path, $filter, $config, $resolver, $referenceType);
- return $this->appendAssetVersion($path);
+ return $this->appendAssetVersion($resolvedPath, $path);
}
/**
@@ -57,33 +69,52 @@ public function filterCache(string $path, string $filter, array $config = [], st
if (\count($config)) {
$path = $this->cache->getRuntimePath($path, $config);
}
- $path = $this->cache->resolve($path, $filter, $resolver);
+ $resolvedPath = $this->cache->resolve($path, $filter, $resolver);
- return $this->appendAssetVersion($path);
+ return $this->appendAssetVersion($resolvedPath, $path);
}
private function cleanPath(string $path): string
{
- if (!$this->assetVersion) {
+ if (!$this->assetVersion && !$this->jsonManifest) {
return $path;
}
- $start = mb_strrpos($path, $this->assetVersion);
- if (mb_strlen($path) - mb_strlen($this->assetVersion) === $start) {
- return rtrim(mb_substr($path, 0, $start), '?');
+ if ($this->assetVersion) {
+ $start = mb_strrpos($path, $this->assetVersion);
+ if (mb_strlen($path) - mb_strlen($this->assetVersion) === $start) {
+ return rtrim(mb_substr($path, 0, $start), '?');
+ }
+ }
+
+ if ($this->jsonManifest) {
+ if (\array_key_exists($path, $this->jsonManifestLookup)) {
+ return $this->jsonManifestLookup[$path];
+ }
}
return $path;
}
- private function appendAssetVersion(string $path): string
+ private function appendAssetVersion(string $resolvedPath, string $path): string
{
- if (!$this->assetVersion) {
- return $path;
+ if (!$this->assetVersion && !$this->jsonManifest) {
+ return $resolvedPath;
}
- $separator = false !== mb_strpos($path, '?') ? '&' : '?';
+ if ($this->assetVersion) {
+ $separator = false !== mb_strpos($resolvedPath, '?') ? '&' : '?';
+
+ return $resolvedPath.$separator.$this->assetVersion;
+ }
+
+ if (\array_key_exists($path, $this->jsonManifest)) {
+ $prefixedSlash = '/' !== mb_substr($path, 0, 1) && '/' === mb_substr($this->jsonManifest[$path], 0, 1);
+ $versionedPath = $prefixedSlash ? mb_substr($this->jsonManifest[$path], 1) : $this->jsonManifest[$path];
+
+ $resolvedPath = str_replace($path, $versionedPath, $resolvedPath);
+ }
- return $path.$separator.$this->assetVersion;
+ return $resolvedPath;
}
}
diff --git a/Tests/Templating/LazyFilterRuntimeTest.php b/Tests/Templating/LazyFilterRuntimeTest.php
index 0167feb4..6e55b9ef 100644
--- a/Tests/Templating/LazyFilterRuntimeTest.php
+++ b/Tests/Templating/LazyFilterRuntimeTest.php
@@ -23,6 +23,12 @@ class LazyFilterRuntimeTest extends AbstractTest
{
private const FILTER = 'thumbnail';
private const VERSION = 'v2';
+ private const JSON_MANIFEST = [
+ 'image/cats.png' => '/image/cats.png?v=bc321bd12a',
+ 'image/dogs.png' => '/image/dogs.ac38d2a1bc.png',
+ '/image/cows.png' => '/image/cows.png?v=a5de32a2c4',
+ '/image/sheep.png' => '/image/sheep.7ca26b36af.png',
+ ];
/**
* @var LazyFilterRuntime
@@ -101,6 +107,37 @@ public function testDifferentVersion(): void
$this->assertSame($cachePath.'?'.self::VERSION, $actualPath);
}
+ /**
+ * @dataProvider provideJsonManifest
+ */
+ public function testJsonManifestVersionHandling(string $sourcePath, string $versionedPath): void
+ {
+ $this->runtime = new LazyFilterRuntime($this->manager, null, self::JSON_MANIFEST);
+
+ $cachePath = 'image/cache/'.self::FILTER.'/'.('/' === (mb_substr($sourcePath, 0, 1)) ? mb_substr($sourcePath, 1) : $sourcePath);
+ $expectedPath = 'image/cache/'.self::FILTER.'/'.('/' === (mb_substr($versionedPath, 0, 1)) ? mb_substr($versionedPath, 1) : $versionedPath);
+
+ $this->manager
+ ->expects($this->once())
+ ->method('getBrowserPath')
+ ->with($sourcePath, self::FILTER)
+ ->willReturn($cachePath);
+
+ $actualPath = $this->runtime->filter($versionedPath, self::FILTER);
+
+ $this->assertSame($expectedPath, $actualPath);
+ }
+
+ public function provideJsonManifest(): array
+ {
+ return [
+ 'query parameter, no slash' => [array_keys(self::JSON_MANIFEST)[0], array_values(self::JSON_MANIFEST)[0]],
+ 'in filename, no slash' => [array_keys(self::JSON_MANIFEST)[1], array_values(self::JSON_MANIFEST)[1]],
+ 'query parameter, slash' => [array_keys(self::JSON_MANIFEST)[2], array_values(self::JSON_MANIFEST)[2]],
+ 'in filename, slash' => [array_keys(self::JSON_MANIFEST)[3], array_values(self::JSON_MANIFEST)[3]],
+ ];
+ }
+
public function testInvokeFilterCacheMethod(): void
{
$expectedInputPath = 'thePathToTheImage';
diff --git a/composer.json b/composer.json
index 55dbcad2..866b8fdc 100644
--- a/composer.json
+++ b/composer.json
@@ -53,6 +53,7 @@
},
"suggest": {
"ext-exif": "required to read EXIF metadata from images",
+ "ext-json": "required to read JSON manifest versioning",
"ext-gd": "required to use gd driver",
"ext-gmagick": "required to use gmagick driver",
"ext-imagick": "required to use imagick driver",