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

tito10047 / progressive-image-bundle / 20675431244

03 Jan 2026 09:29AM UTC coverage: 88.617% (+0.3%) from 88.338%
20675431244

push

github

tito10047
add TTL support for image caching: implement configurable TTL handling in cache services, adapt Twig components, and update tests to validate TTL functionality

12 of 12 new or added lines in 3 files covered. (100.0%)

120 existing lines in 9 files now uncovered.

615 of 694 relevant lines covered (88.62%)

184.23 hits per line

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

76.3
/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\Event\TransparentImageCacheSubscriber;
23
use Tito10047\ProgressiveImageBundle\Resolver\AssetMapperResolver;
24
use Tito10047\ProgressiveImageBundle\Resolver\FileSystemResolver;
25
use Tito10047\ProgressiveImageBundle\Service\LiipImagineRuntimeConfigGenerator;
26
use Tito10047\ProgressiveImageBundle\Service\MetadataReader;
27
use Tito10047\ProgressiveImageBundle\Service\PreloadCollector;
28
use Tito10047\ProgressiveImageBundle\Service\ResponsiveAttributeGenerator;
29
use Tito10047\ProgressiveImageBundle\Twig\Components\Image;
30
use Tito10047\ProgressiveImageBundle\Twig\TransparentCacheExtension;
31
use Tito10047\ProgressiveImageBundle\UrlGenerator\LiipImagineResponsiveImageUrlGenerator;
32
use Tito10047\ProgressiveImageBundle\UrlGenerator\ResponsiveImageUrlGeneratorInterface;
33

34
final class ProgressiveImageExtension extends Extension implements PrependExtensionInterface
35
{
36
    public function getAlias(): string
37
    {
38
        return 'progressive_image';
347✔
39
    }
40

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

59
        $configs = $builder->getExtensionConfig($this->getAlias());
347✔
60
        $configs = $this->processConfiguration(new Configuration(), $configs);
347✔
61

62
        if (isset($configs['responsive_strategy']['breakpoints'])) {
347✔
63
            $breakpoints = $configs['responsive_strategy']['breakpoints'];
×
64
            $liipConfigs = $builder->getExtensionConfig('liip_imagine');
×
65

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

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

88
                            $newFilterSets[$newSetName] = $newSetConfig;
×
89
                        }
90
                    }
91
                }
92
            }
93

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

102
    public function load(array $configs, ContainerBuilder $container): void
