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

68publishers / image-storage / 22677854876

04 Mar 2026 04:07PM UTC coverage: 87.038% (-4.3%) from 91.356%
22677854876

push

github

tg666
Added port to Referer header if exists

0 of 3 new or added lines in 1 file covered. (0.0%)

113 existing lines in 8 files now uncovered.

1484 of 1705 relevant lines covered (87.04%)

0.87 hits per line

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

94.5
/src/Bridge/Nette/DI/ImageStorageExtension.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace SixtyEightPublishers\ImageStorage\Bridge\Nette\DI;
6

7
use Intervention\Image\ImageManager;
8
use League\Flysystem\Config as FlysystemConfig;
9
use League\Flysystem\FilesystemOperator;
10
use League\Flysystem\Visibility;
11
use Nette\Application\IPresenterFactory;
12
use Nette\DI\CompilerExtension;
13
use Nette\DI\Definitions\Reference;
14
use Nette\DI\Definitions\ServiceDefinition;
15
use Nette\DI\Definitions\Statement;
16
use Nette\Routing\Router;
17
use Nette\Schema\Expect;
18
use Nette\Schema\Schema;
19
use SixtyEightPublishers\FileStorage\Bridge\Nette\DI\Config\FilesystemConfig;
20
use SixtyEightPublishers\FileStorage\Bridge\Nette\DI\Config\StorageConfig as FileStorageConfig;
21
use SixtyEightPublishers\FileStorage\Bridge\Nette\DI\FileStorageConsoleExtension;
22
use SixtyEightPublishers\FileStorage\Bridge\Nette\DI\FileStorageDefinitionFactoryInterface;
23
use SixtyEightPublishers\FileStorage\Bridge\Nette\DI\FileStorageExtension;
24
use SixtyEightPublishers\FileStorage\Bridge\Symfony\Console\Configurator\CleanCommandConfiguratorInterface;
25
use SixtyEightPublishers\FileStorage\Cleaner\StorageCleanerInterface;
26
use SixtyEightPublishers\FileStorage\Config\ConfigInterface;
27
use SixtyEightPublishers\FileStorage\Exception\RuntimeException;
28
use SixtyEightPublishers\FileStorage\Resource\ResourceFactoryInterface;
29
use SixtyEightPublishers\ImageStorage\Bridge\Intervention\Image\ImageManager\ImageManagerFactory;
30
use SixtyEightPublishers\ImageStorage\Bridge\Intervention\Image\ImageManager\ImageManagerFactoryInterface;
31
use SixtyEightPublishers\ImageStorage\Bridge\Nette\Application\ImageServerPresenter;
32
use SixtyEightPublishers\ImageStorage\Bridge\Nette\Application\ImageServerRoute;
33
use SixtyEightPublishers\ImageStorage\Bridge\Nette\DI\Config\ImageStorageConfig;
34
use SixtyEightPublishers\ImageStorage\Bridge\Nette\DI\Config\PresetConfig;
35
use SixtyEightPublishers\ImageStorage\Bridge\Nette\DI\Config\StorageConfig;
36
use SixtyEightPublishers\ImageStorage\Bridge\Nette\ImageServer\ResponseFactory;
37
use SixtyEightPublishers\ImageStorage\Bridge\Symfony\Console\Configurator\CleanCommandConfigurator;
38
use SixtyEightPublishers\ImageStorage\Cleaner\StorageCleaner;
39
use SixtyEightPublishers\ImageStorage\Config\Config;
40
use SixtyEightPublishers\ImageStorage\Config\NoImageConfig;
41
use SixtyEightPublishers\ImageStorage\Config\NoImageConfigInterface;
42
use SixtyEightPublishers\ImageStorage\Filesystem\Filesystem;
43
use SixtyEightPublishers\ImageStorage\Filesystem\MountManager;
44
use SixtyEightPublishers\ImageStorage\ImageServer\ExternalImageServerFactory;
45
use SixtyEightPublishers\ImageStorage\ImageServer\ImageServerFactoryInterface;
46
use SixtyEightPublishers\ImageStorage\ImageServer\LocalImageServerFactory;
47
use SixtyEightPublishers\ImageStorage\ImageServer\ResponseFactoryInterface;
48
use SixtyEightPublishers\ImageStorage\ImageStorage;
49
use SixtyEightPublishers\ImageStorage\ImageStorageInterface;
50
use SixtyEightPublishers\ImageStorage\Info\InfoFactory;
51
use SixtyEightPublishers\ImageStorage\Info\InfoFactoryInterface;
52
use SixtyEightPublishers\ImageStorage\LinkGenerator\LinkGenerator;
53
use SixtyEightPublishers\ImageStorage\LinkGenerator\LinkGeneratorInterface;
54
use SixtyEightPublishers\ImageStorage\Modifier;
55
use SixtyEightPublishers\ImageStorage\Modifier\Applicator;
56
use SixtyEightPublishers\ImageStorage\Modifier\Codec\Codec;
57
use SixtyEightPublishers\ImageStorage\Modifier\Codec\PresetCodec;
58
use SixtyEightPublishers\ImageStorage\Modifier\Collection\ModifierCollection;
59
use SixtyEightPublishers\ImageStorage\Modifier\Collection\ModifierCollectionFactoryInterface;
60
use SixtyEightPublishers\ImageStorage\Modifier\Facade\ModifierFacadeFactory;
61
use SixtyEightPublishers\ImageStorage\Modifier\Facade\ModifierFacadeFactoryInterface;
62
use SixtyEightPublishers\ImageStorage\Modifier\Facade\ModifierFacadeInterface;
63
use SixtyEightPublishers\ImageStorage\Modifier\Preset\Preset;
64
use SixtyEightPublishers\ImageStorage\Modifier\Preset\PresetCollection;
65
use SixtyEightPublishers\ImageStorage\Modifier\Preset\PresetCollectionFactoryInterface;
66
use SixtyEightPublishers\ImageStorage\Modifier\Validator;
67
use SixtyEightPublishers\ImageStorage\NoImage\NoImageResolver;
68
use SixtyEightPublishers\ImageStorage\NoImage\NoImageResolverInterface;
69
use SixtyEightPublishers\ImageStorage\Persistence\ImagePersister;
70
use SixtyEightPublishers\ImageStorage\Persistence\ImagePersisterInterface;
71
use SixtyEightPublishers\ImageStorage\Resource\ResourceFactory;
72
use SixtyEightPublishers\ImageStorage\Responsive\Descriptor\WDescriptor;
73
use SixtyEightPublishers\ImageStorage\Responsive\Descriptor\XDescriptor;
74
use SixtyEightPublishers\ImageStorage\Responsive\SrcSetGeneratorFactoryInterface;
75
use SixtyEightPublishers\ImageStorage\Security\KnownModifiers;
76
use SixtyEightPublishers\ImageStorage\Security\SignatureStrategy;
77
use SixtyEightPublishers\ImageStorage\Security\SignatureStrategyInterface;
78
use function array_diff;
79
use function array_fill_keys;
80
use function array_keys;
81
use function array_map;
82
use function array_merge;
83
use function array_unique;
84
use function assert;
85
use function is_array;
86
use function is_string;
87
use function is_subclass_of;
88
use function sprintf;
89

