diff --git a/CHANGELOG.md b/CHANGELOG.md
index a98d3ffd..a3eea964 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ This file contains a complete enumeration of all [pull requests](https://github.
for a given releases. Unreleased, upcoming changes will be updated here periodically; reference the next release on our
[milestones](https://github.com/liip/LiipImagineBundle/milestones) page for the latest changes.
+## [2.12.0](https://github.com/liip/LiipImagineBundle/tree/2.12.0)
+
+- Support JsonManifestVersionStrategy that was added in Symfony 6.
+
## [2.11.0](https://github.com/liip/LiipImagineBundle/tree/2.11.0)
- Compatibility with Symfony 6.3 (We do not expect users to extend a compiler passes or the DI extension of this bundle. If you did, you might need to adjust return types) ([mbabker](https://github.com/liip/LiipImagineBundle/pull/1514))
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/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 e66af07e..91923f8e 100644
--- a/composer.json
+++ b/composer.json
@@ -40,6 +40,7 @@
"phpstan/phpstan": "^1.10.0",
"psr/cache": "^1.0|^2.0|^3.0",
"psr/log": "^1.0",
+ "symfony/asset": "^3.4|^4.4|^5.3|^6.0",
"symfony/browser-kit": "^3.4|^4.4|^5.3|^6.0",
"symfony/cache": "^3.4|^4.4|^5.3|^6.0",
"symfony/console": "^3.4|^4.4|^5.3|^6.0",
@@ -53,6 +54,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",
@@ -65,6 +67,7 @@
"league/flysystem": "required to use FlySystem data loader or cache resolver",
"monolog/monolog": "A psr/log compatible logger is required to enable logging",
"rokka/imagine-vips": "required to use 'vips' driver",
+ "symfony/asset": "If you want to use asset versioning",
"symfony/messenger": "If you like to process images in background",
"symfony/templating": "required to use deprecated Templating component instead of Twig"
},