103
    {
104
        $configs = $this->processConfiguration(new Configuration(), $configs);
347✔
105

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

110
        $loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../../config'));
347✔
111
        $loader->load('services.php');
347✔
112

113
        $this->configureResolvers($configs, $container);
347✔
114

115
        $driver = $configs['driver'] ?? 'gd';
347✔
116
        $analyzerId = match ($driver) {
347✔
117
            'imagick' => 'progressive_image.analyzer.imagick',
×
118
            'gd' => 'progressive_image.analyzer.gd',
347✔
119
            default => $driver,
×
120
        };
347✔
121

122
        $resolver = $configs['resolver'] ?? 'default';
347✔
123
        $maybeService = 'progressive_image.resolver.'.$resolver;
347✔
124
        if ($container->hasDefinition($maybeService)) {
347✔
125
            $resolverId = $maybeService;
347✔
126
        } else {
127
            $resolverId = $resolver;
×
128
        }
129
        $loaderId = $configs['loader'] ?? 'progressive_image.filesystem.loader';
347✔
130
        $cacheId = $configs['cache'] ?? 'cache.app';
347✔
131
        $imageCacheServiceId = $configs['image_cache_service'] ?? 'cache.app';
347✔
132
        $imageCacheEnabled = $configs['image_cache_enabled'] ?? false;
347✔
133
        $ttl = $configs['ttl'] ?? null;
347✔
134

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

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

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

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

166
        if (class_exists(LiipImagineBundle::class)) {
347✔
UNCOV
167
            $container->register(LiipImagineResponsiveImageUrlGenerator::class)
336✔
UNCOV
168
                ->setArgument('$cacheManager', new Reference('liip_imagine.cache.manager'))
336✔
UNCOV
169
                ->setArgument('$router', new Reference('router'))
336✔
UNCOV
170
                ->setArgument('$uriSigner', new Reference('uri_signer'))
336✔
UNCOV
171
                ->setArgument('$runtimeConfigGenerator', new Reference(LiipImagineRuntimeConfigGenerator::class))
336✔
UNCOV
172
                ->setArgument('$filterConfiguration', new Reference('liip_imagine.filter.configuration'))
336✔
UNCOV
173
                ->setArgument('$cache', $imageCacheServiceReference)
336✔
UNCOV
174
                ->setPublic(true);
336✔
175

UNCOV
176
            $container->setAlias(ResponsiveImageUrlGeneratorInterface::class, LiipImagineResponsiveImageUrlGenerator::class);
336✔
177
        }
178
        $responsiveConfig = $configs['responsive_strategy'] ?? [];
347✔
179
        $generatorId = $responsiveConfig['generator'] ?? null;
347✔
180

181
        if ($generatorId || class_exists(LiipImagineBundle::class)) {
347✔
UNCOV
182
            $container->register(ResponsiveAttributeGenerator::class, ResponsiveAttributeGenerator::class)
336✔
UNCOV
183
                ->setArgument('$gridConfig', $responsiveConfig['grid'] ?? [])
336✔
UNCOV
184
                ->setArgument('$ratioConfig', $responsiveConfig['ratios'] ?? [])
336✔
UNCOV
185
                ->setArgument('$preloadCollector', new Reference(PreloadCollector::class))
336✔
UNCOV
186
                ->setArgument('$urlGenerator', $generatorId ? new Reference($generatorId) : new Reference(ResponsiveImageUrlGeneratorInterface::class))
336✔
UNCOV
187
            ;
336✔
188
        }
189

190
        $container->register(Image::class, Image::class)
347✔
191
            ->setArgument('$analyzer', new Reference(MetadataReader::class))
347✔
192
            ->setArgument('$pathDecorator', array_map(fn ($id) => new Reference($id), $configs['path_decorators'] ?? []))
347✔
193
            ->setArgument('$responsiveAttributeGenerator', $generatorId || class_exists(LiipImagineBundle::class) ? new Reference(ResponsiveAttributeGenerator::class) : null)
347✔
194
            ->setArgument('$preloadCollector', new Reference(PreloadCollector::class))
347✔
195
            ->setShared(false)
347✔
196
            ->addTag('twig.component')
347✔
197
            ->setPublic(true);
347✔
198
    }
199

200
    /**
201
     * @param array<string, mixed> $config
202
     */
203
    private function configureResolvers(array $config, ContainerBuilder $container): void
204
    {
205
        $resolvers = $config['resolvers'] ?? [];
347✔
206
        foreach ($resolvers as $name => $resolverConfig) {
347✔
207
            $id = 'progressive_image.resolver.'.$name;
185✔
208

209
            if ('filesystem' === $resolverConfig['type']) {
185✔
210
                $container->register($id, FileSystemResolver::class)
185✔
211
                    ->setArgument('$roots', $resolverConfig['roots'] ?? ['%kernel.project_dir%/public'])
185✔
212
                    ->setArgument('$allowUnresolvable', $resolverConfig['allowUnresolvable'] ?? false);
185✔
213
            } elseif ('asset_mapper' === $resolverConfig['type']) {
×
214
                $container->register($id, AssetMapperResolver::class);
×
215
            }
216
            // Chain resolver logic can be added here if needed
217
        }
218

219
        if (isset($config['resolver']) && !isset($resolvers[$config['resolver']])) {
347✔
220
            // If a default resolver type is used but not defined in resolvers array
221
            if (in_array($config['resolver'], ['filesystem', 'asset_mapper'])) {
×
222
                // handle basic types if they are used as string directly
223
            }
224
        }
225

226
        // Register a default alias if possible
227
        if (isset($config['resolver']) && isset($resolvers[$config['resolver']])) {
347✔
228
            $container->setAlias('progressive_image.resolver.default', 'progressive_image.resolver.'.$config['resolver']);
185✔
229
        } elseif (!empty($resolvers)) {
162✔
230
            $firstResolver = array_key_first($resolvers);
×
231
            $container->setAlias('progressive_image.resolver.default', 'progressive_image.resolver.'.$firstResolver);
×
232
        } else {
233
            // Fallback if no resolvers defined, register a basic one to avoid ServiceNotFoundException
234
            $container->register('progressive_image.resolver.default', FileSystemResolver::class)
162✔
235
                ->setArgument('$roots', ['%kernel.project_dir%/public'])
162✔
236
                ->setArgument('$allowUnresolvable', true);
162✔
237
        }
238
    }
239
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc