Skip to content

Commit

Permalink
Add support for JsonManifestVersionStrategy
Browse files Browse the repository at this point in the history
  • Loading branch information
wouterSkepp authored and dbu committed Sep 7, 2023
1 parent cb64b05 commit 7bd938b
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 19 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
31 changes: 26 additions & 5 deletions DependencyInjection/Compiler/AssetsVersionCompilerPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,24 @@

namespace Liip\ImagineBundle\DependencyInjection\Compiler;

use Symfony\Component\Asset\VersionStrategy\JsonManifestVersionStrategy;
use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy;
use Symfony\Component\DependencyInjection\ContainerBuilder;

/**
* 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
{
Expand All @@ -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;
Expand All @@ -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);
}
}

/**
Expand Down
1 change: 1 addition & 0 deletions Resources/config/imagine_twig_mode_lazy.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<tag name="twig.runtime" />
<argument type="service" id="liip_imagine.cache.manager" />
<argument>null</argument>
<argument>null</argument>
</service>

</services>
Expand Down
59 changes: 45 additions & 14 deletions Templating/LazyFilterRuntime.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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;
}
}
37 changes: 37 additions & 0 deletions Tests/Templating/LazyFilterRuntimeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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';
Expand Down
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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"
},
Expand Down

0 comments on commit 7bd938b

Please sign in to comment.