• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tito10047 / progressive-image-bundle / 21061797997

16 Jan 2026 09:22AM UTC coverage: 90.395% (+0.2%) from 90.185%
21061797997

push

github

web-flow
Merge pull request #1 from tito10047/rentina

Add retina image generation support

23 of 26 new or added lines in 4 files covered. (88.46%)

800 of 885 relevant lines covered (90.4%)

286.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.35
/src/DependencyInjection/ProgressiveImageExtension.php
1
<?php
2

3
/*
4
 * This file is part of the Progressive Image Bundle.
5
 *
6
 * (c) Jozef Môstka <https://github.com/tito10047/progressive-image-bundle>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
namespace Tito10047\ProgressiveImageBundle\DependencyInjection;
13

14
use Liip\ImagineBundle\LiipImagineBundle;
15
use Symfony\Component\Config\FileLocator;
16
use Symfony\Component\DependencyInjection\ContainerBuilder;
17
use Symfony\Component\DependencyInjection\Extension\Extension;
18
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
19
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
20
use Symfony\Component\DependencyInjection\Parameter;
21
use Symfony\Component\DependencyInjection\Reference;
22
use Tito10047\ProgressiveImageBundle\Command\GenerateCustomCssCommand;
23
use Tito10047\ProgressiveImageBundle\Event\TransparentImageCacheSubscriber;
24
use Tito10047\ProgressiveImageBundle\Resolver\AssetMapperResolver;
25
use Tito10047\ProgressiveImageBundle\Resolver\ChainResolver;
26
use Tito10047\ProgressiveImageBundle\Resolver\FileSystemResolver;
27
use Tito10047\ProgressiveImageBundle\Service\LiipImagineRuntimeConfigGenerator;
28
use Tito10047\ProgressiveImageBundle\Service\LiipImagineRuntimeConfigGeneratorInterface;
29
use Tito10047\ProgressiveImageBundle\Service\MetadataReader;
30
use Tito10047\ProgressiveImageBundle\Service\PreloadCollector;
31
use Tito10047\ProgressiveImageBundle\Service\ResponsiveAttributeGenerator;
32
use Tito10047\ProgressiveImageBundle\Twig\Components\Image;
33
use Tito10047\ProgressiveImageBundle\Twig\TransparentCacheExtension;
34
use Tito10047\ProgressiveImageBundle\UrlGenerator\LiipImagineResponsiveImageUrlGenerator;
35
use Tito10047\ProgressiveImageBundle\UrlGenerator\ResponsiveImageUrlGeneratorInterface;
36

37
final class ProgressiveImageExtension extends Extension implements PrependExtensionInterface
38
{
39
    public function getAlias(): string
40
    {
41
        return 'progressive_image';
589✔
42
    }
43

44
    public function prepend(ContainerBuilder $builder): void
45
    {
46
        $builder->prependExtensionConfig('framework', [
589✔
47
            'asset_mapper' => [
589✔
48
                'paths' => [
589✔
49
                    __DIR__.'/../../assets' => 'tito10047/progressive-image-bundle',
589✔
50
                ],
589✔
51
            ],
589✔
52
        ]);
589✔
53
        $builder->prependExtensionConfig('twig_component', [
589✔
54
            'defaults' => [
589✔
55
                'Tito10047\ProgressiveImageBundle\Twig\Components\\' => [
589✔
56
                    'template_directory' => '@ProgressiveImage/components/',
589✔
57
                    'name_prefix' => 'pgi',
589✔
58
                ],
589✔
59
            ],
589✔
60
        ]);
589✔
61

62
        $configs = $builder->getExtensionConfig($this->getAlias());
589✔
63
        $configs = $this->processConfiguration(new Configuration(), $configs);
589✔
64

65
        if (isset($configs['responsive_strategy']['breakpoints'])) {
589✔
66
            $breakpoints = $configs['responsive_strategy']['breakpoints'];
×
67
            $liipConfigs = $builder->getExtensionConfig('liip_imagine');
×
68

69
            $newFilterSets = [];
×
70
            foreach ($liipConfigs as $liipConfig) {
×
71
                if (isset($liipConfig['filter_sets'])) {
×
72
                    foreach ($liipConfig['filter_sets'] as $setName => $setConfig) {
×
73
                        foreach ($breakpoints as $breakpointName => $width) {
×
74
                            $newSetName = $setName.'_'.$breakpointName;
×
75
                            if (isset($newFilterSets[$newSetName])) {
×
76
                                continue;
×
77
                            }
78
                            $newSetConfig = $setConfig;
×
79

80
                            if (isset($newSetConfig['filters']['thumbnail']['size'])) {
×
81
                                [$origWidth, $origHeight] = $newSetConfig['filters']['thumbnail']['size'];
×
82
                                if ($origWidth > 0 && $origHeight > 0) {
×
83
                                    $ratio = $origHeight / $origWidth;
×
84
                                    $newHeight = (int) round($width * $ratio);
×
85
                                    $newSetConfig['filters']['thumbnail']['size'] = [$width, $newHeight];
×
86
                                } else {
87
                                    $newSetConfig['filters']['thumbnail']['size'] = [$width, $width];
×
88
                                }
89
                            }
90

91
                            $newFilterSets[$newSetName] = $newSetConfig;
×
92
                        }
93
                    }
94
                }
95
            }
96

97
            if (!empty($newFilterSets)) {
×
98
                $builder->prependExtensionConfig('liip_imagine', [
×
99
                    'filter_sets' => $newFilterSets,
×
100
                ]);
×
101
            }
102
        }
103
    }
104

105
    public function load(array $configs, ContainerBuilder $container): void
106
    {
107
        $configs = $this->processConfiguration(new Configuration(), $configs);
589✔
108

109
        if (!isset($container->getParameter('kernel.bundles')['TwigBundle'])) {
589✔
110
            throw new \LogicException('The TwigBundle is not registered in your application. Try running "composer require symfony/twig-bundle".');
×
111
        }
112

113
        $loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../../config'));
589✔
114
        $loader->load('services.php');
589✔
115

116
        $this->configureResolvers($configs, $container);
589✔
117

118
        $driver = $configs['driver'] ?? 'gd';
589✔
119
        $analyzerId = match ($driver) {
589✔
120
            'imagick' => 'progressive_image.analyzer.imagick',
×
121
            'gd' => 'progressive_image.analyzer.gd',
589✔
122
            default => $driver,
×
123
        };
589✔
124

125
        $loaderId = $configs['loader'] ?? 'progressive_image.filesystem.loader';
589✔
126
        $cacheId = $configs['cache'] ?? 'cache.app';
589✔
127
        $imageCacheServiceId = $configs['image_cache_service'] ?? 'cache.app';
589✔
128
        $imageCacheEnabled = $configs['image_cache_enabled'] ?? false;
589✔
129
        $ttl = $configs['ttl'] ?? null;
589✔
130
                $retinaConfig = $configs['retina'] ?? ['enabled' => true, 'multipliers' => [1, 2]];
589✔
131
                $retina = $retinaConfig['enabled'] ?? true;
589✔
132
                $retinaMultipliers = $retinaConfig['multipliers'] ?? [1, 2];
589✔
133

134
        if (!$imageCacheEnabled) {
589✔
135
            $imageCacheServiceReference = null;
15✔
136
        } else {
137
            $imageCacheServiceReference = new Reference('progressive_image.image_cache_service');
574✔
138
        }
139

140
        $definition = $container->getDefinition(MetadataReader::class);
589✔
141
        $definition->setArgument('$analyzer', new Reference($analyzerId))
589✔
142
            ->setArgument('$loader', new Reference($loaderId))
589✔
143
                        ->setArgument('$pathResolver', new Reference('progressive_image.resolver.default'))
589✔
144
            ->setArgument('$cache', new Reference($cacheId))
589✔
145
            ->setArgument('$ttl', $configs['ttl'] ?? null)
589✔
146
            ->setArgument('$fallbackPath', $configs['fallback_image'] ?? null)
589✔
147
        ;
589✔
148
        $container->setParameter('progressive_image.image_cache_enabled', $imageCacheEnabled);
589✔
149
        $container->setParameter('progressive_image.ttl', $ttl);
589✔
150
                $container->setParameter('progressive_image.image_configs', $configs['image_configs'] ?? []);
589✔
151
        $container->setAlias('progressive_image.image_cache_service', $imageCacheServiceId);
589✔
152

153
        $container->register(TransparentCacheExtension::class)
589✔
154
            ->setArgument('$ttl', new Parameter('progressive_image.ttl'))
589✔
155
            ->setArgument('$cache', $imageCacheServiceReference)
589✔
156
            ->addTag('twig.extension')
589✔
157
        ;
589✔
158

159
        $container->register(TransparentImageCacheSubscriber::class)
589✔
160
            ->setArgument('$enabled', new Parameter('progressive_image.image_cache_enabled'))
589✔
161
            ->setArgument('$cache', $imageCacheServiceReference)
589✔
162
            ->setArgument('$ttl', new Parameter('progressive_image.ttl'))
589✔
163
            ->addTag('kernel.event_subscriber')
589✔
164
        ;
589✔
165

166
        if (class_exists(LiipImagineBundle::class)) {
589✔
167
                        $container->register(LiipImagineRuntimeConfigGenerator::class)
574✔
168
                                ->setArgument('$filterConfiguration', new Reference('liip_imagine.filter.configuration'))
574✔
169
                                ->setArgument('$imageConfigs', new Parameter('progressive_image.image_configs'));
574✔
170

171
            $container->register(LiipImagineResponsiveImageUrlGenerator::class)
574✔
172
                ->setArgument('$cacheManager', new Reference('liip_imagine.cache.manager'))
574✔
173
                ->setArgument('$router', new Reference('router'))
574✔
174
                ->setArgument('$uriSigner', new Reference('uri_signer'))
574✔
175
                ->setArgument('$runtimeConfigGenerator', new Reference(LiipImagineRuntimeConfigGenerator::class))
574✔
176
                ->setArgument('$filterConfiguration', new Reference('liip_imagine.filter.configuration'))
574✔
177
                                ->setArgument('$requestStack', new Reference('request_stack'))
574✔
178
                ->setArgument('$cache', $imageCacheServiceReference)
574✔
179
                                ->setArgument('$webpGenerate', new Parameter('liip_imagine.webp.generate'))
574✔
180
                ->setPublic(true);
574✔
181

182
                        $container->setAlias(ResponsiveImageUrlGeneratorInterface::class, LiipImagineResponsiveImageUrlGenerator::class)->setPublic(true);
574✔
183
                        $container->setAlias(LiipImagineRuntimeConfigGeneratorInterface::class, LiipImagineRuntimeConfigGenerator::class)->setPublic(true);
574✔
184
        }
185
        $responsiveConfig = $configs['responsive_strategy'] ?? [];
589✔
186
        $generatorId = $responsiveConfig['generator'] ?? null;
589✔
187

188
        if ($generatorId || class_exists(LiipImagineBundle::class)) {
589✔
189
            $container->register(ResponsiveAttributeGenerator::class, ResponsiveAttributeGenerator::class)
574✔
190
                ->setArgument('$gridConfig', $responsiveConfig['grid'] ?? [])
574✔
191
                ->setArgument('$ratioConfig', $responsiveConfig['ratios'] ?? [])
574✔
NEW
192
                                ->setArgument('$retinaMultipliers', $retinaMultipliers)
574✔
193
                ->setArgument('$preloadCollector', new Reference(PreloadCollector::class))
574✔
194
                ->setArgument('$urlGenerator', $generatorId ? new Reference($generatorId) : new Reference(ResponsiveImageUrlGeneratorInterface::class))
574✔
195
            ;
574✔
196
        }
197

198
                $container->register(GenerateCustomCssCommand::class)
589✔
199
                        ->setArgument('$gridConfig', $responsiveConfig['grid'] ?? [])
589✔
200
                        ->setArgument('$projectDir', new Parameter('kernel.project_dir'))
589✔
201
                        ->addTag('console.command');
589✔
202

203
        $container->register(Image::class, Image::class)
589✔
204
            ->setArgument('$analyzer', new Reference(MetadataReader::class))
589✔
205
            ->setArgument('$pathDecorator', array_map(fn ($id) => new Reference($id), $configs['path_decorators'] ?? []))
589✔
206
            ->setArgument('$responsiveAttributeGenerator', $generatorId || class_exists(LiipImagineBundle::class) ? new Reference(ResponsiveAttributeGenerator::class) : null)
589✔
207
            ->setArgument('$preloadCollector', new Reference(PreloadCollector::class))
589✔
208
                        ->setArgument('$framework', $configs['responsive_strategy']['grid']['framework'] ?? 'custom')
589✔
209
                        ->setArgument('$defaultRetina', $retina)
589✔
210
            ->setShared(false)
589✔
211
            ->addTag('twig.component')
589✔
212
            ->setPublic(true);
589✔
213
    }
214

215
    /**
216
     * @param array<string, mixed> $config
217
     */