90
final class ImageStorageExtension extends CompilerExtension implements FileStorageDefinitionFactoryInterface
91
{
92
    public const DRIVER_GD = 'gd';
93
    public const DRIVER_IMAGICK = 'imagick';
94
    public const DRIVER_68PUBLISHERS_IMAGICK = '68publishers.imagick';
95

96
    public const IMAGE_SERVER_LOCAL = 'local';
97
    public const IMAGE_SERVER_EXTERNAL = 'external';
98

99
    /** @var array<string> */
100
    private array $managed = [];
101

102
    /** @var array<string, mixed> */
103
    private array $routes = [];
104

105
    private bool $imageServerPresenterRegistered = false;
106

107
    public function getConfigSchema(): Schema
108
    {
109
        return Expect::structure([
1✔
110
            'driver' => Expect::anyOf(Statement::class, self::DRIVER_GD, self::DRIVER_IMAGICK, self::DRIVER_68PUBLISHERS_IMAGICK)
1✔
111
                ->default(self::DRIVER_GD)
1✔
112
                ->dynamic(),
1✔
113
            'storages' => Expect::arrayOf(
1✔
114
                Expect::structure([
1✔
115
                    'source_filesystem' => Expect::structure([
1✔
116
                        'adapter' => Expect::anyOf(Expect::string(), Expect::type(Statement::class))
1✔
117
                            ->required()
1✔
118
                            ->before(static function ($factory) {
1✔
119
                                return $factory instanceof Statement ? $factory : new Statement($factory);
1✔
120
                            }),
1✔
121
                        'config' => Expect::array([ # @phpstan-ignore-line
1✔
122
                            FlysystemConfig::OPTION_VISIBILITY => Visibility::PRIVATE,
1✔
123
                            FlysystemConfig::OPTION_DIRECTORY_VISIBILITY => Visibility::PRIVATE,
1✔
124
                        ])->mergeDefaults(),
1✔
125
                    ])->castTo(FilesystemConfig::class),
1✔
126

127
                    'server' => Expect::anyOf(self::IMAGE_SERVER_LOCAL, self::IMAGE_SERVER_EXTERNAL)
1✔
128
                        ->default(self::IMAGE_SERVER_LOCAL),
1✔
129
                    'route' => Expect::bool(false),
1✔
130

131
                    'no_image' => Expect::arrayOf('string', 'string')
1✔
132
                        ->default([]),
1✔
133
                    'no_image_patterns' => Expect::arrayOf('string', 'string')
1✔
134
                        ->default([]),
1✔
135
                    'presets' => Expect::arrayOf(
1✔
136
                        Expect::structure([
1✔
137
                            'modifiers' => Expect::arrayOf(Expect::scalar(), 'string'),
1✔
138
                            'w' => Expect::listOf(Expect::int())->default([]),
1✔
139
                            'x' => Expect::listOf(Expect::anyOf(Expect::int(), Expect::float()))->default([]),
1✔
140
                            'defaultW' => Expect::int()->nullable(),
1✔
141
                            'defaultX' => Expect::anyOf(Expect::int(), Expect::float())->nullable(),
1✔
142
                        ])->castTo(PresetConfig::class)
1✔
143
                            ->assert(
1✔
144
                                handler: static fn (PresetConfig $preset): bool => !([] !== $preset->w && [] !== $preset->x),
1✔
145
                                description: 'A preset cannot have both "w" and "x" properties.',
1✔
146
                            ),
147
                        'string',
148
                    )->default([]),
1✔
149

150
                    'modifiers' => Expect::listOf('string|' . Statement::class),
1✔
151
                    'applicators' => Expect::listOf('string|' . Statement::class),
1✔
152
                    'validators' => Expect::listOf('string|' . Statement::class),
1✔
153
                ])->before(function (array $config): array {
1✔
154
                    $config['modifiers'] = $this->normalizeListOfStatementsWithDefaults(
1✔
155
                        is_array($config['modifiers'] ?? null) ? $config['modifiers'] : ['@default'],
1✔
156
                        [
157
                            new Statement(Modifier\Original::class),
1✔
158
                            new Statement(Modifier\Height::class),
1✔
159
                            new Statement(Modifier\Width::class),
1✔
160
                            new Statement(Modifier\AspectRatio::class),
1✔
161
                            new Statement(Modifier\Fit::class),
1✔
162
                            new Statement(Modifier\PixelDensity::class),
1✔
163
                            new Statement(Modifier\Orientation::class),
1✔
164
                            new Statement(Modifier\Quality::class),
1✔
165
                        ],
166
                    );
167

168
                    $config['applicators'] = $this->normalizeListOfStatementsWithDefaults(
1✔
169
                        is_array($config['applicators'] ?? null) ? $config['applicators'] : ['@default'],
1✔
170
                        [
171
                            new Statement(Applicator\Orientation::class),
1✔
172
                            new Statement(Applicator\Resize::class),
1✔
173
                            new Statement(Applicator\Format::class),
1✔
174
                            new Statement(Applicator\StripMeta::class), # must be last
1✔
175
                        ],
176
                    );
177

178
                    $config['validators'] = $this->normalizeListOfStatementsWithDefaults(
1✔
179
                        is_array($config['validators'] ?? null) ? $config['validators'] : ['@default'],
1✔
180
                        [
181
                            new Statement(Validator\AllowedResolutionValidator::class),
1✔
182
                            new Statement(Validator\AllowedPixelDensityValidator::class),
1✔
183
                            new Statement(Validator\AllowedQualityValidator::class),
1✔
184
                        ],
185
                    );
186

187
                    return $config;
1✔
188
                })->castTo(StorageConfig::class),
1✔
189
            ),
190
        ])->castTo(ImageStorageConfig::class);
1✔
191
    }
192

193
    public function loadConfiguration(): void
194
    {
195
        if (0 >= count($this->compiler->getExtensions(FileStorageExtension::class))) {
1✔
196
            throw new RuntimeException(sprintf(
1✔
197
                'The extension %s can be used only with %s.',
1✔
198
                self::class,
1✔
199
                FileStorageExtension::class,
1✔
200
            ));
201
        }
202

203
        $builder = $this->getContainerBuilder();
1✔
204
        $config = $this->getConfig();
1✔
205
        assert($config instanceof ImageStorageConfig);
206

207
        # Image manager
208
        $builder->addDefinition($this->prefix('image_manager_factory'))
1✔
209
            ->setAutowired(false)
1✔
210
            ->setType(ImageManagerFactoryInterface::class)
1✔
211
            ->setFactory(ImageManagerFactory::class);
1✔
212

213
        $builder->addDefinition($this->prefix('image_manager'))
1✔
214
            ->setType(ImageManager::class)
1✔
215
            ->setFactory([$this->prefix('@image_manager_factory'), 'create'], [
1✔
216
                ['driver' => $config->driver],
1✔
217
            ]);
218

219
        # Modifier collection factory
220
        $builder->addFactoryDefinition($this->prefix('modifiers.modifier_collection_factory'))
1✔
221
            ->setAutowired(false)
1✔
222
            ->setImplement(ModifierCollectionFactoryInterface::class)
1✔
223
            ->getResultDefinition()
1✔
224
            ->setFactory(ModifierCollection::class);
1✔
225

226
        # Preset collection factory
227
        $builder->addFactoryDefinition($this->prefix('modifiers.preset_collection_factory'))
1✔
228
            ->setAutowired(false)
1✔
229
            ->setImplement(PresetCollectionFactoryInterface::class)
1✔
230
            ->getResultDefinition()
1✔
231
            ->setFactory(PresetCollection::class);
1✔
232

233
        # Modifier facade factory
234
        $builder->addDefinition($this->prefix('modifiers.modifier_facade_factory'))
1✔
235
            ->setAutowired(false)
1✔
236
            ->setType(ModifierFacadeFactoryInterface::class)
1✔
237
            ->setFactory(ModifierFacadeFactory::class, [
1✔
238
                new Reference($this->prefix('modifiers.preset_collection_factory')),
1✔
239
                new Reference($this->prefix('modifiers.modifier_collection_factory')),
1✔
240
            ]);
241

242
        # Responsive - srcset generator factory
243
        $builder->addFactoryDefinition($this->prefix('responsive.srcset_generator_factory'))
1✔
244
            ->setAutowired(false)
1✔
245
            ->setImplement(SrcSetGeneratorFactoryInterface::class);
1✔
246

247
        # Image server - response factory
248
        $builder->addDefinition($this->prefix('image_server_response_factory'))
1✔
249
            ->setAutowired(false)
1✔
250
            ->setType(ResponseFactoryInterface::class)
1✔
251
            ->setFactory(ResponseFactory::class);
1✔
252

253
        # Custom storage cleaner
254
        $builder->addDefinition($this->prefix('storage_cleaner'))
1✔
255
            ->setAutowired(false)
1✔
256
            ->setType(StorageCleanerInterface::class)
1✔
257
            ->setFactory(StorageCleaner::class);
1✔
258

259
        # Console - extends clean command configurator if the FileStorageConsoleExtension is registered
260
        if (0 < \count($this->compiler->getExtensions(FileStorageConsoleExtension::class))) {
1✔
261
            $builder->addDefinition($this->prefix('configurator.clean_command'))
1✔
262
                ->setType(CleanCommandConfiguratorInterface::class)
1✔
263
                ->setFactory(CleanCommandConfigurator::class)
1✔
264
                ->addTag(FileStorageConsoleExtension::TAG_CLEAN_COMMAND_CONFIGURATOR)
1✔
265
                ->setAutowired(false);
1✔
266
        }
267
    }
1✔
268

269
    public function beforeCompile(): void
270
    {
271
        $builder = $this->getContainerBuilder();
1✔
272
        $config = $this->getConfig();
1✔
273
        assert($config instanceof ImageStorageConfig);
274

275
        $diff = array_diff(array_keys($config->storages), $this->managed);
1✔
276

277
        if (0 < \count($diff)) {
1✔
278
            throw new RuntimeException(sprintf(
1✔
279
                'Missing definition for a storage with the name "%s" in the configuration of the extension %s.',
1✔
280
                array_shift($diff),
1✔
281
                FileStorageExtension::class,
1✔
282
            ));
283
        }
284

285
        $storageCleanerDecorator = $builder->getDefinition($this->prefix('storage_cleaner'));
1✔
286
        $defaultStorageCleaner = $builder->getDefinitionByType(StorageCleanerInterface::class);
1✔
287
        assert($storageCleanerDecorator instanceof ServiceDefinition && $defaultStorageCleaner instanceof ServiceDefinition);
288

289
        $storageCleanerDecorator->setArguments([$defaultStorageCleaner]);
1✔
290
        $storageCleanerDecorator->setAutowired();
1✔
291
        $defaultStorageCleaner->setAutowired(false);
1✔
292

293
        if (empty($this->routes)) {
1✔
294
            return;
1✔
295
        }
296

297
        $presenterFactory = $builder->getDefinitionByType(IPresenterFactory::class);
1✔
298
        $router = $builder->getDefinitionByType(Router::class);
1✔
299
        assert($presenterFactory instanceof ServiceDefinition && $router instanceof ServiceDefinition);
300

301
        $presenterFactory->addSetup('setMapping', [
1✔
302
            [
303
                'ImageStorage' => ['SixtyEightPublishers\\ImageStorage\\Bridge\\Nette\\Application', '*', '*Presenter'],
1✔
304
            ],
305
        ]);
306

307
        foreach ($this->routes as $storageName => $basePath) {
1✔
308
            $router->addSetup('prepend', [
1✔
309
                'router' => new Statement(ImageServerRoute::class, [
1✔
310
                    $storageName,
1✔
311
                    $basePath,
1✔
312
                ]),
313
            ]);
314
        }
315
    }
1✔
316

317
    public function canCreateFileStorage(string $name, FileStorageConfig $config): bool
318
    {
319
        $extensionConfig = $this->getConfig();
1✔
320
        assert($extensionConfig instanceof ImageStorageConfig);
321

322
        return isset($extensionConfig->storages[$name]);
1✔
323
    }
324

325
    public function createFileStorage(string $name, FileStorageConfig $config): ServiceDefinition
326
    {
327
        $builder = $this->getContainerBuilder();
1✔
328
        $extensionConfig = $this->getConfig();
1✔
329
        assert($extensionConfig instanceof ImageStorageConfig);
330

331
        $this->managed[] = $name;
1✔
332
        $imageStorageConfig = $extensionConfig->storages[$name];
1✔
333

334
        $builder->addDefinition($this->prefix('filesystem.' . $name))
1✔
335
            ->setType(FilesystemOperator::class)
1✔
336
            ->setFactory(MountManager::class, [
1✔
337
                [
338
                    ImagePersisterInterface::FILESYSTEM_NAME_CACHE => new Statement(Filesystem::class, [
1✔
339
                        $config->filesystem->adapter,
1✔
340
                        $config->filesystem->config,
1✔
341
                    ]),
342
                    ImagePersisterInterface::FILESYSTEM_NAME_SOURCE => new Statement(Filesystem::class, [
1✔
343
                        $imageStorageConfig->source_filesystem->adapter,
1✔
344
                        $imageStorageConfig->source_filesystem->config,
1✔
345
                    ]),
346
                ],
347
            ])
348
            ->setAutowired(false);
1✔
349

350
        $builder->addDefinition($this->prefix('config.' . $name))
1✔
351
            ->setType(ConfigInterface::class)
1✔
352
            ->setFactory(Config::class, [$config->config])
1✔
353
            ->setAutowired(false);
1✔
354

355
        $presets = array_map(
1✔
356
            callback: static fn (PresetConfig $preset): Statement => new Statement(Preset::class, [
1✔
357
                'modifiers' => $preset->modifiers,
1✔
358
                'descriptor' => match (true) {
359
                    [] !== $preset->w => new Statement(WDescriptor::class, $preset->w),
1✔
360
                    [] !== $preset->x => new Statement(XDescriptor::class, $preset->x),
1✔
361
                    default => null,
1✔
362
                },
363
                'defaultDescriptorValue' => match (true) {
364
                    [] !== $preset->w => $preset->defaultW,
1✔
365
                    [] !== $preset->x => $preset->defaultX,
1✔
366
                    default => null,
1✔
367
                },
368
            ]),
1✔
369
            array: $imageStorageConfig->presets,
1✔
370
        );
371

372
        $builder->addDefinition($this->prefix('modifier_facade.' . $name))
1✔
373
            ->setType(ModifierFacadeInterface::class)
1✔
374
            ->setFactory(new Statement([$this->prefix('@modifiers.modifier_facade_factory'), 'create'], [
1✔
375
                new Reference($this->prefix('config.' . $name)),
1✔
376
            ]))
377
            ->addSetup('setModifiers', [$imageStorageConfig->modifiers])
1✔
378
            ->addSetup('setPresets', [$presets])
1✔
379
            ->addSetup('setApplicators', [$imageStorageConfig->applicators])
1✔
380
            ->addSetup('setValidators', [$imageStorageConfig->validators])
1✔
381
            ->setAutowired(false);
1✔
382

383
        $builder->addDefinition($this->prefix('resource_factory.' . $name))
1✔
384
            ->setType(ResourceFactoryInterface::class)
1✔
385
            ->setFactory(ResourceFactory::class, [
1✔
386
                new Reference($this->prefix('filesystem.' . $name)),
1✔
387
                new Reference($this->prefix('image_manager')),
1✔
388
                new Reference($this->prefix('modifier_facade.' . $name)),
1✔
389
            ])
390
            ->setAutowired(false);
1✔
391

392
        # signature enabled
393
        if (!empty($config->config[Config::SIGNATURE_KEY] ?? '')) {
1✔
394
            $signatureStrategyDefinition = $builder->addDefinition($this->prefix('signature_strategy.' . $name))
1✔
395
                ->setType(SignatureStrategyInterface::class)
1✔
396
                ->setFactory(SignatureStrategy::class, [
1✔
397
                    'config' => new Reference($this->prefix('config.' . $name)),
1✔
398
                    'knownModifiers' => new Statement(KnownModifiers::class, [
1✔
399
                        'list' => $this->buildKnownModifiers(
1✔
400
                            config: $config,
1✔
401
                            extConfig: $imageStorageConfig,
402
                        ),
403
                    ]),
404
                ])
405
                ->setAutowired(false);
1✔
406
        }
407

408
        $builder->addDefinition($this->prefix('link_generator.' . $name))
1✔
409
            ->setType(LinkGeneratorInterface::class)
1✔
410
            ->setFactory(LinkGenerator::class, [
1✔
411
                new Reference($this->prefix('config.' . $name)),
1✔
412
                new Reference($this->prefix('modifier_facade.' . $name)),
1✔
413
                new Reference($this->prefix('responsive.srcset_generator_factory')),
1✔
414
                $signatureStrategyDefinition ?? null,
1✔
415
            ])
416
            ->setAutowired(false);
1✔
417

418
        $builder->addDefinition($this->prefix('image_persister.' . $name))
1✔
419
            ->setType(ImagePersisterInterface::class)
1✔
420
            ->setFactory(ImagePersister::class, [
1✔
421
                new Reference($this->prefix('filesystem.' . $name)),
1✔
422
                new Reference($this->prefix('config.' . $name)),
1✔
423
            ])
424
            ->setAutowired(false);
1✔
425

426
        $builder->addDefinition($this->prefix('info_factory.' . $name))
1✔
427
            ->setType(InfoFactoryInterface::class)
1✔
428
            ->setFactory(InfoFactory::class, [
1✔
429
                new Reference($this->prefix('modifier_facade.' . $name)),
1✔
430
                new Reference($this->prefix('link_generator.' . $name)),
1✔
431
                $name,
1✔
432
            ])
433
            ->setAutowired(false);
1✔
434

435
        $noImages = $imageStorageConfig->no_image;
1✔
436
        $defaultNoImage = $noImages['default'] ?? null;
1✔
437

438
        if (null !== $defaultNoImage) {
1✔
439
            unset($noImages['default']);
1✔
440
        }
441

442
        $builder->addDefinition($this->prefix('no_image_config.' . $name))
1✔
443
            ->setType(NoImageConfigInterface::class)
1✔
444
            ->setFactory(NoImageConfig::class, [
1✔
445
                $defaultNoImage,
1✔
446
                $noImages,
1✔
447
                $imageStorageConfig->no_image_patterns,
1✔
448
            ])
449
            ->setAutowired(false);
1✔
450

451
        $builder->addDefinition($this->prefix('no_image_resolver.' . $name))
1✔
452
            ->setType(NoImageResolverInterface::class)
1✔
453
            ->setFactory(NoImageResolver::class, [
1✔
454
                new Reference($this->prefix('info_factory.' . $name)),
1✔
455
                new Reference($this->prefix('no_image_config.' . $name)),
1✔
456
            ])
457
            ->setAutowired(false);
1✔
458

459
        $imageServerDefinition = $builder->addDefinition($this->prefix('image_server_factory.' . $name))
1✔
460
            ->setType(ImageServerFactoryInterface::class)
1✔
461
            ->setAutowired(false);
1✔
462

463
        switch ($imageStorageConfig->server) {
1✔
464
            case self::IMAGE_SERVER_LOCAL:
1✔
465
                if ($imageStorageConfig->route && '' === ($config->config[ConfigInterface::BASE_PATH] ?? '')) {
1✔
466
                    throw new RuntimeException(sprintf(
1✔
467
                        'Unable to register a route for an image storage with the name "%s". Please set a configuration option "%s".',
1✔
468
                        $name,
469
                        ConfigInterface::BASE_PATH,
1✔
470
                    ));
471
                }
472

473
                if ($imageStorageConfig->route) {
1✔
474
                    $this->registerImageServerPresenter();
1✔
475
                    $this->routes[$name] = $config->config[ConfigInterface::BASE_PATH];
1✔
476
                }
477

478
                $imageServerDefinition->setFactory(LocalImageServerFactory::class, [
1✔
479
                    new Reference($this->prefix('image_server_response_factory')),
1✔
480
                ]);
481

482
                break;
1✔
483
            case self::IMAGE_SERVER_EXTERNAL:
1✔
484
                if ($imageStorageConfig->route) {
1✔
485
                    throw new RuntimeException(sprintf(
1✔
486
                        'Unable to register a route for an image storage with the name "%s" because a server is set as external.',
1✔
487
                        $name,
488
                    ));
489
                }
490

491
                $imageServerDefinition->setFactory(ExternalImageServerFactory::class);
1✔
492

493
                break;
1✔
494
        }
495

496
        return $builder->addDefinition($this->prefix('image_storage.' . $name))
1✔
497
            ->setType(ImageStorageInterface::class)
1✔
498
            ->setFactory(ImageStorage::class, [
1✔
499
                $name,
1✔
500
                new Reference($this->prefix('config.' . $name)),
1✔
501
                new Reference($this->prefix('resource_factory.' . $name)),
1✔
502
                new Reference($this->prefix('link_generator.' . $name)),
1✔
503
                new Reference($this->prefix('image_persister.' . $name)),
1✔
504
                new Reference($this->prefix('no_image_resolver.' . $name)),
1✔
505
                new Reference($this->prefix('info_factory.' . $name)),
1✔
506
                $imageServerDefinition,
1✔
507
            ])
508
            ->setAutowired(false);
1✔
509
    }
510

511
    /**
512
     * @param array<string|Statement> $items
513
     * @param array<Statement>        $defaults
514
     *
515
     * @return array<Statement>
516
     */
517
    private function normalizeListOfStatementsWithDefaults(array $items, array $defaults): array
518
    {
519
        $statements = [];
1✔
520
        $defaultsMerged = false;
1✔
521

522
        foreach ($items as $item) {
1✔
523
            if (!$defaultsMerged && '@default' === $item) {
1✔
524
                foreach ($defaults as $default) {
1✔
525
                    $statements[] = $default;
1✔
526
                }
527

528
                $defaultsMerged = true;
1✔
529

530
                continue;
1✔
531
            }
532

533
            if (!$item instanceof Statement) {
1✔
534
                $item = new Statement($item);
1✔
535
            }
536

537
            $statements[] = $item;
1✔
538
        }
539

540
        return $statements;
1✔
541
    }
542

543
    private function registerImageServerPresenter(): void
544
    {
545
        if ($this->imageServerPresenterRegistered) {
1✔
546
            return;
1✔
547
        }
548

549
        $this->getContainerBuilder()
1✔
550
            ->addDefinition($this->prefix('presenter.image_server'))
1✔
551
            ->setType(ImageServerPresenter::class);
1✔
552

553
        $this->imageServerPresenterRegistered = true;
1✔
554
    }
1✔
555

556
    /**
557
     * @return array<string, true>
558
     */
559
    private function buildKnownModifiers(FileStorageConfig $config, StorageConfig $extConfig): array
560
    {
561
        $presets = array_map(
1✔
562
            callback: static function (PresetConfig $conf): Preset {
1✔
UNCOV
563
                $descriptor = match (true) {
×
UNCOV
564
                    [] !== $conf->w => new WDescriptor(...$conf->w),
×
UNCOV
565
                    [] !== $conf->x => new XDescriptor(...$conf->x),
×
UNCOV
566
                    default => null,
×
567
                };
568

UNCOV
569
                $defaultDescriptorValue = match (true) {
×
UNCOV
570
                    [] !== $conf->w => $conf->defaultW,
×
UNCOV
571
                    [] !== $conf->x => $conf->defaultX,
×
UNCOV
572
                    default => null,
×
573
                };
574

UNCOV
575
                return new Preset(
×
UNCOV
576
                    modifiers: $conf->modifiers,
×
577
                    descriptor: $descriptor,
578
                    defaultDescriptorValue: $defaultDescriptorValue,
579
                );
580
            },
1✔
581
            array: $extConfig->presets,
1✔
582
        );
583

584
        $modifiers = array_map(
1✔
585
            callback: static function (Statement $modifier): Modifier\ModifierInterface {
1✔
586
                $entity = $modifier->getEntity();
1✔
587
                $params = $modifier->arguments;
1✔
588
                assert(is_string($entity) && is_subclass_of($entity, Modifier\AbstractModifier::class) && is_array($params));
589

590
                return new $entity(...$params);
1✔
591
            },
1✔
592
            array: $extConfig->modifiers,
1✔
593
        );
594

595
        $presetsCollection = new PresetCollection();
1✔
596

597
        foreach ($presets as $name => $preset) {
1✔
UNCOV
598
            $presetsCollection->add(presetAlias: $name, preset: $preset);
×
599
        }
600

601
        $modifierCollection = new ModifierCollection();
1✔
602

603
        foreach ($modifiers as $modifier) {
1✔
604
            $modifierCollection->add(modifier: $modifier);
1✔
605
        }
606

607
        $cnf = new Config($config->config);
1✔
608

609
        $codec = new PresetCodec(
1✔
610
            codec: new Codec(
1✔
611
                config: $cnf,
1✔
612
                modifierCollection: $modifierCollection,
613
            ),
614
            config: $cnf,
615
            modifierCollection: $modifierCollection,
616
            presetCollection: $presetsCollection,
617
        );
618

619
        $known = [];
1✔
620

621
        foreach ($presets as $preset) {
1✔
UNCOV
622
            if (null === $preset->descriptor) {
×
UNCOV
623
                $known[] = $codec->modifiersToPath($preset->modifiers);
×
624

UNCOV
625
                continue;
×
626
            }
627

UNCOV
628
            $modifiers = $preset->modifiers;
×
629

UNCOV
630
            foreach ($preset->descriptor->iterateModifiers($modifierCollection) as $mod) {
×
UNCOV
631
                $known[] = $codec->modifiersToPath(array_merge(
×
UNCOV
632
                    $modifiers,
×
633
                    $mod,
634
                ));
635
            }
636
        }
637

638
        return array_fill_keys(
1✔
639
            keys: array_unique($known),
1✔
640
            value: true,
1✔
641
        );
642
    }
643
}
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