From c30473d46e2b5e88dcd07b265a535b8ea303de31 Mon Sep 17 00:00:00 2001 From: tito10047 Date: Tue, 23 Dec 2025 19:56:20 +0100 Subject: [PATCH 01/20] Added AssetMapperLoaderFactory and AssetMapperLocator --- Binary/Loader/AssetMapperLoader.php | 91 +++++++++++++++++++ Binary/Locator/AssetMapperLocator.php | 38 ++++++++ .../Loader/AssetMapperLoaderFactory.php | 56 ++++++++++++ LiipImagineBundle.php | 2 + Resources/config/imagine.php | 15 +++ Tests/LiipImagineBundleTest.php | 4 +- 6 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 Binary/Loader/AssetMapperLoader.php create mode 100644 Binary/Locator/AssetMapperLocator.php create mode 100644 DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php diff --git a/Binary/Loader/AssetMapperLoader.php b/Binary/Loader/AssetMapperLoader.php new file mode 100644 index 000000000..a8faa63fd --- /dev/null +++ b/Binary/Loader/AssetMapperLoader.php @@ -0,0 +1,91 @@ +mimeTypeGuesser = $mimeGuesser; + $this->extensionGuesser = $extensionGuesser; + $this->locator = $locator; + } + public function find($path) + { + $path = $this->locator->locate($path); + + if (false === is_file($path)) { + throw new NotLoadableException(\sprintf('Source image: "%s" is no file.', $path)); + } + + $mimeType = $this->mimeTypeGuesser instanceof DeprecatedMimeTypeGuesserInterface ? $this->mimeTypeGuesser->guess($path) : $this->mimeTypeGuesser->guessMimeType($path); + $extension = $this->getExtension($mimeType); + + return new FileBinary($path, $mimeType, $extension); + } + + private function getExtension(?string $mimeType): ?string + { + if ($this->extensionGuesser instanceof DeprecatedExtensionGuesserInterface) { + return $this->extensionGuesser->guess($mimeType); + } + + if (null === $mimeType) { + return null; + } + + return $this->extensionGuesser->getExtensions($mimeType)[0] ?? null; + } + + +} diff --git a/Binary/Locator/AssetMapperLocator.php b/Binary/Locator/AssetMapperLocator.php new file mode 100644 index 000000000..9981614fe --- /dev/null +++ b/Binary/Locator/AssetMapperLocator.php @@ -0,0 +1,38 @@ +assetMapper->allAssets() as $assetCandidate) { + if ($path === $assetCandidate->publicPath) { + return $assetCandidate->sourcePath; + } + } + throw new NotLoadableException(\sprintf('Asset with public path "%s" not found.', $path)); + + } +} diff --git a/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php b/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php new file mode 100644 index 000000000..ebe0611a1 --- /dev/null +++ b/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php @@ -0,0 +1,56 @@ +replaceArgument(0,new Reference('asset_mapper')); + + $definition = $this->getChildLoaderDefinition(); + + if ($container->hasDefinition('liip_imagine.mime_types')) { + $mimeTypes = $container->getDefinition('liip_imagine.mime_types'); + $definition->replaceArgument(0, $mimeTypes); + $definition->replaceArgument(1, $mimeTypes); + } + $definition->replaceArgument(2, $locatorDefinition); + + return $this->setTaggedLoaderDefinition($loaderName, $definition, $container); + } + + public function getName() + { + return 'asset_mapper'; + } + + public function addConfiguration(ArrayNodeDefinition $builder) + { + $builder + ->children() +// ->scalarNode('superfoo') +// ->isRequired() +// ->cannotBeEmpty() +// ->end() + ->end(); + } + +} diff --git a/LiipImagineBundle.php b/LiipImagineBundle.php index f3457915a..5ea0e6375 100644 --- a/LiipImagineBundle.php +++ b/LiipImagineBundle.php @@ -23,6 +23,7 @@ use Liip\ImagineBundle\DependencyInjection\Compiler\NonFunctionalFilterExceptionPass; use Liip\ImagineBundle\DependencyInjection\Compiler\PostProcessorsCompilerPass; use Liip\ImagineBundle\DependencyInjection\Compiler\ResolversCompilerPass; +use Liip\ImagineBundle\DependencyInjection\Factory\Loader\AssetMapperLoaderFactory; use Liip\ImagineBundle\DependencyInjection\Factory\Loader\ChainLoaderFactory; use Liip\ImagineBundle\DependencyInjection\Factory\Loader\FileSystemLoaderFactory; use Liip\ImagineBundle\DependencyInjection\Factory\Loader\FlysystemLoaderFactory; @@ -69,6 +70,7 @@ public function build(ContainerBuilder $container): void $extension->addLoaderFactory(new StreamLoaderFactory()); $extension->addLoaderFactory(new FileSystemLoaderFactory()); $extension->addLoaderFactory(new FlysystemLoaderFactory()); + $extension->addLoaderFactory(new AssetMapperLoaderFactory()); $extension->addLoaderFactory(new ChainLoaderFactory()); $container->registerForAutoconfiguration(LoaderLoaderInterface::class)->addTag('liip_imagine.filter.loader'); diff --git a/Resources/config/imagine.php b/Resources/config/imagine.php index 045b526f3..5de127dd8 100644 --- a/Resources/config/imagine.php +++ b/Resources/config/imagine.php @@ -16,11 +16,13 @@ use Imagine\Gd\Imagine; use Imagine\Image\ImagineInterface; use Imagine\Image\Metadata\ExifMetadataReader; +use Liip\ImagineBundle\Binary\Loader\AssetMapperLoader; use Liip\ImagineBundle\Binary\Loader\ChainLoader; use Liip\ImagineBundle\Binary\Loader\FileSystemLoader; use Liip\ImagineBundle\Binary\Loader\FlysystemLoader; use Liip\ImagineBundle\Binary\Loader\FlysystemV2Loader; use Liip\ImagineBundle\Binary\Loader\StreamLoader; +use Liip\ImagineBundle\Binary\Locator\AssetMapperLocator; use Liip\ImagineBundle\Binary\Locator\FileSystemInsecureLocator; use Liip\ImagineBundle\Binary\Locator\FileSystemLocator; use Liip\ImagineBundle\Binary\SimpleMimeTypeGuesser; @@ -419,6 +421,12 @@ $services->set('liip_imagine.binary.loader.prototype.chain', ChainLoader::class) ->abstract() ->args(['']); // will be injected by ChainLoaderFactory + $services->set('liip_imagine.binary.loader.prototype.asset_mapper', AssetMapperLoader::class) + ->args([ + service('liip_imagine.mime_type_guesser'), + service('liip_imagine.extension_guesser'), + '', // will be injected by FileSystemLoaderFactory + ]); // Data loader locators $services->set('liip_imagine.binary.locator.filesystem', FileSystemLocator::class) @@ -429,6 +437,13 @@ '', // will be injected by FilesystemLoaderFactory ]) ->tag('liip_imagine.binary.locator', ['shared' => false]); + $services->set('liip_imagine.binary.locator.asset_mapper', AssetMapperLocator::class) + ->share(true) + ->public() + ->args([ + '' + ]) + ->tag('liip_imagine.binary.locator', ['shared' => false]); $services->set('liip_imagine.binary.locator.filesystem_insecure', FileSystemInsecureLocator::class) ->share(false) diff --git a/Tests/LiipImagineBundleTest.php b/Tests/LiipImagineBundleTest.php index 232dd40ae..e92a2d36d 100644 --- a/Tests/LiipImagineBundleTest.php +++ b/Tests/LiipImagineBundleTest.php @@ -20,6 +20,7 @@ use Liip\ImagineBundle\DependencyInjection\Compiler\NonFunctionalFilterExceptionPass; use Liip\ImagineBundle\DependencyInjection\Compiler\PostProcessorsCompilerPass; use Liip\ImagineBundle\DependencyInjection\Compiler\ResolversCompilerPass; +use Liip\ImagineBundle\DependencyInjection\Factory\Loader\AssetMapperLoaderFactory; use Liip\ImagineBundle\DependencyInjection\Factory\Loader\ChainLoaderFactory; use Liip\ImagineBundle\DependencyInjection\Factory\Loader\FileSystemLoaderFactory; use Liip\ImagineBundle\DependencyInjection\Factory\Loader\FlysystemLoaderFactory; @@ -150,7 +151,8 @@ public function testAddLoaders(): void StreamLoaderFactory::class, FileSystemLoaderFactory::class, FlysystemLoaderFactory::class, - ChainLoaderFactory::class, + AssetMapperLoaderFactory::class, + ChainLoaderFactory::class ], $loaders); } From 09003a9a82edcabac2ba8b9ce45448578eaef3ff Mon Sep 17 00:00:00 2001 From: tito10047 Date: Thu, 25 Dec 2025 07:35:07 +0100 Subject: [PATCH 02/20] removed duplicated loader and use filesystem instead --- Binary/Loader/AssetMapperLoader.php | 91 ------------------- .../Loader/AssetMapperLoaderFactory.php | 2 +- Resources/config/imagine.php | 7 +- 3 files changed, 2 insertions(+), 98 deletions(-) delete mode 100644 Binary/Loader/AssetMapperLoader.php diff --git a/Binary/Loader/AssetMapperLoader.php b/Binary/Loader/AssetMapperLoader.php deleted file mode 100644 index a8faa63fd..000000000 --- a/Binary/Loader/AssetMapperLoader.php +++ /dev/null @@ -1,91 +0,0 @@ -mimeTypeGuesser = $mimeGuesser; - $this->extensionGuesser = $extensionGuesser; - $this->locator = $locator; - } - public function find($path) - { - $path = $this->locator->locate($path); - - if (false === is_file($path)) { - throw new NotLoadableException(\sprintf('Source image: "%s" is no file.', $path)); - } - - $mimeType = $this->mimeTypeGuesser instanceof DeprecatedMimeTypeGuesserInterface ? $this->mimeTypeGuesser->guess($path) : $this->mimeTypeGuesser->guessMimeType($path); - $extension = $this->getExtension($mimeType); - - return new FileBinary($path, $mimeType, $extension); - } - - private function getExtension(?string $mimeType): ?string - { - if ($this->extensionGuesser instanceof DeprecatedExtensionGuesserInterface) { - return $this->extensionGuesser->guess($mimeType); - } - - if (null === $mimeType) { - return null; - } - - return $this->extensionGuesser->getExtensions($mimeType)[0] ?? null; - } - - -} diff --git a/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php b/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php index ebe0611a1..43a82634f 100644 --- a/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php +++ b/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php @@ -25,7 +25,7 @@ public function create(ContainerBuilder $container, $loaderName, array $config) $locatorDefinition = new ChildDefinition(\sprintf('liip_imagine.binary.locator.asset_mapper')); $locatorDefinition->replaceArgument(0,new Reference('asset_mapper')); - $definition = $this->getChildLoaderDefinition(); + $definition = $this->getChildLoaderDefinition('filesystem'); if ($container->hasDefinition('liip_imagine.mime_types')) { $mimeTypes = $container->getDefinition('liip_imagine.mime_types'); diff --git a/Resources/config/imagine.php b/Resources/config/imagine.php index 5de127dd8..66113d1d4 100644 --- a/Resources/config/imagine.php +++ b/Resources/config/imagine.php @@ -421,12 +421,7 @@ $services->set('liip_imagine.binary.loader.prototype.chain', ChainLoader::class) ->abstract() ->args(['']); // will be injected by ChainLoaderFactory - $services->set('liip_imagine.binary.loader.prototype.asset_mapper', AssetMapperLoader::class) - ->args([ - service('liip_imagine.mime_type_guesser'), - service('liip_imagine.extension_guesser'), - '', // will be injected by FileSystemLoaderFactory - ]); + // Data loader locators $services->set('liip_imagine.binary.locator.filesystem', FileSystemLocator::class) From b7bebb85439246ade86bcedfa75363f7dd6f5117 Mon Sep 17 00:00:00 2001 From: tito10047 Date: Thu, 25 Dec 2025 07:40:57 +0100 Subject: [PATCH 03/20] add test --- .../Loader/AssetMapperLoaderFactoryTest.php | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 Tests/DependencyInjection/Factory/Loader/AssetMapperLoaderFactoryTest.php diff --git a/Tests/DependencyInjection/Factory/Loader/AssetMapperLoaderFactoryTest.php b/Tests/DependencyInjection/Factory/Loader/AssetMapperLoaderFactoryTest.php new file mode 100644 index 000000000..aa01adba7 --- /dev/null +++ b/Tests/DependencyInjection/Factory/Loader/AssetMapperLoaderFactoryTest.php @@ -0,0 +1,52 @@ + + */ +class AssetMapperLoaderFactoryTest extends FactoryTestCase +{ + public function testImplementsLoaderFactoryInterface(): void + { + $this->assertInstanceOf(LoaderFactoryInterface::class, new AssetMapperLoaderFactory()); + } + + public function testReturnsExpectedName(): void + { + $this->assertSame('asset_mapper', (new AssetMapperLoaderFactory())->getName()); + } + + public function testCreateLoaderDefinition(): void + { + $container = new ContainerBuilder(); + + $loader = new AssetMapperLoaderFactory(); + $loader->create($container, 'the_loader_name', [ + ]); + + $this->assertTrue($container->hasDefinition('liip_imagine.binary.loader.the_loader_name')); + + /** @var ChildDefinition $loaderDefinition */ + $loaderDefinition = $container->getDefinition('liip_imagine.binary.loader.the_loader_name'); + + $this->assertInstanceOfChildDefinition($loaderDefinition); + $this->assertSame('liip_imagine.binary.loader.prototype.filesystem', $loaderDefinition->getParent()); + + } + +} From 34b38d625fe94a41f44c3bc35d4c3d85038c0293 Mon Sep 17 00:00:00 2001 From: tito10047 Date: Thu, 25 Dec 2025 07:45:30 +0100 Subject: [PATCH 04/20] php-cs-fixer run --- Binary/Locator/AssetMapperLocator.php | 35 +++++++++---------- .../Loader/AssetMapperLoaderFactory.php | 29 +++++++-------- Resources/config/imagine.php | 6 ++-- .../Loader/AssetMapperLoaderFactoryTest.php | 3 +- Tests/LiipImagineBundleTest.php | 4 +-- 5 files changed, 34 insertions(+), 43 deletions(-) diff --git a/Binary/Locator/AssetMapperLocator.php b/Binary/Locator/AssetMapperLocator.php index 9981614fe..1ac3f7655 100644 --- a/Binary/Locator/AssetMapperLocator.php +++ b/Binary/Locator/AssetMapperLocator.php @@ -12,27 +12,24 @@ namespace Liip\ImagineBundle\Binary\Locator; use Liip\ImagineBundle\Exception\Binary\Loader\NotLoadableException; -use Liip\ImagineBundle\Exception\InvalidArgumentException; use Symfony\Component\AssetMapper\AssetMapperInterface; class AssetMapperLocator implements LocatorInterface { - public function __construct( - private readonly AssetMapperInterface $assetMapper, - - ) { } - - public function locate(string $path): string { - - - $asset = null; - $path = "/".ltrim($path, '/'); - foreach ($this->assetMapper->allAssets() as $assetCandidate) { - if ($path === $assetCandidate->publicPath) { - return $assetCandidate->sourcePath; - } - } - throw new NotLoadableException(\sprintf('Asset with public path "%s" not found.', $path)); - - } + public function __construct( + private readonly AssetMapperInterface $assetMapper, + ) { + } + + public function locate(string $path): string + { + $asset = null; + $path = '/'.mb_ltrim($path, '/'); + foreach ($this->assetMapper->allAssets() as $assetCandidate) { + if ($path === $assetCandidate->publicPath) { + return $assetCandidate->sourcePath; + } + } + throw new NotLoadableException(\sprintf('Asset with public path "%s" not found.', $path)); + } } diff --git a/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php b/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php index 43a82634f..41608cea2 100644 --- a/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php +++ b/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php @@ -11,31 +11,29 @@ namespace Liip\ImagineBundle\DependencyInjection\Factory\Loader; -use Symfony\Component\AssetMapper\AssetMapper; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; -use function Symfony\Component\DependencyInjection\Loader\Configurator\service; class AssetMapperLoaderFactory extends AbstractLoaderFactory { - public function create(ContainerBuilder $container, $loaderName, array $config) - { - $locatorDefinition = new ChildDefinition(\sprintf('liip_imagine.binary.locator.asset_mapper')); - $locatorDefinition->replaceArgument(0,new Reference('asset_mapper')); + public function create(ContainerBuilder $container, $loaderName, array $config) + { + $locatorDefinition = new ChildDefinition('liip_imagine.binary.locator.asset_mapper'); + $locatorDefinition->replaceArgument(0, new Reference('asset_mapper')); - $definition = $this->getChildLoaderDefinition('filesystem'); + $definition = $this->getChildLoaderDefinition('filesystem'); - if ($container->hasDefinition('liip_imagine.mime_types')) { - $mimeTypes = $container->getDefinition('liip_imagine.mime_types'); - $definition->replaceArgument(0, $mimeTypes); - $definition->replaceArgument(1, $mimeTypes); - } - $definition->replaceArgument(2, $locatorDefinition); + if ($container->hasDefinition('liip_imagine.mime_types')) { + $mimeTypes = $container->getDefinition('liip_imagine.mime_types'); + $definition->replaceArgument(0, $mimeTypes); + $definition->replaceArgument(1, $mimeTypes); + } + $definition->replaceArgument(2, $locatorDefinition); - return $this->setTaggedLoaderDefinition($loaderName, $definition, $container); - } + return $this->setTaggedLoaderDefinition($loaderName, $definition, $container); + } public function getName() { @@ -52,5 +50,4 @@ public function addConfiguration(ArrayNodeDefinition $builder) // ->end() ->end(); } - } diff --git a/Resources/config/imagine.php b/Resources/config/imagine.php index 66113d1d4..02e816d51 100644 --- a/Resources/config/imagine.php +++ b/Resources/config/imagine.php @@ -16,7 +16,6 @@ use Imagine\Gd\Imagine; use Imagine\Image\ImagineInterface; use Imagine\Image\Metadata\ExifMetadataReader; -use Liip\ImagineBundle\Binary\Loader\AssetMapperLoader; use Liip\ImagineBundle\Binary\Loader\ChainLoader; use Liip\ImagineBundle\Binary\Loader\FileSystemLoader; use Liip\ImagineBundle\Binary\Loader\FlysystemLoader; @@ -422,7 +421,6 @@ ->abstract() ->args(['']); // will be injected by ChainLoaderFactory - // Data loader locators $services->set('liip_imagine.binary.locator.filesystem', FileSystemLocator::class) ->share(false) @@ -436,8 +434,8 @@ ->share(true) ->public() ->args([ - '' - ]) + '', + ]) ->tag('liip_imagine.binary.locator', ['shared' => false]); $services->set('liip_imagine.binary.locator.filesystem_insecure', FileSystemInsecureLocator::class) diff --git a/Tests/DependencyInjection/Factory/Loader/AssetMapperLoaderFactoryTest.php b/Tests/DependencyInjection/Factory/Loader/AssetMapperLoaderFactoryTest.php index aa01adba7..e682fae5f 100644 --- a/Tests/DependencyInjection/Factory/Loader/AssetMapperLoaderFactoryTest.php +++ b/Tests/DependencyInjection/Factory/Loader/AssetMapperLoaderFactoryTest.php @@ -16,6 +16,7 @@ use Liip\ImagineBundle\Tests\DependencyInjection\Factory\FactoryTestCase; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; + /** * @covers \Liip\ImagineBundle\DependencyInjection\Factory\Loader\AssetMapperLoaderFactory */ @@ -46,7 +47,5 @@ public function testCreateLoaderDefinition(): void $this->assertInstanceOfChildDefinition($loaderDefinition); $this->assertSame('liip_imagine.binary.loader.prototype.filesystem', $loaderDefinition->getParent()); - } - } diff --git a/Tests/LiipImagineBundleTest.php b/Tests/LiipImagineBundleTest.php index e92a2d36d..8082beab9 100644 --- a/Tests/LiipImagineBundleTest.php +++ b/Tests/LiipImagineBundleTest.php @@ -151,8 +151,8 @@ public function testAddLoaders(): void StreamLoaderFactory::class, FileSystemLoaderFactory::class, FlysystemLoaderFactory::class, - AssetMapperLoaderFactory::class, - ChainLoaderFactory::class + AssetMapperLoaderFactory::class, + ChainLoaderFactory::class, ], $loaders); } From a79384cfeecf55523b42bd07e10210f9a90c3106 Mon Sep 17 00:00:00 2001 From: tito10047 Date: Thu, 25 Dec 2025 07:52:16 +0100 Subject: [PATCH 05/20] updated documentation --- Resources/doc/configuration.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Resources/doc/configuration.rst b/Resources/doc/configuration.rst index 36ce79e3a..6ffbdc352 100644 --- a/Resources/doc/configuration.rst +++ b/Resources/doc/configuration.rst @@ -77,7 +77,8 @@ There are several configuration options available: * ``cache`` - default cache resolver. Default value: ``web_path`` (which means the standard web_path resolver is used) * ``data_loader`` - name of a custom data loader. Default value: ``filesystem`` - (which means the standard filesystem loader is used). + (which means the standard filesystem loader is used). Built-in loaders include: + ``filesystem``, ``chain``, ``flysystem``, ``stream``, and ``asset_mapper``. * ``twig.mode`` - Twig filter integration. ``none`` disables the twig filters, ``lazy`` enables Twig using the Twig runtime for lazy loading. The default value is ``legacy`` and enables the old Twig integration that is loaded on each request. Version 3 will drop ``legacy`` and default @@ -99,7 +100,8 @@ There are several configuration options available: * ``cache`` - default cache resolver. Default value: ``web_path`` (which means the standard web_path resolver is used) * ``data_loader`` - name of a custom data loader. Default value: ``filesystem`` - (which means the standard filesystem loader is used). + (which means the standard filesystem loader is used). Built-in loaders include: + ``filesystem``, ``chain``, ``flysystem``, ``stream``, and ``asset_mapper``. * ``post_processors`` - sets post-processors to be applied on filtered image (see Post-Processors section in the :doc:`filters chapter ` for details). * ``driver`` - one of the drivers: ``gd``, ``imagick``, ``gmagick``, ``vips``. From 2e44bff9207c2058bc803a9fc574fa7e5e906e5c Mon Sep 17 00:00:00 2001 From: tito10047 Date: Thu, 25 Dec 2025 07:52:23 +0100 Subject: [PATCH 06/20] updated documentation --- Resources/doc/data-loader/asset_mapper.rst | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 Resources/doc/data-loader/asset_mapper.rst diff --git a/Resources/doc/data-loader/asset_mapper.rst b/Resources/doc/data-loader/asset_mapper.rst new file mode 100644 index 000000000..544a77ca5 --- /dev/null +++ b/Resources/doc/data-loader/asset_mapper.rst @@ -0,0 +1,29 @@ + +.. _data-loaders-asset-mapper: + +Asset Mapper Loader +=================== + +The ``AssetMapper`` data loader allows for loading images using Symfony's AssetMapper component. + +Configuration +------------- + +To enable the Asset Mapper loader, you need to configure it in your loaders section. +A common use case is to use it in combination with the default filesystem loader using a chain loader, +especially in development environment. + +.. code-block:: yaml + + # config/packages/liip_imagine.yaml + when@dev: + liip_imagine: + loaders: + asset_mapper: + asset_mapper: ~ + chain: + chain: + loaders: [ asset_mapper, default ] + data_loader: chain + +This configuration creates an ``asset_mapper`` loader and a ``chain`` loader that first tries to find the asset via AssetMapper, and then falls back to the ``default`` loader. Finally, it sets the global ``data_loader`` to use this new ``chain`` loader. From fbf6adfe910be14417026646781f35667f00dcb6 Mon Sep 17 00:00:00 2001 From: tito10047 Date: Thu, 25 Dec 2025 07:55:50 +0100 Subject: [PATCH 07/20] removed unused code --- .../Factory/Loader/AssetMapperLoaderFactory.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php b/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php index 41608cea2..f6400e87d 100644 --- a/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php +++ b/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php @@ -44,10 +44,6 @@ public function addConfiguration(ArrayNodeDefinition $builder) { $builder ->children() -// ->scalarNode('superfoo') -// ->isRequired() -// ->cannotBeEmpty() -// ->end() ->end(); } } From ef9c9557614fb47c5ec750c502e730ac47619e5e Mon Sep 17 00:00:00 2001 From: tito10047 Date: Thu, 25 Dec 2025 07:59:23 +0100 Subject: [PATCH 08/20] removed unbused variable and readonly keyword --- Binary/Locator/AssetMapperLocator.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Binary/Locator/AssetMapperLocator.php b/Binary/Locator/AssetMapperLocator.php index 1ac3f7655..b5f83b5d8 100644 --- a/Binary/Locator/AssetMapperLocator.php +++ b/Binary/Locator/AssetMapperLocator.php @@ -17,13 +17,12 @@ class AssetMapperLocator implements LocatorInterface { public function __construct( - private readonly AssetMapperInterface $assetMapper, + private AssetMapperInterface $assetMapper, ) { } public function locate(string $path): string { - $asset = null; $path = '/'.mb_ltrim($path, '/'); foreach ($this->assetMapper->allAssets() as $assetCandidate) { if ($path === $assetCandidate->publicPath) { From 1dd541c3dcd0d3fcf490b4aa373c5971f203304b Mon Sep 17 00:00:00 2001 From: tito10047 Date: Mon, 5 Jan 2026 12:28:11 +0100 Subject: [PATCH 09/20] enhanced `AssetMapperLoaderFactory` and `AssetMapperLocator` to support caching, updated PHPDoc, and standardized configuration formatting --- Binary/Locator/AssetMapperLocator.php | 82 +- .../Loader/AssetMapperLoaderFactory.php | 57 +- Resources/config/imagine.php | 973 +++++++++--------- 3 files changed, 583 insertions(+), 529 deletions(-) diff --git a/Binary/Locator/AssetMapperLocator.php b/Binary/Locator/AssetMapperLocator.php index b5f83b5d8..838fa29e6 100644 --- a/Binary/Locator/AssetMapperLocator.php +++ b/Binary/Locator/AssetMapperLocator.php @@ -13,22 +13,74 @@ use Liip\ImagineBundle\Exception\Binary\Loader\NotLoadableException; use Symfony\Component\AssetMapper\AssetMapperInterface; +use Psr\Cache\CacheItemPoolInterface; +/** + * Class responsible for locating assets in the asset mapper. + * Implements the LocatorInterface to resolve asset paths. + * This Class should be used only in dev environment. + */ class AssetMapperLocator implements LocatorInterface { - public function __construct( - private AssetMapperInterface $assetMapper, - ) { - } - - public function locate(string $path): string - { - $path = '/'.mb_ltrim($path, '/'); - foreach ($this->assetMapper->allAssets() as $assetCandidate) { - if ($path === $assetCandidate->publicPath) { - return $assetCandidate->sourcePath; - } - } - throw new NotLoadableException(\sprintf('Asset with public path "%s" not found.', $path)); - } + + private $assetMapper; + private $cacheMapCache = null; + + public function __construct( + AssetMapperInterface $assetMapper, + ?CacheItemPoolInterface $cacheMapCache = null + ) { + $this->cacheMapCache = $cacheMapCache; + $this->assetMapper = $assetMapper; + } + + /** + * Locates an asset by its public path. + * + * This method attempts to retrieve an asset using its public path. It first + * checks for a cached version of the asset, and if none is found, it iterates + * through all available assets to find a match. If no matching asset is located, + * an exception is thrown. + * + * Inspired by Symfony's AssetMapper component @see https://github.com/symfony/asset-mapper/blob/7.3/AssetMapperDevServerSubscriber.php#L179. + * + * @param string $path The public path of the asset to locate. + * + * @return string The located asset. + * + * @throws NotLoadableException If no asset with the specified public path is found. + */ + public function locate(string $path): string + { + $pathInfo = '/'.ltrim($path, '/'); + $cachedAsset = null; + if (null !== $this->cacheMapCache) { + $cachedAsset = $this->cacheMapCache->getItem(hash('xxh128', $pathInfo)); + $asset = $cachedAsset->isHit() ? $this->assetMapper->getAsset($cachedAsset->get()) : null; + + if (null !== $asset && $asset->publicPath === $pathInfo) { + return $asset; + } + } + + // we did not find a match + $asset = null; + foreach ($this->assetMapper->allAssets() as $assetCandidate) { + if ($pathInfo === $assetCandidate->publicPath) { + $asset = $assetCandidate; + break; + } + } + + if (null === $asset) { + throw new NotLoadableException(\sprintf('Asset with public path "%s" not found.', $path)); + } + + if (null !== $cachedAsset) { + $cachedAsset->set($asset->logicalPath); + $this->cacheMapCache->save($cachedAsset); + } + + return $asset; + } } diff --git a/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php b/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php index f6400e87d..e8b1db7ab 100644 --- a/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php +++ b/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php @@ -18,32 +18,33 @@ class AssetMapperLoaderFactory extends AbstractLoaderFactory { - public function create(ContainerBuilder $container, $loaderName, array $config) - { - $locatorDefinition = new ChildDefinition('liip_imagine.binary.locator.asset_mapper'); - $locatorDefinition->replaceArgument(0, new Reference('asset_mapper')); - - $definition = $this->getChildLoaderDefinition('filesystem'); - - if ($container->hasDefinition('liip_imagine.mime_types')) { - $mimeTypes = $container->getDefinition('liip_imagine.mime_types'); - $definition->replaceArgument(0, $mimeTypes); - $definition->replaceArgument(1, $mimeTypes); - } - $definition->replaceArgument(2, $locatorDefinition); - - return $this->setTaggedLoaderDefinition($loaderName, $definition, $container); - } - - public function getName() - { - return 'asset_mapper'; - } - - public function addConfiguration(ArrayNodeDefinition $builder) - { - $builder - ->children() - ->end(); - } + public function create(ContainerBuilder $container, $loaderName, array $config) + { + $locatorDefinition = new ChildDefinition('liip_imagine.binary.locator.asset_mapper'); + $locatorDefinition->replaceArgument(0, new Reference('asset_mapper')); + $locatorDefinition->replaceArgument(1, new Reference('cache.asset_mapper')); + + $definition = $this->getChildLoaderDefinition('filesystem'); + + if ($container->hasDefinition('liip_imagine.mime_types')) { + $mimeTypes = $container->getDefinition('liip_imagine.mime_types'); + $definition->replaceArgument(0, $mimeTypes); + $definition->replaceArgument(1, $mimeTypes); + } + $definition->replaceArgument(2, $locatorDefinition); + + return $this->setTaggedLoaderDefinition($loaderName, $definition, $container); + } + + public function getName() + { + return 'asset_mapper'; + } + + public function addConfiguration(ArrayNodeDefinition $builder) + { + $builder + ->children() + ->end(); + } } diff --git a/Resources/config/imagine.php b/Resources/config/imagine.php index 02e816d51..067eb8f65 100644 --- a/Resources/config/imagine.php +++ b/Resources/config/imagine.php @@ -89,495 +89,496 @@ use Liip\ImagineBundle\Service\FilterService; return static function (ContainerConfigurator $container) { - $services = $container->services(); - $parameters = $container->parameters(); + $services = $container->services(); + $parameters = $container->parameters(); - // JpegOptim parameters - $parameters->set('liip_imagine.jpegoptim.binary', '/usr/bin/jpegoptim'); - $parameters->set('liip_imagine.jpegoptim.stripAll', true); - $parameters->set('liip_imagine.jpegoptim.max', null); - $parameters->set('liip_imagine.jpegoptim.progressive', true); - $parameters->set('liip_imagine.jpegoptim.tempDir', null); + // JpegOptim parameters + $parameters->set('liip_imagine.jpegoptim.binary', '/usr/bin/jpegoptim'); + $parameters->set('liip_imagine.jpegoptim.stripAll', true); + $parameters->set('liip_imagine.jpegoptim.max', null); + $parameters->set('liip_imagine.jpegoptim.progressive', true); + $parameters->set('liip_imagine.jpegoptim.tempDir', null); - // OptiPng parameters - $parameters->set('liip_imagine.optipng.binary', '/usr/bin/optipng'); - $parameters->set('liip_imagine.optipng.level', 7); - $parameters->set('liip_imagine.optipng.stripAll', true); - $parameters->set('liip_imagine.optipng.tempDir', null); + // OptiPng parameters + $parameters->set('liip_imagine.optipng.binary', '/usr/bin/optipng'); + $parameters->set('liip_imagine.optipng.level', 7); + $parameters->set('liip_imagine.optipng.stripAll', true); + $parameters->set('liip_imagine.optipng.tempDir', null); - // Pngquant parameters - $parameters->set('liip_imagine.pngquant.binary', '/usr/bin/pngquant'); + // Pngquant parameters + $parameters->set('liip_imagine.pngquant.binary', '/usr/bin/pngquant'); - // MozJpeg parameters - $parameters->set('liip_imagine.mozjpeg.binary', '/opt/mozjpeg/bin/cjpeg'); + // MozJpeg parameters + $parameters->set('liip_imagine.mozjpeg.binary', '/opt/mozjpeg/bin/cjpeg'); - // cwebp parameters - $parameters->set('liip_imagine.cwebp.binary', '/usr/bin/cwebp'); - $parameters->set('liip_imagine.cwebp.tempDir', null); - $parameters->set('liip_imagine.cwebp.q', 75); - $parameters->set('liip_imagine.cwebp.alphaQ', 100); - $parameters->set('liip_imagine.cwebp.m', 4); - $parameters->set('liip_imagine.cwebp.alphaFilter', 'fast'); - $parameters->set('liip_imagine.cwebp.alphaMethod', 1); - $parameters->set('liip_imagine.cwebp.exact', false); - $parameters->set('liip_imagine.cwebp.metadata', ['none']); - - // Factory services - $services->set('liip_imagine.factory.config.filter.argument.point', PointFactory::class); - - $services->set('liip_imagine.factory.config.filter.argument.size', SizeFactory::class); - - $services->set('liip_imagine.factory.config.stack', StackFactory::class); - - $services->set('liip_imagine.factory.config.filter.auto_rotate', AutoRotateFactory::class); - - $services->set('liip_imagine.factory.config.filter.background', BackgroundFactory::class) - ->args([service('liip_imagine.factory.config.filter.argument.size')]); - - $services->set('liip_imagine.factory.config.filter.crop', CropFactory::class) - ->args([ - service('liip_imagine.factory.config.filter.argument.size'), - service('liip_imagine.factory.config.filter.argument.point'), - ]); - - $services->set('liip_imagine.factory.config.filter.downscale', DownscaleFactory::class) - ->args([service('liip_imagine.factory.config.filter.argument.size')]); - - $services->set('liip_imagine.factory.config.filter.flip', FlipFactory::class); - - $services->set('liip_imagine.factory.config.filter.grayscale', GrayscaleFactory::class); - - $services->set('liip_imagine.factory.config.filter.interlace', InterlaceFactory::class); - - $services->set('liip_imagine.factory.config.filter.paste', PasteFactory::class) - ->args([service('liip_imagine.factory.config.filter.argument.point')]); - - $services->set('liip_imagine.factory.config.filter.relative_resize', RelativeResizeFactory::class); - - $services->set('liip_imagine.factory.config.filter.resize', ResizeFactory::class) - ->args([service('liip_imagine.factory.config.filter.argument.size')]); - - $services->set('liip_imagine.factory.config.filter.rotate', RotateFactory::class); - - $services->set('liip_imagine.factory.config.filter.scale', ScaleFactory::class) - ->args([service('liip_imagine.factory.config.filter.argument.size')]); - - $services->set('liip_imagine.factory.config.filter.strip', StripFactory::class); - - $services->set('liip_imagine.factory.config.filter.thumbnail', ThumbnailFactory::class) - ->args([service('liip_imagine.factory.config.filter.argument.size')]); - - $services->set('liip_imagine.factory.config.filter.upscale', UpscaleFactory::class) - ->args([service('liip_imagine.factory.config.filter.argument.size')]); - - $services->set('liip_imagine.factory.config.filter.watermark', WatermarkFactory::class); - - // Config services - $services->set('liip_imagine.config.filter_factory_collection', FilterFactoryCollection::class) - ->args([ - service('liip_imagine.factory.config.filter.auto_rotate'), - service('liip_imagine.factory.config.filter.background'), - service('liip_imagine.factory.config.filter.crop'), - service('liip_imagine.factory.config.filter.downscale'), - service('liip_imagine.factory.config.filter.flip'), - service('liip_imagine.factory.config.filter.grayscale'), - service('liip_imagine.factory.config.filter.interlace'), - service('liip_imagine.factory.config.filter.paste'), - service('liip_imagine.factory.config.filter.relative_resize'), - service('liip_imagine.factory.config.filter.resize'), - service('liip_imagine.factory.config.filter.rotate'), - service('liip_imagine.factory.config.filter.scale'), - service('liip_imagine.factory.config.filter.strip'), - service('liip_imagine.factory.config.filter.thumbnail'), - service('liip_imagine.factory.config.filter.upscale'), - service('liip_imagine.factory.config.filter.watermark'), - ]); - - $services->alias(FilterFactoryCollection::class, 'liip_imagine.config.filter_factory_collection'); - - $services->set('liip_imagine.config.stack_builder', StackBuilder::class) - ->args([ - service('liip_imagine.factory.config.stack'), - service('liip_imagine.config.filter_factory_collection'), - ]); - - $services->alias(StackBuilder::class, 'liip_imagine.config.stack_builder'); - - $services->set('liip_imagine.config.stack_collection', StackCollection::class) - ->public() - ->args([ - service('liip_imagine.config.stack_builder'), - '%liip_imagine.filter_sets%', - ]); - - $services->alias(StackCollection::class, 'liip_imagine.config.stack_collection'); - - // Utility services - $services->set('liip_imagine.filter.manager', FilterManager::class) - ->public() - ->args([ - service('liip_imagine.filter.configuration'), - service('liip_imagine'), - service('liip_imagine.binary.mime_type_guesser'), - ]); - - $services->alias(FilterManager::class, 'liip_imagine.filter.manager'); - - $services->set('liip_imagine.data.manager', DataManager::class) - ->public() - ->args([ - service('liip_imagine.binary.mime_type_guesser'), - service('liip_imagine.extension_guesser'), - service('liip_imagine.filter.configuration'), - '%liip_imagine.binary.loader.default%', - '%liip_imagine.default_image%', - ]); - - $services->alias(DataManager::class, 'liip_imagine.data.manager'); - - $services->set('liip_imagine.cache.manager', CacheManager::class) - ->public() - ->args([ - service('liip_imagine.filter.configuration'), - service('router'), - service('liip_imagine.cache.signer'), - service('event_dispatcher'), - '%liip_imagine.cache.resolver.default%', - '%liip_imagine.webp.generate%', - ]); - - $services->alias(CacheManager::class, 'liip_imagine.cache.manager'); - - $services->set('liip_imagine.filter.configuration', FilterConfiguration::class) - ->args(['%liip_imagine.filter_sets%']); - - $services->set('liip_imagine.service.filter', FilterService::class) - ->args([ - service('liip_imagine.data.manager'), - service('liip_imagine.filter.manager'), - service('liip_imagine.cache.manager'), - '%liip_imagine.webp.generate%', - '%liip_imagine.webp.options%', - service('logger')->ignoreOnInvalid(), - ]); - - $services->alias(FilterService::class, 'liip_imagine.service.filter'); - - // Config - $services->set('liip_imagine.controller.config', ControllerConfig::class) - ->private() - ->args(['']); - - // Controller - $services->set(ImagineController::class) - ->public() - ->args([ - service('liip_imagine.service.filter'), - service('liip_imagine.data.manager'), - service('liip_imagine.cache.signer'), - service('liip_imagine.controller.config'), - ]); - - $services->alias('liip_imagine.controller', ImagineController::class) - ->public(); - - $services->set('liip_imagine.meta_data.reader', ExifMetadataReader::class) - ->private(); - - // ImagineInterface instances - $services->alias('liip_imagine', 'liip_imagine.gd'); - - $services->alias(ImagineInterface::class, 'liip_imagine'); - - $services->set('liip_imagine.gd', Imagine::class) - ->private() - ->call('setMetadataReader', [service('liip_imagine.meta_data.reader')]); - - $services->set('liip_imagine.imagick', \Imagine\Imagick\Imagine::class) - ->private() - ->call('setMetadataReader', [service('liip_imagine.meta_data.reader')]); - - $services->set('liip_imagine.gmagick', \Imagine\Gmagick\Imagine::class) - ->private() - ->call('setMetadataReader', [service('liip_imagine.meta_data.reader')]); - - // Filter loaders - $services->set('liip_imagine.filter.loader.relative_resize', RelativeResizeFilterLoader::class) - ->tag('liip_imagine.filter.loader', ['loader' => 'relative_resize']); - - $services->set('liip_imagine.filter.loader.resize', ResizeFilterLoader::class) - ->tag('liip_imagine.filter.loader', ['loader' => 'resize']); - - $services->set('liip_imagine.filter.loader.thumbnail', ThumbnailFilterLoader::class) - ->tag('liip_imagine.filter.loader', ['loader' => 'thumbnail']); - - $services->set('liip_imagine.filter.loader.crop', CropFilterLoader::class) - ->tag('liip_imagine.filter.loader', ['loader' => 'crop']); - - $services->set('liip_imagine.filter.loader.grayscale', GrayscaleFilterLoader::class) - ->public() - ->tag('liip_imagine.filter.loader', ['loader' => 'grayscale']); - - $services->set('liip_imagine.filter.loader.paste_image', PasteFilterLoader::class) - ->args([ - service('liip_imagine'), - '%kernel.project_dir%', - ]) - ->tag('liip_imagine.filter.loader', ['loader' => 'paste_image']); - - // not officially deprecated because still injected and appears "used" - $services->set('liip_imagine.filter.loader.paste', PasteFilterLoader::class) - ->args([ - service('liip_imagine'), - '%kernel.root_dir%', - ]) - ->tag('liip_imagine.filter.loader', ['loader' => 'paste']); - - // not officially deprecated because still injected and appears "used" - $services->set('liip_imagine.filter.loader.watermark', WatermarkFilterLoader::class) - ->args([ - service('liip_imagine'), - '%kernel.root_dir%', - ]) - ->tag('liip_imagine.filter.loader', ['loader' => 'watermark']); - - $services->set('liip_imagine.filter.loader.watermark_image', WatermarkFilterLoader::class) - ->args([ - service('liip_imagine'), - '%kernel.project_dir%', - ]) - ->tag('liip_imagine.filter.loader', ['loader' => 'watermark_image']); - - $services->set('liip_imagine.filter.loader.background', BackgroundFilterLoader::class) - ->args([service('liip_imagine')]) - ->tag('liip_imagine.filter.loader', ['loader' => 'background']); - - $services->set('liip_imagine.filter.loader.strip', StripFilterLoader::class) - ->tag('liip_imagine.filter.loader', ['loader' => 'strip']); - - $services->set('liip_imagine.filter.loader.scale', ScaleFilterLoader::class) - ->tag('liip_imagine.filter.loader', ['loader' => 'scale']); - - $services->set('liip_imagine.filter.loader.upscale', UpscaleFilterLoader::class) - ->tag('liip_imagine.filter.loader', ['loader' => 'upscale']); - - $services->set('liip_imagine.filter.loader.downscale', DownscaleFilterLoader::class) - ->public() - ->tag('liip_imagine.filter.loader', ['loader' => 'downscale']); - - $services->set('liip_imagine.filter.loader.auto_rotate', AutoRotateFilterLoader::class) - ->tag('liip_imagine.filter.loader', ['loader' => 'auto_rotate']); - - $services->set('liip_imagine.filter.loader.rotate', RotateFilterLoader::class) - ->public() - ->tag('liip_imagine.filter.loader', ['loader' => 'rotate']); - - $services->set('liip_imagine.filter.loader.flip', FlipFilterLoader::class) - ->public() - ->tag('liip_imagine.filter.loader', ['loader' => 'flip']); - - $services->set('liip_imagine.filter.loader.interlace', InterlaceFilterLoader::class) - ->public() - ->tag('liip_imagine.filter.loader', ['loader' => 'interlace']); - - $services->set('liip_imagine.filter.loader.resample', ResampleFilterLoader::class) - ->public() - ->args([service('liip_imagine')]) - ->tag('liip_imagine.filter.loader', ['loader' => 'resample']); - - $services->set('liip_imagine.filter.loader.fixed', FixedFilterLoader::class) - ->public() - ->tag('liip_imagine.filter.loader', ['loader' => 'fixed']); - - // Data loaders - $services->set('liip_imagine.binary.loader.prototype.filesystem', FileSystemLoader::class) - ->args([ - service('liip_imagine.mime_type_guesser'), - service('liip_imagine.extension_guesser'), - '', // will be injected by FileSystemLoaderFactory - ]); - - $services->set('liip_imagine.binary.loader.prototype.stream', StreamLoader::class) - ->args([ - '', // will be injected by StreamLoaderFactory - '', // will be injected by StreamLoaderFactory - ]); - - $services->set('liip_imagine.binary.loader.prototype.flysystem', FlysystemLoader::class) - ->abstract() - ->args([ - service('liip_imagine.extension_guesser'), - '', // will be injected by FlysystemLoaderFactory - ]); - - $services->set('liip_imagine.binary.loader.prototype.flysystem2', FlysystemV2Loader::class) - ->abstract() - ->args([ - service('liip_imagine.extension_guesser'), - '', // will be injected by FlysystemV2LoaderFactory - ]); - - $services->set('liip_imagine.binary.loader.prototype.chain', ChainLoader::class) - ->abstract() - ->args(['']); // will be injected by ChainLoaderFactory - - // Data loader locators - $services->set('liip_imagine.binary.locator.filesystem', FileSystemLocator::class) - ->share(false) - ->private() - ->args([ - '', // will be injected by FilesystemLoaderFactory - '', // will be injected by FilesystemLoaderFactory - ]) - ->tag('liip_imagine.binary.locator', ['shared' => false]); - $services->set('liip_imagine.binary.locator.asset_mapper', AssetMapperLocator::class) - ->share(true) - ->public() - ->args([ - '', - ]) - ->tag('liip_imagine.binary.locator', ['shared' => false]); - - $services->set('liip_imagine.binary.locator.filesystem_insecure', FileSystemInsecureLocator::class) - ->share(false) - ->private() - ->args([ - '', // will be injected by FilesystemLoaderFactory - '', // will be injected by FilesystemLoaderFactory - ]) - ->tag('liip_imagine.binary.locator', ['shared' => false]); - - // Cache resolver - $services->set('liip_imagine.cache.resolver.prototype.web_path', WebPathResolver::class) - ->public() - ->abstract() - ->args([ - service('filesystem'), - service('router.request_context'), - '', // will be injected by WebPathResolverFactory - '', // will be injected by WebPathResolverFactory - ]); - - $services->set('liip_imagine.cache.resolver.prototype.aws_s3', AwsS3Resolver::class) - ->public() - ->abstract() - ->args([ - '', // will be injected by AwsS3ResolverFactory - '', // will be injected by AwsS3ResolverFactory - '', // will be injected by AwsS3ResolverFactory - '', // will be injected by AwsS3ResolverFactory - '', // will be injected by AwsS3ResolverFactory - ]); - - $services->set('liip_imagine.cache.resolver.prototype.cache', CacheResolver::class) - ->public() - ->abstract() - ->args([ - // args will be injected by a ResolverFactory - '', - '', - ]); - - $services->set('liip_imagine.cache.resolver.prototype.flysystem', FlysystemResolver::class) - ->public() - ->abstract() - ->args([ - '', // will be injected by a ResolverFactory - service('router.request_context'), - '', // will be injected by a ResolverFactory - '', // will be injected by a ResolverFactory - '', // will be injected by a ResolverFactory - ]); - - $services->set('liip_imagine.cache.resolver.prototype.flysystem2', FlysystemV2Resolver::class) - ->public() - ->abstract() - ->args([ - '', - service('router.request_context'), - '', - '', - '', - ]); - - $services->set('liip_imagine.cache.resolver.prototype.proxy', ProxyResolver::class) - ->public() - ->abstract() - ->args([ - '', // will be injected by AwsS3ResolverFactory - '', // will be injected by AwsS3ResolverFactory - ]); - - $services->set('liip_imagine.cache.resolver.prototype.psr_cache', PsrCacheResolver::class) - ->public() - ->abstract() - ->args([ - '', // will be injected by a ResolverFactory - '', // will be injected by a ResolverFactory - ]); - - $services->set('liip_imagine.cache.resolver.no_cache_web_path', NoCacheWebPathResolver::class) - ->public() - ->args([service('router.request_context')]) - ->tag('liip_imagine.cache.resolver', ['resolver' => 'no_cache']); - - // Form types - $services->set('liip_imagine.form.type.image', ImageType::class) - ->tag('form.type', ['alias' => 'liip_imagine_image']); - - // Guessers - $services->set('liip_imagine.mime_type_guesser', 'Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface') - ->factory(['Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser', 'getInstance']); - - $services->set('liip_imagine.extension_guesser', 'Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesserInterface') - ->factory(['Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser', 'getInstance']); - - $services->set('liip_imagine.binary.mime_type_guesser', SimpleMimeTypeGuesser::class) - ->args([service('liip_imagine.mime_type_guesser')]); - - $services->set('liip_imagine.cache.signer', Signer::class) - ->public() - ->args(['%kernel.secret%']); - - // Post processors - $services->set('liip_imagine.filter.post_processor.jpegoptim', JpegOptimPostProcessor::class) - ->args([ - '%liip_imagine.jpegoptim.binary%', - '%liip_imagine.jpegoptim.stripAll%', - '%liip_imagine.jpegoptim.max%', - '%liip_imagine.jpegoptim.progressive%', - '%liip_imagine.jpegoptim.tempDir%', - ]) - ->tag('liip_imagine.filter.post_processor', ['post_processor' => 'jpegoptim']); - - $services->set('liip_imagine.filter.post_processor.optipng', OptiPngPostProcessor::class) - ->args([ - '%liip_imagine.optipng.binary%', - '%liip_imagine.optipng.level%', - '%liip_imagine.optipng.stripAll%', - '%liip_imagine.optipng.tempDir%', - ]) - ->tag('liip_imagine.filter.post_processor', ['post_processor' => 'optipng']); - - $services->set('liip_imagine.filter.post_processor.pngquant', PngquantPostProcessor::class) - ->args(['%liip_imagine.pngquant.binary%']) - ->tag('liip_imagine.filter.post_processor', ['post_processor' => 'pngquant']); - - $services->set('liip_imagine.filter.post_processor.mozjpeg', MozJpegPostProcessor::class) - ->args(['%liip_imagine.mozjpeg.binary%']) - ->tag('liip_imagine.filter.post_processor', ['post_processor' => 'mozjpeg']); - - $services->set('liip_imagine.filter.post_processor.cwebp', CwebpPostProcessor::class) - ->args([ - '%liip_imagine.cwebp.binary%', - '%liip_imagine.cwebp.tempDir%', - '%liip_imagine.cwebp.q%', - '%liip_imagine.cwebp.alphaQ%', - '%liip_imagine.cwebp.m%', - '%liip_imagine.cwebp.alphaFilter%', - '%liip_imagine.cwebp.alphaMethod%', - '%liip_imagine.cwebp.exact%', - '%liip_imagine.cwebp.metadata%', - ]) - ->tag('liip_imagine.filter.post_processor', ['post_processor' => 'cwebp']); + // cwebp parameters + $parameters->set('liip_imagine.cwebp.binary', '/usr/bin/cwebp'); + $parameters->set('liip_imagine.cwebp.tempDir', null); + $parameters->set('liip_imagine.cwebp.q', 75); + $parameters->set('liip_imagine.cwebp.alphaQ', 100); + $parameters->set('liip_imagine.cwebp.m', 4); + $parameters->set('liip_imagine.cwebp.alphaFilter', 'fast'); + $parameters->set('liip_imagine.cwebp.alphaMethod', 1); + $parameters->set('liip_imagine.cwebp.exact', false); + $parameters->set('liip_imagine.cwebp.metadata', ['none']); + + // Factory services + $services->set('liip_imagine.factory.config.filter.argument.point', PointFactory::class); + + $services->set('liip_imagine.factory.config.filter.argument.size', SizeFactory::class); + + $services->set('liip_imagine.factory.config.stack', StackFactory::class); + + $services->set('liip_imagine.factory.config.filter.auto_rotate', AutoRotateFactory::class); + + $services->set('liip_imagine.factory.config.filter.background', BackgroundFactory::class) + ->args([service('liip_imagine.factory.config.filter.argument.size')]); + + $services->set('liip_imagine.factory.config.filter.crop', CropFactory::class) + ->args([ + service('liip_imagine.factory.config.filter.argument.size'), + service('liip_imagine.factory.config.filter.argument.point'), + ]); + + $services->set('liip_imagine.factory.config.filter.downscale', DownscaleFactory::class) + ->args([service('liip_imagine.factory.config.filter.argument.size')]); + + $services->set('liip_imagine.factory.config.filter.flip', FlipFactory::class); + + $services->set('liip_imagine.factory.config.filter.grayscale', GrayscaleFactory::class); + + $services->set('liip_imagine.factory.config.filter.interlace', InterlaceFactory::class); + + $services->set('liip_imagine.factory.config.filter.paste', PasteFactory::class) + ->args([service('liip_imagine.factory.config.filter.argument.point')]); + + $services->set('liip_imagine.factory.config.filter.relative_resize', RelativeResizeFactory::class); + + $services->set('liip_imagine.factory.config.filter.resize', ResizeFactory::class) + ->args([service('liip_imagine.factory.config.filter.argument.size')]); + + $services->set('liip_imagine.factory.config.filter.rotate', RotateFactory::class); + + $services->set('liip_imagine.factory.config.filter.scale', ScaleFactory::class) + ->args([service('liip_imagine.factory.config.filter.argument.size')]); + + $services->set('liip_imagine.factory.config.filter.strip', StripFactory::class); + + $services->set('liip_imagine.factory.config.filter.thumbnail', ThumbnailFactory::class) + ->args([service('liip_imagine.factory.config.filter.argument.size')]); + + $services->set('liip_imagine.factory.config.filter.upscale', UpscaleFactory::class) + ->args([service('liip_imagine.factory.config.filter.argument.size')]); + + $services->set('liip_imagine.factory.config.filter.watermark', WatermarkFactory::class); + + // Config services + $services->set('liip_imagine.config.filter_factory_collection', FilterFactoryCollection::class) + ->args([ + service('liip_imagine.factory.config.filter.auto_rotate'), + service('liip_imagine.factory.config.filter.background'), + service('liip_imagine.factory.config.filter.crop'), + service('liip_imagine.factory.config.filter.downscale'), + service('liip_imagine.factory.config.filter.flip'), + service('liip_imagine.factory.config.filter.grayscale'), + service('liip_imagine.factory.config.filter.interlace'), + service('liip_imagine.factory.config.filter.paste'), + service('liip_imagine.factory.config.filter.relative_resize'), + service('liip_imagine.factory.config.filter.resize'), + service('liip_imagine.factory.config.filter.rotate'), + service('liip_imagine.factory.config.filter.scale'), + service('liip_imagine.factory.config.filter.strip'), + service('liip_imagine.factory.config.filter.thumbnail'), + service('liip_imagine.factory.config.filter.upscale'), + service('liip_imagine.factory.config.filter.watermark'), + ]); + + $services->alias(FilterFactoryCollection::class, 'liip_imagine.config.filter_factory_collection'); + + $services->set('liip_imagine.config.stack_builder', StackBuilder::class) + ->args([ + service('liip_imagine.factory.config.stack'), + service('liip_imagine.config.filter_factory_collection'), + ]); + + $services->alias(StackBuilder::class, 'liip_imagine.config.stack_builder'); + + $services->set('liip_imagine.config.stack_collection', StackCollection::class) + ->public() + ->args([ + service('liip_imagine.config.stack_builder'), + '%liip_imagine.filter_sets%', + ]); + + $services->alias(StackCollection::class, 'liip_imagine.config.stack_collection'); + + // Utility services + $services->set('liip_imagine.filter.manager', FilterManager::class) + ->public() + ->args([ + service('liip_imagine.filter.configuration'), + service('liip_imagine'), + service('liip_imagine.binary.mime_type_guesser'), + ]); + + $services->alias(FilterManager::class, 'liip_imagine.filter.manager'); + + $services->set('liip_imagine.data.manager', DataManager::class) + ->public() + ->args([ + service('liip_imagine.binary.mime_type_guesser'), + service('liip_imagine.extension_guesser'), + service('liip_imagine.filter.configuration'), + '%liip_imagine.binary.loader.default%', + '%liip_imagine.default_image%', + ]); + + $services->alias(DataManager::class, 'liip_imagine.data.manager'); + + $services->set('liip_imagine.cache.manager', CacheManager::class) + ->public() + ->args([ + service('liip_imagine.filter.configuration'), + service('router'), + service('liip_imagine.cache.signer'), + service('event_dispatcher'), + '%liip_imagine.cache.resolver.default%', + '%liip_imagine.webp.generate%', + ]); + + $services->alias(CacheManager::class, 'liip_imagine.cache.manager'); + + $services->set('liip_imagine.filter.configuration', FilterConfiguration::class) + ->args(['%liip_imagine.filter_sets%']); + + $services->set('liip_imagine.service.filter', FilterService::class) + ->args([ + service('liip_imagine.data.manager'), + service('liip_imagine.filter.manager'), + service('liip_imagine.cache.manager'), + '%liip_imagine.webp.generate%', + '%liip_imagine.webp.options%', + service('logger')->ignoreOnInvalid(), + ]); + + $services->alias(FilterService::class, 'liip_imagine.service.filter'); + + // Config + $services->set('liip_imagine.controller.config', ControllerConfig::class) + ->private() + ->args(['']); + + // Controller + $services->set(ImagineController::class) + ->public() + ->args([ + service('liip_imagine.service.filter'), + service('liip_imagine.data.manager'), + service('liip_imagine.cache.signer'), + service('liip_imagine.controller.config'), + ]); + + $services->alias('liip_imagine.controller', ImagineController::class) + ->public(); + + $services->set('liip_imagine.meta_data.reader', ExifMetadataReader::class) + ->private(); + + // ImagineInterface instances + $services->alias('liip_imagine', 'liip_imagine.gd'); + + $services->alias(ImagineInterface::class, 'liip_imagine'); + + $services->set('liip_imagine.gd', Imagine::class) + ->private() + ->call('setMetadataReader', [service('liip_imagine.meta_data.reader')]); + + $services->set('liip_imagine.imagick', \Imagine\Imagick\Imagine::class) + ->private() + ->call('setMetadataReader', [service('liip_imagine.meta_data.reader')]); + + $services->set('liip_imagine.gmagick', \Imagine\Gmagick\Imagine::class) + ->private() + ->call('setMetadataReader', [service('liip_imagine.meta_data.reader')]); + + // Filter loaders + $services->set('liip_imagine.filter.loader.relative_resize', RelativeResizeFilterLoader::class) + ->tag('liip_imagine.filter.loader', ['loader' => 'relative_resize']); + + $services->set('liip_imagine.filter.loader.resize', ResizeFilterLoader::class) + ->tag('liip_imagine.filter.loader', ['loader' => 'resize']); + + $services->set('liip_imagine.filter.loader.thumbnail', ThumbnailFilterLoader::class) + ->tag('liip_imagine.filter.loader', ['loader' => 'thumbnail']); + + $services->set('liip_imagine.filter.loader.crop', CropFilterLoader::class) + ->tag('liip_imagine.filter.loader', ['loader' => 'crop']); + + $services->set('liip_imagine.filter.loader.grayscale', GrayscaleFilterLoader::class) + ->public() + ->tag('liip_imagine.filter.loader', ['loader' => 'grayscale']); + + $services->set('liip_imagine.filter.loader.paste_image', PasteFilterLoader::class) + ->args([ + service('liip_imagine'), + '%kernel.project_dir%', + ]) + ->tag('liip_imagine.filter.loader', ['loader' => 'paste_image']); + + // not officially deprecated because still injected and appears "used" + $services->set('liip_imagine.filter.loader.paste', PasteFilterLoader::class) + ->args([ + service('liip_imagine'), + '%kernel.root_dir%', + ]) + ->tag('liip_imagine.filter.loader', ['loader' => 'paste']); + + // not officially deprecated because still injected and appears "used" + $services->set('liip_imagine.filter.loader.watermark', WatermarkFilterLoader::class) + ->args([ + service('liip_imagine'), + '%kernel.root_dir%', + ]) + ->tag('liip_imagine.filter.loader', ['loader' => 'watermark']); + + $services->set('liip_imagine.filter.loader.watermark_image', WatermarkFilterLoader::class) + ->args([ + service('liip_imagine'), + '%kernel.project_dir%', + ]) + ->tag('liip_imagine.filter.loader', ['loader' => 'watermark_image']); + + $services->set('liip_imagine.filter.loader.background', BackgroundFilterLoader::class) + ->args([service('liip_imagine')]) + ->tag('liip_imagine.filter.loader', ['loader' => 'background']); + + $services->set('liip_imagine.filter.loader.strip', StripFilterLoader::class) + ->tag('liip_imagine.filter.loader', ['loader' => 'strip']); + + $services->set('liip_imagine.filter.loader.scale', ScaleFilterLoader::class) + ->tag('liip_imagine.filter.loader', ['loader' => 'scale']); + + $services->set('liip_imagine.filter.loader.upscale', UpscaleFilterLoader::class) + ->tag('liip_imagine.filter.loader', ['loader' => 'upscale']); + + $services->set('liip_imagine.filter.loader.downscale', DownscaleFilterLoader::class) + ->public() + ->tag('liip_imagine.filter.loader', ['loader' => 'downscale']); + + $services->set('liip_imagine.filter.loader.auto_rotate', AutoRotateFilterLoader::class) + ->tag('liip_imagine.filter.loader', ['loader' => 'auto_rotate']); + + $services->set('liip_imagine.filter.loader.rotate', RotateFilterLoader::class) + ->public() + ->tag('liip_imagine.filter.loader', ['loader' => 'rotate']); + + $services->set('liip_imagine.filter.loader.flip', FlipFilterLoader::class) + ->public() + ->tag('liip_imagine.filter.loader', ['loader' => 'flip']); + + $services->set('liip_imagine.filter.loader.interlace', InterlaceFilterLoader::class) + ->public() + ->tag('liip_imagine.filter.loader', ['loader' => 'interlace']); + + $services->set('liip_imagine.filter.loader.resample', ResampleFilterLoader::class) + ->public() + ->args([service('liip_imagine')]) + ->tag('liip_imagine.filter.loader', ['loader' => 'resample']); + + $services->set('liip_imagine.filter.loader.fixed', FixedFilterLoader::class) + ->public() + ->tag('liip_imagine.filter.loader', ['loader' => 'fixed']); + + // Data loaders + $services->set('liip_imagine.binary.loader.prototype.filesystem', FileSystemLoader::class) + ->args([ + service('liip_imagine.mime_type_guesser'), + service('liip_imagine.extension_guesser'), + '', // will be injected by FileSystemLoaderFactory + ]); + + $services->set('liip_imagine.binary.loader.prototype.stream', StreamLoader::class) + ->args([ + '', // will be injected by StreamLoaderFactory + '', // will be injected by StreamLoaderFactory + ]); + + $services->set('liip_imagine.binary.loader.prototype.flysystem', FlysystemLoader::class) + ->abstract() + ->args([ + service('liip_imagine.extension_guesser'), + '', // will be injected by FlysystemLoaderFactory + ]); + + $services->set('liip_imagine.binary.loader.prototype.flysystem2', FlysystemV2Loader::class) + ->abstract() + ->args([ + service('liip_imagine.extension_guesser'), + '', // will be injected by FlysystemV2LoaderFactory + ]); + + $services->set('liip_imagine.binary.loader.prototype.chain', ChainLoader::class) + ->abstract() + ->args(['']); // will be injected by ChainLoaderFactory + + // Data loader locators + $services->set('liip_imagine.binary.locator.filesystem', FileSystemLocator::class) + ->share(false) + ->private() + ->args([ + '', // will be injected by FilesystemLoaderFactory + '', // will be injected by FilesystemLoaderFactory + ]) + ->tag('liip_imagine.binary.locator', ['shared' => false]); + $services->set('liip_imagine.binary.locator.asset_mapper', AssetMapperLocator::class) + ->share(true) + ->public() + ->args([ + '', // will be injected by AssetMapperLoaderFactory + '', // will be injected by AssetMapperLoaderFactory + ]) + ->tag('liip_imagine.binary.locator', ['shared' => false]); + + $services->set('liip_imagine.binary.locator.filesystem_insecure', FileSystemInsecureLocator::class) + ->share(false) + ->private() + ->args([ + '', // will be injected by FilesystemLoaderFactory + '', // will be injected by FilesystemLoaderFactory + ]) + ->tag('liip_imagine.binary.locator', ['shared' => false]); + + // Cache resolver + $services->set('liip_imagine.cache.resolver.prototype.web_path', WebPathResolver::class) + ->public() + ->abstract() + ->args([ + service('filesystem'), + service('router.request_context'), + '', // will be injected by WebPathResolverFactory + '', // will be injected by WebPathResolverFactory + ]); + + $services->set('liip_imagine.cache.resolver.prototype.aws_s3', AwsS3Resolver::class) + ->public() + ->abstract() + ->args([ + '', // will be injected by AwsS3ResolverFactory + '', // will be injected by AwsS3ResolverFactory + '', // will be injected by AwsS3ResolverFactory + '', // will be injected by AwsS3ResolverFactory + '', // will be injected by AwsS3ResolverFactory + ]); + + $services->set('liip_imagine.cache.resolver.prototype.cache', CacheResolver::class) + ->public() + ->abstract() + ->args([ + // args will be injected by a ResolverFactory + '', + '', + ]); + + $services->set('liip_imagine.cache.resolver.prototype.flysystem', FlysystemResolver::class) + ->public() + ->abstract() + ->args([ + '', // will be injected by a ResolverFactory + service('router.request_context'), + '', // will be injected by a ResolverFactory + '', // will be injected by a ResolverFactory + '', // will be injected by a ResolverFactory + ]); + + $services->set('liip_imagine.cache.resolver.prototype.flysystem2', FlysystemV2Resolver::class) + ->public() + ->abstract() + ->args([ + '', + service('router.request_context'), + '', + '', + '', + ]); + + $services->set('liip_imagine.cache.resolver.prototype.proxy', ProxyResolver::class) + ->public() + ->abstract() + ->args([ + '', // will be injected by AwsS3ResolverFactory + '', // will be injected by AwsS3ResolverFactory + ]); + + $services->set('liip_imagine.cache.resolver.prototype.psr_cache', PsrCacheResolver::class) + ->public() + ->abstract() + ->args([ + '', // will be injected by a ResolverFactory + '', // will be injected by a ResolverFactory + ]); + + $services->set('liip_imagine.cache.resolver.no_cache_web_path', NoCacheWebPathResolver::class) + ->public() + ->args([service('router.request_context')]) + ->tag('liip_imagine.cache.resolver', ['resolver' => 'no_cache']); + + // Form types + $services->set('liip_imagine.form.type.image', ImageType::class) + ->tag('form.type', ['alias' => 'liip_imagine_image']); + + // Guessers + $services->set('liip_imagine.mime_type_guesser', 'Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface') + ->factory(['Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser', 'getInstance']); + + $services->set('liip_imagine.extension_guesser', 'Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesserInterface') + ->factory(['Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser', 'getInstance']); + + $services->set('liip_imagine.binary.mime_type_guesser', SimpleMimeTypeGuesser::class) + ->args([service('liip_imagine.mime_type_guesser')]); + + $services->set('liip_imagine.cache.signer', Signer::class) + ->public() + ->args(['%kernel.secret%']); + + // Post processors + $services->set('liip_imagine.filter.post_processor.jpegoptim', JpegOptimPostProcessor::class) + ->args([ + '%liip_imagine.jpegoptim.binary%', + '%liip_imagine.jpegoptim.stripAll%', + '%liip_imagine.jpegoptim.max%', + '%liip_imagine.jpegoptim.progressive%', + '%liip_imagine.jpegoptim.tempDir%', + ]) + ->tag('liip_imagine.filter.post_processor', ['post_processor' => 'jpegoptim']); + + $services->set('liip_imagine.filter.post_processor.optipng', OptiPngPostProcessor::class) + ->args([ + '%liip_imagine.optipng.binary%', + '%liip_imagine.optipng.level%', + '%liip_imagine.optipng.stripAll%', + '%liip_imagine.optipng.tempDir%', + ]) + ->tag('liip_imagine.filter.post_processor', ['post_processor' => 'optipng']); + + $services->set('liip_imagine.filter.post_processor.pngquant', PngquantPostProcessor::class) + ->args(['%liip_imagine.pngquant.binary%']) + ->tag('liip_imagine.filter.post_processor', ['post_processor' => 'pngquant']); + + $services->set('liip_imagine.filter.post_processor.mozjpeg', MozJpegPostProcessor::class) + ->args(['%liip_imagine.mozjpeg.binary%']) + ->tag('liip_imagine.filter.post_processor', ['post_processor' => 'mozjpeg']); + + $services->set('liip_imagine.filter.post_processor.cwebp', CwebpPostProcessor::class) + ->args([ + '%liip_imagine.cwebp.binary%', + '%liip_imagine.cwebp.tempDir%', + '%liip_imagine.cwebp.q%', + '%liip_imagine.cwebp.alphaQ%', + '%liip_imagine.cwebp.m%', + '%liip_imagine.cwebp.alphaFilter%', + '%liip_imagine.cwebp.alphaMethod%', + '%liip_imagine.cwebp.exact%', + '%liip_imagine.cwebp.metadata%', + ]) + ->tag('liip_imagine.filter.post_processor', ['post_processor' => 'cwebp']); }; From 1864225676941b3634ce1c7db0d5397b49c50853 Mon Sep 17 00:00:00 2001 From: tito10047 Date: Mon, 5 Jan 2026 13:35:35 +0100 Subject: [PATCH 10/20] add Symfony Asset Mapper to PHPStan workflow --- .github/workflows/phpstan.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 099c13f1a..5045a2bd9 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -26,5 +26,8 @@ jobs: - name: Add vips run: composer require rokka/imagine-vips + - name: Add asset mapper + run: composer require symfony/asset-mapper + - name: Run PHPStan run: vendor/bin/phpstan analyze From 6a524a1cc06e13b1cff34ef645e42463e78a600a Mon Sep 17 00:00:00 2001 From: tito10047 Date: Mon, 5 Jan 2026 13:50:56 +0100 Subject: [PATCH 11/20] add tests for `AssetMapperLocator` and `AssetMapperLoaderFactory`, fix caching logic in `AssetMapperLocator`, and update workflows to require Symfony Asset Mapper --- .github/workflows/phpstan.yml | 1 + .github/workflows/phpunit.yml | 4 + Binary/Locator/AssetMapperLocator.php | 6 +- .../Binary/Locator/AssetMapperLocatorTest.php | 151 ++++++++++++++++++ .../Loader/AssetMapperLoaderFactoryTest.php | 8 + 5 files changed, 167 insertions(+), 3 deletions(-) create mode 100644 Tests/Binary/Locator/AssetMapperLocatorTest.php diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 5045a2bd9..eba4f35e8 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -27,6 +27,7 @@ jobs: run: composer require rokka/imagine-vips - name: Add asset mapper + if: ${{ matrix.php-version >= '8.0' || (matrix.php-version == '' && 8.2 >= 8.0) }} run: composer require symfony/asset-mapper - name: Run PHPStan diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index f6067a22f..e4ddd06c8 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -85,6 +85,10 @@ jobs: if: ${{ matrix.php == '7.4' }} run: composer require --no-update enqueue/enqueue 0.10.18 + - name: Add asset mapper + if: ${{ matrix.php >= '8.0' }} + run: composer require --no-update symfony/asset-mapper + - name: Update project dependencies uses: ramsey/composer-install@v2 with: diff --git a/Binary/Locator/AssetMapperLocator.php b/Binary/Locator/AssetMapperLocator.php index 838fa29e6..e30e82c68 100644 --- a/Binary/Locator/AssetMapperLocator.php +++ b/Binary/Locator/AssetMapperLocator.php @@ -59,7 +59,7 @@ public function locate(string $path): string $asset = $cachedAsset->isHit() ? $this->assetMapper->getAsset($cachedAsset->get()) : null; if (null !== $asset && $asset->publicPath === $pathInfo) { - return $asset; + return $asset->sourcePath; } } @@ -73,7 +73,7 @@ public function locate(string $path): string } if (null === $asset) { - throw new NotLoadableException(\sprintf('Asset with public path "%s" not found.', $path)); + throw new NotLoadableException(\sprintf('Asset with public path "%s" not found.', $pathInfo)); } if (null !== $cachedAsset) { @@ -81,6 +81,6 @@ public function locate(string $path): string $this->cacheMapCache->save($cachedAsset); } - return $asset; + return $asset->sourcePath; } } diff --git a/Tests/Binary/Locator/AssetMapperLocatorTest.php b/Tests/Binary/Locator/AssetMapperLocatorTest.php new file mode 100644 index 000000000..e6141b9df --- /dev/null +++ b/Tests/Binary/Locator/AssetMapperLocatorTest.php @@ -0,0 +1,151 @@ +markTestSkipped('symfony/asset-mapper is required for this test.'); + } + } + + public function testImplementsLocatorInterface(): void + { + $assetMapper = $this->createMock(AssetMapperInterface::class); + $this->assertInstanceOf(LocatorInterface::class, new AssetMapperLocator($assetMapper)); + } + + public function testLocateWithoutCache(): void + { + $path = 'images/logo.png'; + $pathInfo = '/images/logo.png'; + $sourcePath = '/path/to/logo.png'; + $logicalPath = 'logo.png'; + + $asset = new MappedAsset($logicalPath, $sourcePath, $pathInfo, $pathInfo); + + $assetMapper = $this->createMock(AssetMapperInterface::class); + $assetMapper->expects($this->once()) + ->method('allAssets') + ->willReturn([$asset]); + + $locator = new AssetMapperLocator($assetMapper); + $result = $locator->locate($path); + + $this->assertSame($sourcePath, $result); + } + + public function testLocateWithCacheHit(): void + { + $path = 'images/logo.png'; + $pathInfo = '/images/logo.png'; + $logicalPath = 'logo.png'; + $sourcePath = '/path/to/logo.png'; + + $asset = new MappedAsset($logicalPath, $sourcePath, $pathInfo, $pathInfo); + + $cacheItem = $this->createMock(CacheItemInterface::class); + $cacheItem->expects($this->once()) + ->method('isHit') + ->willReturn(true); + $cacheItem->expects($this->once()) + ->method('get') + ->willReturn($logicalPath); + + $cache = $this->createMock(CacheItemPoolInterface::class); + $cache->expects($this->once()) + ->method('getItem') + ->with(hash('xxh128', $pathInfo)) + ->willReturn($cacheItem); + + $assetMapper = $this->createMock(AssetMapperInterface::class); + $assetMapper->expects($this->once()) + ->method('getAsset') + ->with($logicalPath) + ->willReturn($asset); + $assetMapper->expects($this->never()) + ->method('allAssets'); + + $locator = new AssetMapperLocator($assetMapper, $cache); + $result = $locator->locate($path); + + $this->assertSame($sourcePath, $result); + } + + public function testLocateWithCacheMissAndSave(): void + { + $path = 'images/logo.png'; + $pathInfo = '/images/logo.png'; + $logicalPath = 'logo.png'; + $sourcePath = '/path/to/logo.png'; + + $asset = new MappedAsset($logicalPath, $sourcePath, $pathInfo, $pathInfo); + + $cacheItem = $this->createMock(CacheItemInterface::class); + $cacheItem->expects($this->once()) + ->method('isHit') + ->willReturn(false); + $cacheItem->expects($this->once()) + ->method('set') + ->with($logicalPath); + + $cache = $this->createMock(CacheItemPoolInterface::class); + $cache->expects($this->once()) + ->method('getItem') + ->with(hash('xxh128', $pathInfo)) + ->willReturn($cacheItem); + $cache->expects($this->once()) + ->method('save') + ->with($cacheItem); + + $assetMapper = $this->createMock(AssetMapperInterface::class); + $assetMapper->expects($this->once()) + ->method('allAssets') + ->willReturn([$asset]); + + $locator = new AssetMapperLocator($assetMapper, $cache); + $result = $locator->locate($path); + + $this->assertSame($sourcePath, $result); + } + + public function testThrowsIfAssetNotFound(): void + { + $path = 'images/logo.png'; + + $assetMapper = $this->createMock(AssetMapperInterface::class); + $assetMapper->expects($this->once()) + ->method('allAssets') + ->willReturn([]); + + $locator = new AssetMapperLocator($assetMapper); + + $this->expectException(NotLoadableException::class); + $this->expectExceptionMessage('Asset with public path "/images/logo.png" not found.'); + + $locator->locate($path); + } +} diff --git a/Tests/DependencyInjection/Factory/Loader/AssetMapperLoaderFactoryTest.php b/Tests/DependencyInjection/Factory/Loader/AssetMapperLoaderFactoryTest.php index e682fae5f..9ac658da5 100644 --- a/Tests/DependencyInjection/Factory/Loader/AssetMapperLoaderFactoryTest.php +++ b/Tests/DependencyInjection/Factory/Loader/AssetMapperLoaderFactoryTest.php @@ -14,6 +14,7 @@ use Liip\ImagineBundle\DependencyInjection\Factory\Loader\AssetMapperLoaderFactory; use Liip\ImagineBundle\DependencyInjection\Factory\Loader\LoaderFactoryInterface; use Liip\ImagineBundle\Tests\DependencyInjection\Factory\FactoryTestCase; +use Symfony\Component\AssetMapper\AssetMapperInterface; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -22,6 +23,13 @@ */ class AssetMapperLoaderFactoryTest extends FactoryTestCase { + protected function setUp(): void + { + if (!interface_exists(AssetMapperInterface::class)) { + $this->markTestSkipped('symfony/asset-mapper is required for this test.'); + } + } + public function testImplementsLoaderFactoryInterface(): void { $this->assertInstanceOf(LoaderFactoryInterface::class, new AssetMapperLoaderFactory()); From 780101828dbe712fdd24cba5be908fa52dc36585 Mon Sep 17 00:00:00 2001 From: Jozef Mostka Date: Mon, 5 Jan 2026 13:52:10 +0100 Subject: [PATCH 12/20] Update Resources/doc/data-loader/asset_mapper.rst Co-authored-by: David Buchmann --- Resources/doc/data-loader/asset_mapper.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/doc/data-loader/asset_mapper.rst b/Resources/doc/data-loader/asset_mapper.rst index 544a77ca5..f9cd29246 100644 --- a/Resources/doc/data-loader/asset_mapper.rst +++ b/Resources/doc/data-loader/asset_mapper.rst @@ -1,7 +1,7 @@ .. _data-loaders-asset-mapper: -Asset Mapper Loader +Asset Mapper Loader (for dev) =================== The ``AssetMapper`` data loader allows for loading images using Symfony's AssetMapper component. From eeea5617f87854cd1a197e900d8f09082dbeb284 Mon Sep 17 00:00:00 2001 From: Jozef Mostka Date: Mon, 5 Jan 2026 13:52:17 +0100 Subject: [PATCH 13/20] Update Resources/doc/data-loader/asset_mapper.rst Co-authored-by: David Buchmann --- Resources/doc/data-loader/asset_mapper.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/doc/data-loader/asset_mapper.rst b/Resources/doc/data-loader/asset_mapper.rst index f9cd29246..0f37facef 100644 --- a/Resources/doc/data-loader/asset_mapper.rst +++ b/Resources/doc/data-loader/asset_mapper.rst @@ -2,7 +2,7 @@ .. _data-loaders-asset-mapper: Asset Mapper Loader (for dev) -=================== +============================ The ``AssetMapper`` data loader allows for loading images using Symfony's AssetMapper component. From 68b8d96dfc59a66a3047cea4a474c916646ed5c9 Mon Sep 17 00:00:00 2001 From: tito10047 Date: Mon, 5 Jan 2026 14:16:21 +0100 Subject: [PATCH 14/20] cs fixer --- Binary/Locator/AssetMapperLocator.php | 109 +- .../Loader/AssetMapperLoaderFactory.php | 58 +- Resources/config/imagine.php | 974 +++++++++--------- .../Binary/Locator/AssetMapperLocatorTest.php | 4 +- 4 files changed, 572 insertions(+), 573 deletions(-) diff --git a/Binary/Locator/AssetMapperLocator.php b/Binary/Locator/AssetMapperLocator.php index e30e82c68..1a6717601 100644 --- a/Binary/Locator/AssetMapperLocator.php +++ b/Binary/Locator/AssetMapperLocator.php @@ -12,8 +12,8 @@ namespace Liip\ImagineBundle\Binary\Locator; use Liip\ImagineBundle\Exception\Binary\Loader\NotLoadableException; -use Symfony\Component\AssetMapper\AssetMapperInterface; use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\AssetMapper\AssetMapperInterface; /** * Class responsible for locating assets in the asset mapper. @@ -22,65 +22,64 @@ */ class AssetMapperLocator implements LocatorInterface { + private $assetMapper; + private $cacheMapCache; - private $assetMapper; - private $cacheMapCache = null; - - public function __construct( - AssetMapperInterface $assetMapper, - ?CacheItemPoolInterface $cacheMapCache = null - ) { - $this->cacheMapCache = $cacheMapCache; - $this->assetMapper = $assetMapper; - } + public function __construct( + AssetMapperInterface $assetMapper, + ?CacheItemPoolInterface $cacheMapCache = null + ) { + $this->cacheMapCache = $cacheMapCache; + $this->assetMapper = $assetMapper; + } - /** - * Locates an asset by its public path. - * - * This method attempts to retrieve an asset using its public path. It first - * checks for a cached version of the asset, and if none is found, it iterates - * through all available assets to find a match. If no matching asset is located, - * an exception is thrown. - * - * Inspired by Symfony's AssetMapper component @see https://github.com/symfony/asset-mapper/blob/7.3/AssetMapperDevServerSubscriber.php#L179. - * - * @param string $path The public path of the asset to locate. - * - * @return string The located asset. - * - * @throws NotLoadableException If no asset with the specified public path is found. - */ - public function locate(string $path): string - { - $pathInfo = '/'.ltrim($path, '/'); - $cachedAsset = null; - if (null !== $this->cacheMapCache) { - $cachedAsset = $this->cacheMapCache->getItem(hash('xxh128', $pathInfo)); - $asset = $cachedAsset->isHit() ? $this->assetMapper->getAsset($cachedAsset->get()) : null; + /** + * Locates an asset by its public path. + * + * This method attempts to retrieve an asset using its public path. It first + * checks for a cached version of the asset, and if none is found, it iterates + * through all available assets to find a match. If no matching asset is located, + * an exception is thrown. + * + * Inspired by Symfony's AssetMapper component @see https://github.com/symfony/asset-mapper/blob/7.3/AssetMapperDevServerSubscriber.php#L179. + * + * @param string $path the public path of the asset to locate + * + * @throws NotLoadableException if no asset with the specified public path is found + * + * @return string the located asset + */ + public function locate(string $path): string + { + $pathInfo = '/'.ltrim($path, '/'); + $cachedAsset = null; + if (null !== $this->cacheMapCache) { + $cachedAsset = $this->cacheMapCache->getItem(hash('xxh128', $pathInfo)); + $asset = $cachedAsset->isHit() ? $this->assetMapper->getAsset($cachedAsset->get()) : null; - if (null !== $asset && $asset->publicPath === $pathInfo) { - return $asset->sourcePath; - } - } + if (null !== $asset && $asset->publicPath === $pathInfo) { + return $asset->sourcePath; + } + } - // we did not find a match - $asset = null; - foreach ($this->assetMapper->allAssets() as $assetCandidate) { - if ($pathInfo === $assetCandidate->publicPath) { - $asset = $assetCandidate; - break; - } - } + // we did not find a match + $asset = null; + foreach ($this->assetMapper->allAssets() as $assetCandidate) { + if ($pathInfo === $assetCandidate->publicPath) { + $asset = $assetCandidate; + break; + } + } - if (null === $asset) { - throw new NotLoadableException(\sprintf('Asset with public path "%s" not found.', $pathInfo)); - } + if (null === $asset) { + throw new NotLoadableException(\sprintf('Asset with public path "%s" not found.', $pathInfo)); + } - if (null !== $cachedAsset) { - $cachedAsset->set($asset->logicalPath); - $this->cacheMapCache->save($cachedAsset); - } + if (null !== $cachedAsset) { + $cachedAsset->set($asset->logicalPath); + $this->cacheMapCache->save($cachedAsset); + } - return $asset->sourcePath; - } + return $asset->sourcePath; + } } diff --git a/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php b/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php index e8b1db7ab..4c68030f7 100644 --- a/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php +++ b/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php @@ -18,33 +18,33 @@ class AssetMapperLoaderFactory extends AbstractLoaderFactory { - public function create(ContainerBuilder $container, $loaderName, array $config) - { - $locatorDefinition = new ChildDefinition('liip_imagine.binary.locator.asset_mapper'); - $locatorDefinition->replaceArgument(0, new Reference('asset_mapper')); - $locatorDefinition->replaceArgument(1, new Reference('cache.asset_mapper')); - - $definition = $this->getChildLoaderDefinition('filesystem'); - - if ($container->hasDefinition('liip_imagine.mime_types')) { - $mimeTypes = $container->getDefinition('liip_imagine.mime_types'); - $definition->replaceArgument(0, $mimeTypes); - $definition->replaceArgument(1, $mimeTypes); - } - $definition->replaceArgument(2, $locatorDefinition); - - return $this->setTaggedLoaderDefinition($loaderName, $definition, $container); - } - - public function getName() - { - return 'asset_mapper'; - } - - public function addConfiguration(ArrayNodeDefinition $builder) - { - $builder - ->children() - ->end(); - } + public function create(ContainerBuilder $container, $loaderName, array $config) + { + $locatorDefinition = new ChildDefinition('liip_imagine.binary.locator.asset_mapper'); + $locatorDefinition->replaceArgument(0, new Reference('asset_mapper')); + $locatorDefinition->replaceArgument(1, new Reference('cache.asset_mapper')); + + $definition = $this->getChildLoaderDefinition('filesystem'); + + if ($container->hasDefinition('liip_imagine.mime_types')) { + $mimeTypes = $container->getDefinition('liip_imagine.mime_types'); + $definition->replaceArgument(0, $mimeTypes); + $definition->replaceArgument(1, $mimeTypes); + } + $definition->replaceArgument(2, $locatorDefinition); + + return $this->setTaggedLoaderDefinition($loaderName, $definition, $container); + } + + public function getName() + { + return 'asset_mapper'; + } + + public function addConfiguration(ArrayNodeDefinition $builder) + { + $builder + ->children() + ->end(); + } } diff --git a/Resources/config/imagine.php b/Resources/config/imagine.php index 067eb8f65..d4c6611b2 100644 --- a/Resources/config/imagine.php +++ b/Resources/config/imagine.php @@ -89,496 +89,496 @@ use Liip\ImagineBundle\Service\FilterService; return static function (ContainerConfigurator $container) { - $services = $container->services(); - $parameters = $container->parameters(); + $services = $container->services(); + $parameters = $container->parameters(); - // JpegOptim parameters - $parameters->set('liip_imagine.jpegoptim.binary', '/usr/bin/jpegoptim'); - $parameters->set('liip_imagine.jpegoptim.stripAll', true); - $parameters->set('liip_imagine.jpegoptim.max', null); - $parameters->set('liip_imagine.jpegoptim.progressive', true); - $parameters->set('liip_imagine.jpegoptim.tempDir', null); + // JpegOptim parameters + $parameters->set('liip_imagine.jpegoptim.binary', '/usr/bin/jpegoptim'); + $parameters->set('liip_imagine.jpegoptim.stripAll', true); + $parameters->set('liip_imagine.jpegoptim.max', null); + $parameters->set('liip_imagine.jpegoptim.progressive', true); + $parameters->set('liip_imagine.jpegoptim.tempDir', null); - // OptiPng parameters - $parameters->set('liip_imagine.optipng.binary', '/usr/bin/optipng'); - $parameters->set('liip_imagine.optipng.level', 7); - $parameters->set('liip_imagine.optipng.stripAll', true); - $parameters->set('liip_imagine.optipng.tempDir', null); + // OptiPng parameters + $parameters->set('liip_imagine.optipng.binary', '/usr/bin/optipng'); + $parameters->set('liip_imagine.optipng.level', 7); + $parameters->set('liip_imagine.optipng.stripAll', true); + $parameters->set('liip_imagine.optipng.tempDir', null); - // Pngquant parameters - $parameters->set('liip_imagine.pngquant.binary', '/usr/bin/pngquant'); + // Pngquant parameters + $parameters->set('liip_imagine.pngquant.binary', '/usr/bin/pngquant'); - // MozJpeg parameters - $parameters->set('liip_imagine.mozjpeg.binary', '/opt/mozjpeg/bin/cjpeg'); + // MozJpeg parameters + $parameters->set('liip_imagine.mozjpeg.binary', '/opt/mozjpeg/bin/cjpeg'); - // cwebp parameters - $parameters->set('liip_imagine.cwebp.binary', '/usr/bin/cwebp'); - $parameters->set('liip_imagine.cwebp.tempDir', null); - $parameters->set('liip_imagine.cwebp.q', 75); - $parameters->set('liip_imagine.cwebp.alphaQ', 100); - $parameters->set('liip_imagine.cwebp.m', 4); - $parameters->set('liip_imagine.cwebp.alphaFilter', 'fast'); - $parameters->set('liip_imagine.cwebp.alphaMethod', 1); - $parameters->set('liip_imagine.cwebp.exact', false); - $parameters->set('liip_imagine.cwebp.metadata', ['none']); - - // Factory services - $services->set('liip_imagine.factory.config.filter.argument.point', PointFactory::class); - - $services->set('liip_imagine.factory.config.filter.argument.size', SizeFactory::class); - - $services->set('liip_imagine.factory.config.stack', StackFactory::class); - - $services->set('liip_imagine.factory.config.filter.auto_rotate', AutoRotateFactory::class); - - $services->set('liip_imagine.factory.config.filter.background', BackgroundFactory::class) - ->args([service('liip_imagine.factory.config.filter.argument.size')]); - - $services->set('liip_imagine.factory.config.filter.crop', CropFactory::class) - ->args([ - service('liip_imagine.factory.config.filter.argument.size'), - service('liip_imagine.factory.config.filter.argument.point'), - ]); - - $services->set('liip_imagine.factory.config.filter.downscale', DownscaleFactory::class) - ->args([service('liip_imagine.factory.config.filter.argument.size')]); - - $services->set('liip_imagine.factory.config.filter.flip', FlipFactory::class); - - $services->set('liip_imagine.factory.config.filter.grayscale', GrayscaleFactory::class); - - $services->set('liip_imagine.factory.config.filter.interlace', InterlaceFactory::class); - - $services->set('liip_imagine.factory.config.filter.paste', PasteFactory::class) - ->args([service('liip_imagine.factory.config.filter.argument.point')]); - - $services->set('liip_imagine.factory.config.filter.relative_resize', RelativeResizeFactory::class); - - $services->set('liip_imagine.factory.config.filter.resize', ResizeFactory::class) - ->args([service('liip_imagine.factory.config.filter.argument.size')]); - - $services->set('liip_imagine.factory.config.filter.rotate', RotateFactory::class); - - $services->set('liip_imagine.factory.config.filter.scale', ScaleFactory::class) - ->args([service('liip_imagine.factory.config.filter.argument.size')]); - - $services->set('liip_imagine.factory.config.filter.strip', StripFactory::class); - - $services->set('liip_imagine.factory.config.filter.thumbnail', ThumbnailFactory::class) - ->args([service('liip_imagine.factory.config.filter.argument.size')]); - - $services->set('liip_imagine.factory.config.filter.upscale', UpscaleFactory::class) - ->args([service('liip_imagine.factory.config.filter.argument.size')]); - - $services->set('liip_imagine.factory.config.filter.watermark', WatermarkFactory::class); - - // Config services - $services->set('liip_imagine.config.filter_factory_collection', FilterFactoryCollection::class) - ->args([ - service('liip_imagine.factory.config.filter.auto_rotate'), - service('liip_imagine.factory.config.filter.background'), - service('liip_imagine.factory.config.filter.crop'), - service('liip_imagine.factory.config.filter.downscale'), - service('liip_imagine.factory.config.filter.flip'), - service('liip_imagine.factory.config.filter.grayscale'), - service('liip_imagine.factory.config.filter.interlace'), - service('liip_imagine.factory.config.filter.paste'), - service('liip_imagine.factory.config.filter.relative_resize'), - service('liip_imagine.factory.config.filter.resize'), - service('liip_imagine.factory.config.filter.rotate'), - service('liip_imagine.factory.config.filter.scale'), - service('liip_imagine.factory.config.filter.strip'), - service('liip_imagine.factory.config.filter.thumbnail'), - service('liip_imagine.factory.config.filter.upscale'), - service('liip_imagine.factory.config.filter.watermark'), - ]); - - $services->alias(FilterFactoryCollection::class, 'liip_imagine.config.filter_factory_collection'); - - $services->set('liip_imagine.config.stack_builder', StackBuilder::class) - ->args([ - service('liip_imagine.factory.config.stack'), - service('liip_imagine.config.filter_factory_collection'), - ]); - - $services->alias(StackBuilder::class, 'liip_imagine.config.stack_builder'); - - $services->set('liip_imagine.config.stack_collection', StackCollection::class) - ->public() - ->args([ - service('liip_imagine.config.stack_builder'), - '%liip_imagine.filter_sets%', - ]); - - $services->alias(StackCollection::class, 'liip_imagine.config.stack_collection'); - - // Utility services - $services->set('liip_imagine.filter.manager', FilterManager::class) - ->public() - ->args([ - service('liip_imagine.filter.configuration'), - service('liip_imagine'), - service('liip_imagine.binary.mime_type_guesser'), - ]); - - $services->alias(FilterManager::class, 'liip_imagine.filter.manager'); - - $services->set('liip_imagine.data.manager', DataManager::class) - ->public() - ->args([ - service('liip_imagine.binary.mime_type_guesser'), - service('liip_imagine.extension_guesser'), - service('liip_imagine.filter.configuration'), - '%liip_imagine.binary.loader.default%', - '%liip_imagine.default_image%', - ]); - - $services->alias(DataManager::class, 'liip_imagine.data.manager'); - - $services->set('liip_imagine.cache.manager', CacheManager::class) - ->public() - ->args([ - service('liip_imagine.filter.configuration'), - service('router'), - service('liip_imagine.cache.signer'), - service('event_dispatcher'), - '%liip_imagine.cache.resolver.default%', - '%liip_imagine.webp.generate%', - ]); - - $services->alias(CacheManager::class, 'liip_imagine.cache.manager'); - - $services->set('liip_imagine.filter.configuration', FilterConfiguration::class) - ->args(['%liip_imagine.filter_sets%']); - - $services->set('liip_imagine.service.filter', FilterService::class) - ->args([ - service('liip_imagine.data.manager'), - service('liip_imagine.filter.manager'), - service('liip_imagine.cache.manager'), - '%liip_imagine.webp.generate%', - '%liip_imagine.webp.options%', - service('logger')->ignoreOnInvalid(), - ]); - - $services->alias(FilterService::class, 'liip_imagine.service.filter'); - - // Config - $services->set('liip_imagine.controller.config', ControllerConfig::class) - ->private() - ->args(['']); - - // Controller - $services->set(ImagineController::class) - ->public() - ->args([ - service('liip_imagine.service.filter'), - service('liip_imagine.data.manager'), - service('liip_imagine.cache.signer'), - service('liip_imagine.controller.config'), - ]); - - $services->alias('liip_imagine.controller', ImagineController::class) - ->public(); - - $services->set('liip_imagine.meta_data.reader', ExifMetadataReader::class) - ->private(); - - // ImagineInterface instances - $services->alias('liip_imagine', 'liip_imagine.gd'); - - $services->alias(ImagineInterface::class, 'liip_imagine'); - - $services->set('liip_imagine.gd', Imagine::class) - ->private() - ->call('setMetadataReader', [service('liip_imagine.meta_data.reader')]); - - $services->set('liip_imagine.imagick', \Imagine\Imagick\Imagine::class) - ->private() - ->call('setMetadataReader', [service('liip_imagine.meta_data.reader')]); - - $services->set('liip_imagine.gmagick', \Imagine\Gmagick\Imagine::class) - ->private() - ->call('setMetadataReader', [service('liip_imagine.meta_data.reader')]); - - // Filter loaders - $services->set('liip_imagine.filter.loader.relative_resize', RelativeResizeFilterLoader::class) - ->tag('liip_imagine.filter.loader', ['loader' => 'relative_resize']); - - $services->set('liip_imagine.filter.loader.resize', ResizeFilterLoader::class) - ->tag('liip_imagine.filter.loader', ['loader' => 'resize']); - - $services->set('liip_imagine.filter.loader.thumbnail', ThumbnailFilterLoader::class) - ->tag('liip_imagine.filter.loader', ['loader' => 'thumbnail']); - - $services->set('liip_imagine.filter.loader.crop', CropFilterLoader::class) - ->tag('liip_imagine.filter.loader', ['loader' => 'crop']); - - $services->set('liip_imagine.filter.loader.grayscale', GrayscaleFilterLoader::class) - ->public() - ->tag('liip_imagine.filter.loader', ['loader' => 'grayscale']); - - $services->set('liip_imagine.filter.loader.paste_image', PasteFilterLoader::class) - ->args([ - service('liip_imagine'), - '%kernel.project_dir%', - ]) - ->tag('liip_imagine.filter.loader', ['loader' => 'paste_image']); - - // not officially deprecated because still injected and appears "used" - $services->set('liip_imagine.filter.loader.paste', PasteFilterLoader::class) - ->args([ - service('liip_imagine'), - '%kernel.root_dir%', - ]) - ->tag('liip_imagine.filter.loader', ['loader' => 'paste']); - - // not officially deprecated because still injected and appears "used" - $services->set('liip_imagine.filter.loader.watermark', WatermarkFilterLoader::class) - ->args([ - service('liip_imagine'), - '%kernel.root_dir%', - ]) - ->tag('liip_imagine.filter.loader', ['loader' => 'watermark']); - - $services->set('liip_imagine.filter.loader.watermark_image', WatermarkFilterLoader::class) - ->args([ - service('liip_imagine'), - '%kernel.project_dir%', - ]) - ->tag('liip_imagine.filter.loader', ['loader' => 'watermark_image']); - - $services->set('liip_imagine.filter.loader.background', BackgroundFilterLoader::class) - ->args([service('liip_imagine')]) - ->tag('liip_imagine.filter.loader', ['loader' => 'background']); - - $services->set('liip_imagine.filter.loader.strip', StripFilterLoader::class) - ->tag('liip_imagine.filter.loader', ['loader' => 'strip']); - - $services->set('liip_imagine.filter.loader.scale', ScaleFilterLoader::class) - ->tag('liip_imagine.filter.loader', ['loader' => 'scale']); - - $services->set('liip_imagine.filter.loader.upscale', UpscaleFilterLoader::class) - ->tag('liip_imagine.filter.loader', ['loader' => 'upscale']); - - $services->set('liip_imagine.filter.loader.downscale', DownscaleFilterLoader::class) - ->public() - ->tag('liip_imagine.filter.loader', ['loader' => 'downscale']); - - $services->set('liip_imagine.filter.loader.auto_rotate', AutoRotateFilterLoader::class) - ->tag('liip_imagine.filter.loader', ['loader' => 'auto_rotate']); - - $services->set('liip_imagine.filter.loader.rotate', RotateFilterLoader::class) - ->public() - ->tag('liip_imagine.filter.loader', ['loader' => 'rotate']); - - $services->set('liip_imagine.filter.loader.flip', FlipFilterLoader::class) - ->public() - ->tag('liip_imagine.filter.loader', ['loader' => 'flip']); - - $services->set('liip_imagine.filter.loader.interlace', InterlaceFilterLoader::class) - ->public() - ->tag('liip_imagine.filter.loader', ['loader' => 'interlace']); - - $services->set('liip_imagine.filter.loader.resample', ResampleFilterLoader::class) - ->public() - ->args([service('liip_imagine')]) - ->tag('liip_imagine.filter.loader', ['loader' => 'resample']); - - $services->set('liip_imagine.filter.loader.fixed', FixedFilterLoader::class) - ->public() - ->tag('liip_imagine.filter.loader', ['loader' => 'fixed']); - - // Data loaders - $services->set('liip_imagine.binary.loader.prototype.filesystem', FileSystemLoader::class) - ->args([ - service('liip_imagine.mime_type_guesser'), - service('liip_imagine.extension_guesser'), - '', // will be injected by FileSystemLoaderFactory - ]); - - $services->set('liip_imagine.binary.loader.prototype.stream', StreamLoader::class) - ->args([ - '', // will be injected by StreamLoaderFactory - '', // will be injected by StreamLoaderFactory - ]); - - $services->set('liip_imagine.binary.loader.prototype.flysystem', FlysystemLoader::class) - ->abstract() - ->args([ - service('liip_imagine.extension_guesser'), - '', // will be injected by FlysystemLoaderFactory - ]); - - $services->set('liip_imagine.binary.loader.prototype.flysystem2', FlysystemV2Loader::class) - ->abstract() - ->args([ - service('liip_imagine.extension_guesser'), - '', // will be injected by FlysystemV2LoaderFactory - ]); - - $services->set('liip_imagine.binary.loader.prototype.chain', ChainLoader::class) - ->abstract() - ->args(['']); // will be injected by ChainLoaderFactory - - // Data loader locators - $services->set('liip_imagine.binary.locator.filesystem', FileSystemLocator::class) - ->share(false) - ->private() - ->args([ - '', // will be injected by FilesystemLoaderFactory - '', // will be injected by FilesystemLoaderFactory - ]) - ->tag('liip_imagine.binary.locator', ['shared' => false]); - $services->set('liip_imagine.binary.locator.asset_mapper', AssetMapperLocator::class) - ->share(true) - ->public() - ->args([ - '', // will be injected by AssetMapperLoaderFactory - '', // will be injected by AssetMapperLoaderFactory - ]) - ->tag('liip_imagine.binary.locator', ['shared' => false]); - - $services->set('liip_imagine.binary.locator.filesystem_insecure', FileSystemInsecureLocator::class) - ->share(false) - ->private() - ->args([ - '', // will be injected by FilesystemLoaderFactory - '', // will be injected by FilesystemLoaderFactory - ]) - ->tag('liip_imagine.binary.locator', ['shared' => false]); - - // Cache resolver - $services->set('liip_imagine.cache.resolver.prototype.web_path', WebPathResolver::class) - ->public() - ->abstract() - ->args([ - service('filesystem'), - service('router.request_context'), - '', // will be injected by WebPathResolverFactory - '', // will be injected by WebPathResolverFactory - ]); - - $services->set('liip_imagine.cache.resolver.prototype.aws_s3', AwsS3Resolver::class) - ->public() - ->abstract() - ->args([ - '', // will be injected by AwsS3ResolverFactory - '', // will be injected by AwsS3ResolverFactory - '', // will be injected by AwsS3ResolverFactory - '', // will be injected by AwsS3ResolverFactory - '', // will be injected by AwsS3ResolverFactory - ]); - - $services->set('liip_imagine.cache.resolver.prototype.cache', CacheResolver::class) - ->public() - ->abstract() - ->args([ - // args will be injected by a ResolverFactory - '', - '', - ]); - - $services->set('liip_imagine.cache.resolver.prototype.flysystem', FlysystemResolver::class) - ->public() - ->abstract() - ->args([ - '', // will be injected by a ResolverFactory - service('router.request_context'), - '', // will be injected by a ResolverFactory - '', // will be injected by a ResolverFactory - '', // will be injected by a ResolverFactory - ]); - - $services->set('liip_imagine.cache.resolver.prototype.flysystem2', FlysystemV2Resolver::class) - ->public() - ->abstract() - ->args([ - '', - service('router.request_context'), - '', - '', - '', - ]); - - $services->set('liip_imagine.cache.resolver.prototype.proxy', ProxyResolver::class) - ->public() - ->abstract() - ->args([ - '', // will be injected by AwsS3ResolverFactory - '', // will be injected by AwsS3ResolverFactory - ]); - - $services->set('liip_imagine.cache.resolver.prototype.psr_cache', PsrCacheResolver::class) - ->public() - ->abstract() - ->args([ - '', // will be injected by a ResolverFactory - '', // will be injected by a ResolverFactory - ]); - - $services->set('liip_imagine.cache.resolver.no_cache_web_path', NoCacheWebPathResolver::class) - ->public() - ->args([service('router.request_context')]) - ->tag('liip_imagine.cache.resolver', ['resolver' => 'no_cache']); - - // Form types - $services->set('liip_imagine.form.type.image', ImageType::class) - ->tag('form.type', ['alias' => 'liip_imagine_image']); - - // Guessers - $services->set('liip_imagine.mime_type_guesser', 'Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface') - ->factory(['Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser', 'getInstance']); - - $services->set('liip_imagine.extension_guesser', 'Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesserInterface') - ->factory(['Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser', 'getInstance']); - - $services->set('liip_imagine.binary.mime_type_guesser', SimpleMimeTypeGuesser::class) - ->args([service('liip_imagine.mime_type_guesser')]); - - $services->set('liip_imagine.cache.signer', Signer::class) - ->public() - ->args(['%kernel.secret%']); - - // Post processors - $services->set('liip_imagine.filter.post_processor.jpegoptim', JpegOptimPostProcessor::class) - ->args([ - '%liip_imagine.jpegoptim.binary%', - '%liip_imagine.jpegoptim.stripAll%', - '%liip_imagine.jpegoptim.max%', - '%liip_imagine.jpegoptim.progressive%', - '%liip_imagine.jpegoptim.tempDir%', - ]) - ->tag('liip_imagine.filter.post_processor', ['post_processor' => 'jpegoptim']); - - $services->set('liip_imagine.filter.post_processor.optipng', OptiPngPostProcessor::class) - ->args([ - '%liip_imagine.optipng.binary%', - '%liip_imagine.optipng.level%', - '%liip_imagine.optipng.stripAll%', - '%liip_imagine.optipng.tempDir%', - ]) - ->tag('liip_imagine.filter.post_processor', ['post_processor' => 'optipng']); - - $services->set('liip_imagine.filter.post_processor.pngquant', PngquantPostProcessor::class) - ->args(['%liip_imagine.pngquant.binary%']) - ->tag('liip_imagine.filter.post_processor', ['post_processor' => 'pngquant']); - - $services->set('liip_imagine.filter.post_processor.mozjpeg', MozJpegPostProcessor::class) - ->args(['%liip_imagine.mozjpeg.binary%']) - ->tag('liip_imagine.filter.post_processor', ['post_processor' => 'mozjpeg']); - - $services->set('liip_imagine.filter.post_processor.cwebp', CwebpPostProcessor::class) - ->args([ - '%liip_imagine.cwebp.binary%', - '%liip_imagine.cwebp.tempDir%', - '%liip_imagine.cwebp.q%', - '%liip_imagine.cwebp.alphaQ%', - '%liip_imagine.cwebp.m%', - '%liip_imagine.cwebp.alphaFilter%', - '%liip_imagine.cwebp.alphaMethod%', - '%liip_imagine.cwebp.exact%', - '%liip_imagine.cwebp.metadata%', - ]) - ->tag('liip_imagine.filter.post_processor', ['post_processor' => 'cwebp']); + // cwebp parameters + $parameters->set('liip_imagine.cwebp.binary', '/usr/bin/cwebp'); + $parameters->set('liip_imagine.cwebp.tempDir', null); + $parameters->set('liip_imagine.cwebp.q', 75); + $parameters->set('liip_imagine.cwebp.alphaQ', 100); + $parameters->set('liip_imagine.cwebp.m', 4); + $parameters->set('liip_imagine.cwebp.alphaFilter', 'fast'); + $parameters->set('liip_imagine.cwebp.alphaMethod', 1); + $parameters->set('liip_imagine.cwebp.exact', false); + $parameters->set('liip_imagine.cwebp.metadata', ['none']); + + // Factory services + $services->set('liip_imagine.factory.config.filter.argument.point', PointFactory::class); + + $services->set('liip_imagine.factory.config.filter.argument.size', SizeFactory::class); + + $services->set('liip_imagine.factory.config.stack', StackFactory::class); + + $services->set('liip_imagine.factory.config.filter.auto_rotate', AutoRotateFactory::class); + + $services->set('liip_imagine.factory.config.filter.background', BackgroundFactory::class) + ->args([service('liip_imagine.factory.config.filter.argument.size')]); + + $services->set('liip_imagine.factory.config.filter.crop', CropFactory::class) + ->args([ + service('liip_imagine.factory.config.filter.argument.size'), + service('liip_imagine.factory.config.filter.argument.point'), + ]); + + $services->set('liip_imagine.factory.config.filter.downscale', DownscaleFactory::class) + ->args([service('liip_imagine.factory.config.filter.argument.size')]); + + $services->set('liip_imagine.factory.config.filter.flip', FlipFactory::class); + + $services->set('liip_imagine.factory.config.filter.grayscale', GrayscaleFactory::class); + + $services->set('liip_imagine.factory.config.filter.interlace', InterlaceFactory::class); + + $services->set('liip_imagine.factory.config.filter.paste', PasteFactory::class) + ->args([service('liip_imagine.factory.config.filter.argument.point')]); + + $services->set('liip_imagine.factory.config.filter.relative_resize', RelativeResizeFactory::class); + + $services->set('liip_imagine.factory.config.filter.resize', ResizeFactory::class) + ->args([service('liip_imagine.factory.config.filter.argument.size')]); + + $services->set('liip_imagine.factory.config.filter.rotate', RotateFactory::class); + + $services->set('liip_imagine.factory.config.filter.scale', ScaleFactory::class) + ->args([service('liip_imagine.factory.config.filter.argument.size')]); + + $services->set('liip_imagine.factory.config.filter.strip', StripFactory::class); + + $services->set('liip_imagine.factory.config.filter.thumbnail', ThumbnailFactory::class) + ->args([service('liip_imagine.factory.config.filter.argument.size')]); + + $services->set('liip_imagine.factory.config.filter.upscale', UpscaleFactory::class) + ->args([service('liip_imagine.factory.config.filter.argument.size')]); + + $services->set('liip_imagine.factory.config.filter.watermark', WatermarkFactory::class); + + // Config services + $services->set('liip_imagine.config.filter_factory_collection', FilterFactoryCollection::class) + ->args([ + service('liip_imagine.factory.config.filter.auto_rotate'), + service('liip_imagine.factory.config.filter.background'), + service('liip_imagine.factory.config.filter.crop'), + service('liip_imagine.factory.config.filter.downscale'), + service('liip_imagine.factory.config.filter.flip'), + service('liip_imagine.factory.config.filter.grayscale'), + service('liip_imagine.factory.config.filter.interlace'), + service('liip_imagine.factory.config.filter.paste'), + service('liip_imagine.factory.config.filter.relative_resize'), + service('liip_imagine.factory.config.filter.resize'), + service('liip_imagine.factory.config.filter.rotate'), + service('liip_imagine.factory.config.filter.scale'), + service('liip_imagine.factory.config.filter.strip'), + service('liip_imagine.factory.config.filter.thumbnail'), + service('liip_imagine.factory.config.filter.upscale'), + service('liip_imagine.factory.config.filter.watermark'), + ]); + + $services->alias(FilterFactoryCollection::class, 'liip_imagine.config.filter_factory_collection'); + + $services->set('liip_imagine.config.stack_builder', StackBuilder::class) + ->args([ + service('liip_imagine.factory.config.stack'), + service('liip_imagine.config.filter_factory_collection'), + ]); + + $services->alias(StackBuilder::class, 'liip_imagine.config.stack_builder'); + + $services->set('liip_imagine.config.stack_collection', StackCollection::class) + ->public() + ->args([ + service('liip_imagine.config.stack_builder'), + '%liip_imagine.filter_sets%', + ]); + + $services->alias(StackCollection::class, 'liip_imagine.config.stack_collection'); + + // Utility services + $services->set('liip_imagine.filter.manager', FilterManager::class) + ->public() + ->args([ + service('liip_imagine.filter.configuration'), + service('liip_imagine'), + service('liip_imagine.binary.mime_type_guesser'), + ]); + + $services->alias(FilterManager::class, 'liip_imagine.filter.manager'); + + $services->set('liip_imagine.data.manager', DataManager::class) + ->public() + ->args([ + service('liip_imagine.binary.mime_type_guesser'), + service('liip_imagine.extension_guesser'), + service('liip_imagine.filter.configuration'), + '%liip_imagine.binary.loader.default%', + '%liip_imagine.default_image%', + ]); + + $services->alias(DataManager::class, 'liip_imagine.data.manager'); + + $services->set('liip_imagine.cache.manager', CacheManager::class) + ->public() + ->args([ + service('liip_imagine.filter.configuration'), + service('router'), + service('liip_imagine.cache.signer'), + service('event_dispatcher'), + '%liip_imagine.cache.resolver.default%', + '%liip_imagine.webp.generate%', + ]); + + $services->alias(CacheManager::class, 'liip_imagine.cache.manager'); + + $services->set('liip_imagine.filter.configuration', FilterConfiguration::class) + ->args(['%liip_imagine.filter_sets%']); + + $services->set('liip_imagine.service.filter', FilterService::class) + ->args([ + service('liip_imagine.data.manager'), + service('liip_imagine.filter.manager'), + service('liip_imagine.cache.manager'), + '%liip_imagine.webp.generate%', + '%liip_imagine.webp.options%', + service('logger')->ignoreOnInvalid(), + ]); + + $services->alias(FilterService::class, 'liip_imagine.service.filter'); + + // Config + $services->set('liip_imagine.controller.config', ControllerConfig::class) + ->private() + ->args(['']); + + // Controller + $services->set(ImagineController::class) + ->public() + ->args([ + service('liip_imagine.service.filter'), + service('liip_imagine.data.manager'), + service('liip_imagine.cache.signer'), + service('liip_imagine.controller.config'), + ]); + + $services->alias('liip_imagine.controller', ImagineController::class) + ->public(); + + $services->set('liip_imagine.meta_data.reader', ExifMetadataReader::class) + ->private(); + + // ImagineInterface instances + $services->alias('liip_imagine', 'liip_imagine.gd'); + + $services->alias(ImagineInterface::class, 'liip_imagine'); + + $services->set('liip_imagine.gd', Imagine::class) + ->private() + ->call('setMetadataReader', [service('liip_imagine.meta_data.reader')]); + + $services->set('liip_imagine.imagick', \Imagine\Imagick\Imagine::class) + ->private() + ->call('setMetadataReader', [service('liip_imagine.meta_data.reader')]); + + $services->set('liip_imagine.gmagick', \Imagine\Gmagick\Imagine::class) + ->private() + ->call('setMetadataReader', [service('liip_imagine.meta_data.reader')]); + + // Filter loaders + $services->set('liip_imagine.filter.loader.relative_resize', RelativeResizeFilterLoader::class) + ->tag('liip_imagine.filter.loader', ['loader' => 'relative_resize']); + + $services->set('liip_imagine.filter.loader.resize', ResizeFilterLoader::class) + ->tag('liip_imagine.filter.loader', ['loader' => 'resize']); + + $services->set('liip_imagine.filter.loader.thumbnail', ThumbnailFilterLoader::class) + ->tag('liip_imagine.filter.loader', ['loader' => 'thumbnail']); + + $services->set('liip_imagine.filter.loader.crop', CropFilterLoader::class) + ->tag('liip_imagine.filter.loader', ['loader' => 'crop']); + + $services->set('liip_imagine.filter.loader.grayscale', GrayscaleFilterLoader::class) + ->public() + ->tag('liip_imagine.filter.loader', ['loader' => 'grayscale']); + + $services->set('liip_imagine.filter.loader.paste_image', PasteFilterLoader::class) + ->args([ + service('liip_imagine'), + '%kernel.project_dir%', + ]) + ->tag('liip_imagine.filter.loader', ['loader' => 'paste_image']); + + // not officially deprecated because still injected and appears "used" + $services->set('liip_imagine.filter.loader.paste', PasteFilterLoader::class) + ->args([ + service('liip_imagine'), + '%kernel.root_dir%', + ]) + ->tag('liip_imagine.filter.loader', ['loader' => 'paste']); + + // not officially deprecated because still injected and appears "used" + $services->set('liip_imagine.filter.loader.watermark', WatermarkFilterLoader::class) + ->args([ + service('liip_imagine'), + '%kernel.root_dir%', + ]) + ->tag('liip_imagine.filter.loader', ['loader' => 'watermark']); + + $services->set('liip_imagine.filter.loader.watermark_image', WatermarkFilterLoader::class) + ->args([ + service('liip_imagine'), + '%kernel.project_dir%', + ]) + ->tag('liip_imagine.filter.loader', ['loader' => 'watermark_image']); + + $services->set('liip_imagine.filter.loader.background', BackgroundFilterLoader::class) + ->args([service('liip_imagine')]) + ->tag('liip_imagine.filter.loader', ['loader' => 'background']); + + $services->set('liip_imagine.filter.loader.strip', StripFilterLoader::class) + ->tag('liip_imagine.filter.loader', ['loader' => 'strip']); + + $services->set('liip_imagine.filter.loader.scale', ScaleFilterLoader::class) + ->tag('liip_imagine.filter.loader', ['loader' => 'scale']); + + $services->set('liip_imagine.filter.loader.upscale', UpscaleFilterLoader::class) + ->tag('liip_imagine.filter.loader', ['loader' => 'upscale']); + + $services->set('liip_imagine.filter.loader.downscale', DownscaleFilterLoader::class) + ->public() + ->tag('liip_imagine.filter.loader', ['loader' => 'downscale']); + + $services->set('liip_imagine.filter.loader.auto_rotate', AutoRotateFilterLoader::class) + ->tag('liip_imagine.filter.loader', ['loader' => 'auto_rotate']); + + $services->set('liip_imagine.filter.loader.rotate', RotateFilterLoader::class) + ->public() + ->tag('liip_imagine.filter.loader', ['loader' => 'rotate']); + + $services->set('liip_imagine.filter.loader.flip', FlipFilterLoader::class) + ->public() + ->tag('liip_imagine.filter.loader', ['loader' => 'flip']); + + $services->set('liip_imagine.filter.loader.interlace', InterlaceFilterLoader::class) + ->public() + ->tag('liip_imagine.filter.loader', ['loader' => 'interlace']); + + $services->set('liip_imagine.filter.loader.resample', ResampleFilterLoader::class) + ->public() + ->args([service('liip_imagine')]) + ->tag('liip_imagine.filter.loader', ['loader' => 'resample']); + + $services->set('liip_imagine.filter.loader.fixed', FixedFilterLoader::class) + ->public() + ->tag('liip_imagine.filter.loader', ['loader' => 'fixed']); + + // Data loaders + $services->set('liip_imagine.binary.loader.prototype.filesystem', FileSystemLoader::class) + ->args([ + service('liip_imagine.mime_type_guesser'), + service('liip_imagine.extension_guesser'), + '', // will be injected by FileSystemLoaderFactory + ]); + + $services->set('liip_imagine.binary.loader.prototype.stream', StreamLoader::class) + ->args([ + '', // will be injected by StreamLoaderFactory + '', // will be injected by StreamLoaderFactory + ]); + + $services->set('liip_imagine.binary.loader.prototype.flysystem', FlysystemLoader::class) + ->abstract() + ->args([ + service('liip_imagine.extension_guesser'), + '', // will be injected by FlysystemLoaderFactory + ]); + + $services->set('liip_imagine.binary.loader.prototype.flysystem2', FlysystemV2Loader::class) + ->abstract() + ->args([ + service('liip_imagine.extension_guesser'), + '', // will be injected by FlysystemV2LoaderFactory + ]); + + $services->set('liip_imagine.binary.loader.prototype.chain', ChainLoader::class) + ->abstract() + ->args(['']); // will be injected by ChainLoaderFactory + + // Data loader locators + $services->set('liip_imagine.binary.locator.filesystem', FileSystemLocator::class) + ->share(false) + ->private() + ->args([ + '', // will be injected by FilesystemLoaderFactory + '', // will be injected by FilesystemLoaderFactory + ]) + ->tag('liip_imagine.binary.locator', ['shared' => false]); + $services->set('liip_imagine.binary.locator.asset_mapper', AssetMapperLocator::class) + ->share(true) + ->public() + ->args([ + '', // will be injected by AssetMapperLoaderFactory + '', // will be injected by AssetMapperLoaderFactory + ]) + ->tag('liip_imagine.binary.locator', ['shared' => false]); + + $services->set('liip_imagine.binary.locator.filesystem_insecure', FileSystemInsecureLocator::class) + ->share(false) + ->private() + ->args([ + '', // will be injected by FilesystemLoaderFactory + '', // will be injected by FilesystemLoaderFactory + ]) + ->tag('liip_imagine.binary.locator', ['shared' => false]); + + // Cache resolver + $services->set('liip_imagine.cache.resolver.prototype.web_path', WebPathResolver::class) + ->public() + ->abstract() + ->args([ + service('filesystem'), + service('router.request_context'), + '', // will be injected by WebPathResolverFactory + '', // will be injected by WebPathResolverFactory + ]); + + $services->set('liip_imagine.cache.resolver.prototype.aws_s3', AwsS3Resolver::class) + ->public() + ->abstract() + ->args([ + '', // will be injected by AwsS3ResolverFactory + '', // will be injected by AwsS3ResolverFactory + '', // will be injected by AwsS3ResolverFactory + '', // will be injected by AwsS3ResolverFactory + '', // will be injected by AwsS3ResolverFactory + ]); + + $services->set('liip_imagine.cache.resolver.prototype.cache', CacheResolver::class) + ->public() + ->abstract() + ->args([ + // args will be injected by a ResolverFactory + '', + '', + ]); + + $services->set('liip_imagine.cache.resolver.prototype.flysystem', FlysystemResolver::class) + ->public() + ->abstract() + ->args([ + '', // will be injected by a ResolverFactory + service('router.request_context'), + '', // will be injected by a ResolverFactory + '', // will be injected by a ResolverFactory + '', // will be injected by a ResolverFactory + ]); + + $services->set('liip_imagine.cache.resolver.prototype.flysystem2', FlysystemV2Resolver::class) + ->public() + ->abstract() + ->args([ + '', + service('router.request_context'), + '', + '', + '', + ]); + + $services->set('liip_imagine.cache.resolver.prototype.proxy', ProxyResolver::class) + ->public() + ->abstract() + ->args([ + '', // will be injected by AwsS3ResolverFactory + '', // will be injected by AwsS3ResolverFactory + ]); + + $services->set('liip_imagine.cache.resolver.prototype.psr_cache', PsrCacheResolver::class) + ->public() + ->abstract() + ->args([ + '', // will be injected by a ResolverFactory + '', // will be injected by a ResolverFactory + ]); + + $services->set('liip_imagine.cache.resolver.no_cache_web_path', NoCacheWebPathResolver::class) + ->public() + ->args([service('router.request_context')]) + ->tag('liip_imagine.cache.resolver', ['resolver' => 'no_cache']); + + // Form types + $services->set('liip_imagine.form.type.image', ImageType::class) + ->tag('form.type', ['alias' => 'liip_imagine_image']); + + // Guessers + $services->set('liip_imagine.mime_type_guesser', 'Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface') + ->factory(['Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser', 'getInstance']); + + $services->set('liip_imagine.extension_guesser', 'Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesserInterface') + ->factory(['Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser', 'getInstance']); + + $services->set('liip_imagine.binary.mime_type_guesser', SimpleMimeTypeGuesser::class) + ->args([service('liip_imagine.mime_type_guesser')]); + + $services->set('liip_imagine.cache.signer', Signer::class) + ->public() + ->args(['%kernel.secret%']); + + // Post processors + $services->set('liip_imagine.filter.post_processor.jpegoptim', JpegOptimPostProcessor::class) + ->args([ + '%liip_imagine.jpegoptim.binary%', + '%liip_imagine.jpegoptim.stripAll%', + '%liip_imagine.jpegoptim.max%', + '%liip_imagine.jpegoptim.progressive%', + '%liip_imagine.jpegoptim.tempDir%', + ]) + ->tag('liip_imagine.filter.post_processor', ['post_processor' => 'jpegoptim']); + + $services->set('liip_imagine.filter.post_processor.optipng', OptiPngPostProcessor::class) + ->args([ + '%liip_imagine.optipng.binary%', + '%liip_imagine.optipng.level%', + '%liip_imagine.optipng.stripAll%', + '%liip_imagine.optipng.tempDir%', + ]) + ->tag('liip_imagine.filter.post_processor', ['post_processor' => 'optipng']); + + $services->set('liip_imagine.filter.post_processor.pngquant', PngquantPostProcessor::class) + ->args(['%liip_imagine.pngquant.binary%']) + ->tag('liip_imagine.filter.post_processor', ['post_processor' => 'pngquant']); + + $services->set('liip_imagine.filter.post_processor.mozjpeg', MozJpegPostProcessor::class) + ->args(['%liip_imagine.mozjpeg.binary%']) + ->tag('liip_imagine.filter.post_processor', ['post_processor' => 'mozjpeg']); + + $services->set('liip_imagine.filter.post_processor.cwebp', CwebpPostProcessor::class) + ->args([ + '%liip_imagine.cwebp.binary%', + '%liip_imagine.cwebp.tempDir%', + '%liip_imagine.cwebp.q%', + '%liip_imagine.cwebp.alphaQ%', + '%liip_imagine.cwebp.m%', + '%liip_imagine.cwebp.alphaFilter%', + '%liip_imagine.cwebp.alphaMethod%', + '%liip_imagine.cwebp.exact%', + '%liip_imagine.cwebp.metadata%', + ]) + ->tag('liip_imagine.filter.post_processor', ['post_processor' => 'cwebp']); }; diff --git a/Tests/Binary/Locator/AssetMapperLocatorTest.php b/Tests/Binary/Locator/AssetMapperLocatorTest.php index e6141b9df..d89204a85 100644 --- a/Tests/Binary/Locator/AssetMapperLocatorTest.php +++ b/Tests/Binary/Locator/AssetMapperLocatorTest.php @@ -15,10 +15,10 @@ use Liip\ImagineBundle\Binary\Locator\LocatorInterface; use Liip\ImagineBundle\Exception\Binary\Loader\NotLoadableException; use PHPUnit\Framework\TestCase; -use Symfony\Component\AssetMapper\AssetMapperInterface; -use Symfony\Component\AssetMapper\MappedAsset; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\AssetMapper\AssetMapperInterface; +use Symfony\Component\AssetMapper\MappedAsset; /** * @covers \Liip\ImagineBundle\Binary\Locator\AssetMapperLocator From 0a3730cc141056318d9f71eaebfaee416596a10d Mon Sep 17 00:00:00 2001 From: tito10047 Date: Mon, 5 Jan 2026 14:24:52 +0100 Subject: [PATCH 15/20] add asset mapper dev requirements --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index c08526455..00da3a193 100644 --- a/composer.json +++ b/composer.json @@ -43,6 +43,7 @@ "psr/cache": "^1.0|^2.0|^3.0", "psr/log": "^1.0", "symfony/asset": "^5.4|^6.4|^7.3|^8.0", + "symfony/asset-mapper": "^6.3", "symfony/browser-kit": "^5.4|^6.4|^7.3|^8.0", "symfony/cache": "^5.4|^6.4|^7.3|^8.0", "symfony/console": "^5.4|^6.4|^7.3|^8.0", From 61c62716ac57c2af574bb2a75bfecaeface180ca Mon Sep 17 00:00:00 2001 From: tito10047 Date: Mon, 5 Jan 2026 14:25:43 +0100 Subject: [PATCH 16/20] add asset mapper dev requirements --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 00da3a193..b2822706d 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,7 @@ "psr/cache": "^1.0|^2.0|^3.0", "psr/log": "^1.0", "symfony/asset": "^5.4|^6.4|^7.3|^8.0", - "symfony/asset-mapper": "^6.3", + "symfony/asset-mapper": "^5.4|^6.4|^7.3|^8.0", "symfony/browser-kit": "^5.4|^6.4|^7.3|^8.0", "symfony/cache": "^5.4|^6.4|^7.3|^8.0", "symfony/console": "^5.4|^6.4|^7.3|^8.0", From 61f03c20e564767b345098accc1a0ed19d80b965 Mon Sep 17 00:00:00 2001 From: tito10047 Date: Mon, 5 Jan 2026 14:28:34 +0100 Subject: [PATCH 17/20] remove `symfony/asset-mapper` from dependencies, add conditional dev requirement to workflow because asset mapper is available only on 6.3 version --- .github/workflows/phpunit.yml | 4 ++++ composer.json | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 03161ea9b..6e1625b8c 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -84,6 +84,10 @@ jobs: SYMFONY_DEPRECATIONS_HELPER: max[self]=0 run: vendor/bin/simple-phpunit -v + - name: Add asset mapper + if: ${{ matrix.symfony >= '6.4' }} + run: composer require --no-update symfony/asset-mapper --dev + - name: Install php-coveralls run: composer global require --no-interaction --no-progress php-coveralls/php-coveralls diff --git a/composer.json b/composer.json index b2822706d..c08526455 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,6 @@ "psr/cache": "^1.0|^2.0|^3.0", "psr/log": "^1.0", "symfony/asset": "^5.4|^6.4|^7.3|^8.0", - "symfony/asset-mapper": "^5.4|^6.4|^7.3|^8.0", "symfony/browser-kit": "^5.4|^6.4|^7.3|^8.0", "symfony/cache": "^5.4|^6.4|^7.3|^8.0", "symfony/console": "^5.4|^6.4|^7.3|^8.0", From 20f306113e9952c06d5d6afca2758707d9386fe0 Mon Sep 17 00:00:00 2001 From: Jozef Mostka Date: Mon, 5 Jan 2026 14:41:39 +0100 Subject: [PATCH 18/20] Update .github/workflows/phpstan.yml Co-authored-by: David Buchmann --- .github/workflows/phpstan.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index eba4f35e8..5045a2bd9 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -27,7 +27,6 @@ jobs: run: composer require rokka/imagine-vips - name: Add asset mapper - if: ${{ matrix.php-version >= '8.0' || (matrix.php-version == '' && 8.2 >= 8.0) }} run: composer require symfony/asset-mapper - name: Run PHPStan From 299bbea0fcc36a2e1e2923d59742c9a38c48245d Mon Sep 17 00:00:00 2001 From: Jozef Mostka Date: Mon, 5 Jan 2026 14:41:46 +0100 Subject: [PATCH 19/20] Update .github/workflows/phpunit.yml Co-authored-by: David Buchmann --- .github/workflows/phpunit.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 6e1625b8c..12ea0adc9 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -85,6 +85,7 @@ jobs: run: vendor/bin/simple-phpunit -v - name: Add asset mapper + # asset mapper support is optional in LiipImagineBundle. asset mapper was only added in symfony 6 if: ${{ matrix.symfony >= '6.4' }} run: composer require --no-update symfony/asset-mapper --dev From ae06e8f6bf9b3f75c6d58cee119bf470946c076a Mon Sep 17 00:00:00 2001 From: tito10047 Date: Mon, 5 Jan 2026 14:56:34 +0100 Subject: [PATCH 20/20] Remove cache from asset AssetMapperLocator.php and add better documentation explaining why is this necessary --- Binary/Locator/AssetMapperLocator.php | 68 ++++++----------- .../Loader/AssetMapperLoaderFactory.php | 1 - Resources/config/imagine.php | 1 - .../Binary/Locator/AssetMapperLocatorTest.php | 76 ------------------- 4 files changed, 22 insertions(+), 124 deletions(-) diff --git a/Binary/Locator/AssetMapperLocator.php b/Binary/Locator/AssetMapperLocator.php index 1a6717601..20d3418e0 100644 --- a/Binary/Locator/AssetMapperLocator.php +++ b/Binary/Locator/AssetMapperLocator.php @@ -12,74 +12,50 @@ namespace Liip\ImagineBundle\Binary\Locator; use Liip\ImagineBundle\Exception\Binary\Loader\NotLoadableException; -use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\AssetMapper\AssetMapperInterface; /** - * Class responsible for locating assets in the asset mapper. - * Implements the LocatorInterface to resolve asset paths. - * This Class should be used only in dev environment. + * Resolves asset paths using the Symfony AssetMapper component. + * + * In a development environment, AssetMapper serves assets from their source directories (typically `/assets`) + * without versioning. However, Twig's `asset()` helper generates public paths that include version hashes. + * Since these versioned files do not physically exist on disk during development, this locator is used + * to map the requested public versioned path back to the actual source file on the filesystem. + * + * In production, AssetMapper compiles and copies assets to the public directory with their version hashes + * included in the filenames. Consequently, this locator is no longer necessary as the files exist at + * their versioned paths. */ class AssetMapperLocator implements LocatorInterface { - private $assetMapper; - private $cacheMapCache; - public function __construct( - AssetMapperInterface $assetMapper, - ?CacheItemPoolInterface $cacheMapCache = null + private AssetMapperInterface $assetMapper, ) { - $this->cacheMapCache = $cacheMapCache; - $this->assetMapper = $assetMapper; } /** - * Locates an asset by its public path. + * Locates the physical source file for a given public asset path. * - * This method attempts to retrieve an asset using its public path. It first - * checks for a cached version of the asset, and if none is found, it iterates - * through all available assets to find a match. If no matching asset is located, - * an exception is thrown. + * This method iterates through all registered assets in the AssetMapper to find a match + * for the provided public path (which may contain a version hash). If a match is found, + * it returns the absolute path to the original source file. * * Inspired by Symfony's AssetMapper component @see https://github.com/symfony/asset-mapper/blob/7.3/AssetMapperDevServerSubscriber.php#L179. * - * @param string $path the public path of the asset to locate + * @param string $path the public path of the asset (e.g., as generated by Twig's asset() helper) * - * @throws NotLoadableException if no asset with the specified public path is found + * @throws NotLoadableException if the public path cannot be resolved to a source asset * - * @return string the located asset + * @return string the absolute path to the source asset file */ public function locate(string $path): string { - $pathInfo = '/'.ltrim($path, '/'); - $cachedAsset = null; - if (null !== $this->cacheMapCache) { - $cachedAsset = $this->cacheMapCache->getItem(hash('xxh128', $pathInfo)); - $asset = $cachedAsset->isHit() ? $this->assetMapper->getAsset($cachedAsset->get()) : null; - - if (null !== $asset && $asset->publicPath === $pathInfo) { - return $asset->sourcePath; - } - } - - // we did not find a match - $asset = null; + $path = '/'.mb_ltrim($path, '/'); foreach ($this->assetMapper->allAssets() as $assetCandidate) { - if ($pathInfo === $assetCandidate->publicPath) { - $asset = $assetCandidate; - break; + if ($path === $assetCandidate->publicPath) { + return $assetCandidate->sourcePath; } } - - if (null === $asset) { - throw new NotLoadableException(\sprintf('Asset with public path "%s" not found.', $pathInfo)); - } - - if (null !== $cachedAsset) { - $cachedAsset->set($asset->logicalPath); - $this->cacheMapCache->save($cachedAsset); - } - - return $asset->sourcePath; + throw new NotLoadableException(\sprintf('Asset with public path "%s" not found.', $path)); } } diff --git a/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php b/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php index 4c68030f7..f6400e87d 100644 --- a/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php +++ b/DependencyInjection/Factory/Loader/AssetMapperLoaderFactory.php @@ -22,7 +22,6 @@ public function create(ContainerBuilder $container, $loaderName, array $config) { $locatorDefinition = new ChildDefinition('liip_imagine.binary.locator.asset_mapper'); $locatorDefinition->replaceArgument(0, new Reference('asset_mapper')); - $locatorDefinition->replaceArgument(1, new Reference('cache.asset_mapper')); $definition = $this->getChildLoaderDefinition('filesystem'); diff --git a/Resources/config/imagine.php b/Resources/config/imagine.php index d4c6611b2..61f2c220e 100644 --- a/Resources/config/imagine.php +++ b/Resources/config/imagine.php @@ -435,7 +435,6 @@ ->public() ->args([ '', // will be injected by AssetMapperLoaderFactory - '', // will be injected by AssetMapperLoaderFactory ]) ->tag('liip_imagine.binary.locator', ['shared' => false]); diff --git a/Tests/Binary/Locator/AssetMapperLocatorTest.php b/Tests/Binary/Locator/AssetMapperLocatorTest.php index d89204a85..3c755b19a 100644 --- a/Tests/Binary/Locator/AssetMapperLocatorTest.php +++ b/Tests/Binary/Locator/AssetMapperLocatorTest.php @@ -15,8 +15,6 @@ use Liip\ImagineBundle\Binary\Locator\LocatorInterface; use Liip\ImagineBundle\Exception\Binary\Loader\NotLoadableException; use PHPUnit\Framework\TestCase; -use Psr\Cache\CacheItemInterface; -use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\AssetMapper\AssetMapperInterface; use Symfony\Component\AssetMapper\MappedAsset; @@ -58,80 +56,6 @@ public function testLocateWithoutCache(): void $this->assertSame($sourcePath, $result); } - public function testLocateWithCacheHit(): void - { - $path = 'images/logo.png'; - $pathInfo = '/images/logo.png'; - $logicalPath = 'logo.png'; - $sourcePath = '/path/to/logo.png'; - - $asset = new MappedAsset($logicalPath, $sourcePath, $pathInfo, $pathInfo); - - $cacheItem = $this->createMock(CacheItemInterface::class); - $cacheItem->expects($this->once()) - ->method('isHit') - ->willReturn(true); - $cacheItem->expects($this->once()) - ->method('get') - ->willReturn($logicalPath); - - $cache = $this->createMock(CacheItemPoolInterface::class); - $cache->expects($this->once()) - ->method('getItem') - ->with(hash('xxh128', $pathInfo)) - ->willReturn($cacheItem); - - $assetMapper = $this->createMock(AssetMapperInterface::class); - $assetMapper->expects($this->once()) - ->method('getAsset') - ->with($logicalPath) - ->willReturn($asset); - $assetMapper->expects($this->never()) - ->method('allAssets'); - - $locator = new AssetMapperLocator($assetMapper, $cache); - $result = $locator->locate($path); - - $this->assertSame($sourcePath, $result); - } - - public function testLocateWithCacheMissAndSave(): void - { - $path = 'images/logo.png'; - $pathInfo = '/images/logo.png'; - $logicalPath = 'logo.png'; - $sourcePath = '/path/to/logo.png'; - - $asset = new MappedAsset($logicalPath, $sourcePath, $pathInfo, $pathInfo); - - $cacheItem = $this->createMock(CacheItemInterface::class); - $cacheItem->expects($this->once()) - ->method('isHit') - ->willReturn(false); - $cacheItem->expects($this->once()) - ->method('set') - ->with($logicalPath); - - $cache = $this->createMock(CacheItemPoolInterface::class); - $cache->expects($this->once()) - ->method('getItem') - ->with(hash('xxh128', $pathInfo)) - ->willReturn($cacheItem); - $cache->expects($this->once()) - ->method('save') - ->with($cacheItem); - - $assetMapper = $this->createMock(AssetMapperInterface::class); - $assetMapper->expects($this->once()) - ->method('allAssets') - ->willReturn([$asset]); - - $locator = new AssetMapperLocator($assetMapper, $cache); - $result = $locator->locate($path); - - $this->assertSame($sourcePath, $result); - } - public function testThrowsIfAssetNotFound(): void { $path = 'images/logo.png';