218
    private function configureResolvers(array $config, ContainerBuilder $container): void
219
    {
220
        $resolvers = $config['resolvers'] ?? [];
589✔
221
        foreach ($resolvers as $name => $resolverConfig) {
589✔
222
            $id = 'progressive_image.resolver.'.$name;
384✔
223

224
            if ('filesystem' === $resolverConfig['type']) {
384✔
225
                $container->register($id, FileSystemResolver::class)
384✔
226
                    ->setArgument('$roots', $resolverConfig['roots'] ?? ['%kernel.project_dir%/public'])
384✔
227
                                        ->setArgument('$allowUnresolvable', $resolverConfig['allowUnresolvable'] ?? true);
384✔
228
            } elseif ('asset_mapper' === $resolverConfig['type']) {
15✔
229
                                $container->register($id, AssetMapperResolver::class)
15✔
230
                                        ->setArgument('$assetMapper', new Reference('asset_mapper'));
15✔
231
                        } elseif ('chain' === $resolverConfig['type']) {
15✔
232
                                $childResolvers = array_map(fn($name) => new Reference('progressive_image.resolver.' . $name), $resolverConfig['resolvers'] ?? []);
15✔
233
                                $container->register($id, ChainResolver::class)
15✔
234
                                        ->setArgument('$resolvers', $childResolvers);
15✔
235
            }
236
        }
237

238
                $resolver = $config['resolver'] ?? 'default';
589✔
239

240
                if (isset($resolvers[$resolver])) {
589✔
241
                        $container->setAlias('progressive_image.resolver.default', 'progressive_image.resolver.' . $resolver);
369✔
242
                } elseif (in_array($resolver, ['filesystem', 'asset_mapper'])) {
220✔
243
                        $container->setAlias('progressive_image.resolver.default', 'progressive_image.resolver.' . $resolver);
×
244
                } elseif (!empty($resolvers) && 'default' === $resolver) {
220✔
245
            $firstResolver = array_key_first($resolvers);
15✔
246
            $container->setAlias('progressive_image.resolver.default', 'progressive_image.resolver.'.$firstResolver);
15✔
247
        } else {
248
            $container->register('progressive_image.resolver.default', FileSystemResolver::class)
205✔
249
                ->setArgument('$roots', ['%kernel.project_dir%/public'])
205✔
250
                ->setArgument('$allowUnresolvable', true);
205✔
251
        }
252
    }
253
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc