Passed
Push — main ( 422f08...35cd69 )
by Fabian
08:22 queued 02:49
created

Service::getRendererName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Fabiang\AsseticBundle;
6
7
use Assetic\Asset\AssetCollection;
8
use Assetic\AssetManager;
9
use Assetic\FilterManager as AsseticFilterManager;
10
use Assetic\Contracts\Factory\Worker\WorkerInterface;
11
use Assetic\Factory\AssetFactory;
12
use Assetic\AssetWriter;
13
use Assetic\Contracts\Asset\AssetInterface;
14
use Assetic\Asset\AssetCache;
15
use Assetic\Cache\FilesystemCache;
16
use Laminas\View\Renderer\RendererInterface as Renderer;
17
use Fabiang\AsseticBundle\View\StrategyInterface;
18
use Fabiang\AsseticBundle\Exception\InvalidArgumentException;
19
20
class Service
21
{
22
23
    public const DEFAULT_ROUTE_NAME = 'default';
24
25
    protected string $routeName      = self::DEFAULT_ROUTE_NAME;
26
    protected ?string $controllerName = null;
27
    protected ?string $actionName     = null;
28
    protected Configuration $configuration;
29
30
    /**
31
     * @var array<string, StrategyInterface>
32
     */
33
    protected array $strategy            = [];
34
    protected ?AssetManager $assetManager        = null;
35
    protected ?AssetWriter $assetWriter         = null;
36
    protected ?WorkerInterface $cacheBusterStrategy = null;
37
    protected ?AsseticFilterManager $filterManager       = null;
38
39 1
    public function __construct(Configuration $configuration)
40
    {
41 1
        $this->configuration = $configuration;
42
    }
43
44 3
    public function setRouteName(string $routeName): void
45
    {
46 3
        $this->routeName = $routeName;
47
    }
48
49 3
    public function getRouteName(): string
50
    {
51 3
        return $this->routeName;
52
    }
53
54 1
    public function setAssetManager(AssetManager $assetManager): void
55
    {
56 1
        $this->assetManager = $assetManager;
57
    }
58
59 6
    public function getAssetManager(): AssetManager
60
    {
61 6
        if (null === $this->assetManager) {
62 5
            $this->assetManager = new AssetManager();
63
        }
64
65 6
        return $this->assetManager;
66
    }
67
68 4
    public function getAssetWriter(): AssetWriter
69
    {
70 4
        if (null === $this->assetWriter) {
71 4
            $webPath = $this->configuration->getWebPath();
72 4
            $this->assetWriter = new AssetWriter($webPath ?? '');
73
        }
74
75 4
        return $this->assetWriter;
76
    }
77
78 1
    public function setAssetWriter(AssetWriter $assetWriter): void
79
    {
80 1
        $this->assetWriter = $assetWriter;
81
    }
82
83 1
    public function getCacheBusterStrategy(): ?WorkerInterface
84
    {
85 1
        return $this->cacheBusterStrategy;
86
    }
87
88 1
    public function setCacheBusterStrategy(?WorkerInterface $cacheBusterStrategy): void
89
    {
90 1
        $this->cacheBusterStrategy = $cacheBusterStrategy;
91
    }
92
93 1
    public function setFilterManager(AsseticFilterManager $filterManager): void
94
    {
95 1
        $this->filterManager = $filterManager;
96
    }
97
98 2
    public function getFilterManager(): AsseticFilterManager
99
    {
100 2
        if (null === $this->filterManager) {
101 1
            $this->filterManager = new AsseticFilterManager();
102
        }
103
104 2
        return $this->filterManager;
105
    }
106
107 1
    public function setControllerName(?string $controllerName): void
108
    {
109 1
        $this->controllerName = $controllerName;
110
    }
111
112 2
    public function getControllerName(): ?string
113
    {
114 2
        return $this->controllerName;
115
    }
116
117 1
    public function setActionName(?string $actionName): void
118
    {
119 1
        $this->actionName = $actionName;
120
    }
121
122 2
    public function getActionName(): ?string
123
    {
124 2
        return $this->actionName;
125
    }
126
127
    /**
128
     * Build collection of assets.
129
     */
130 3
    public function build(): void
131
    {
132 3
        $moduleConfiguration = $this->configuration->getModules();
133 3
        foreach ($moduleConfiguration as $configuration) {
134 3
            $factory     = $this->createAssetFactory($configuration);
135 3
            $collections = (array) $configuration['collections'];
136 3
            foreach ($collections as $name => $options) {
137 3
                $this->prepareCollection($options, $name, $factory);
138
            }
139
        }
140
    }
141
142 3
    private function cacheAsset(AssetInterface $asset): AssetInterface
143
    {
144 3
        if ($this->configuration->getCacheEnabled()) {
145
            return new AssetCache(
146
                $asset,
147
                new FilesystemCache($this->configuration->getCachePath())
148
            );
149
        }
150 3
        return $asset;
151
    }
152
153 3
    private function initFilters(array $filters): array
154
    {
155 3
        $result = [];
156
157 3
        $fm = $this->getFilterManager();
158
159 3
        foreach ($filters as $alias => $options) {
160 3
            $option = null;
161 3
            $name   = null;
162 3
            if (is_array($options)) {
163 3
                if (!isset($options['name'])) {
164
                    throw new Exception\InvalidArgumentException(
165
                        'Filter "' . $alias . '" required option "name"'
166
                    );
167
                }
168
169 3
                $name   = $options['name'];
170 3
                $option = isset($options['option']) ? $options['option'] : null;
171
            } elseif (is_string($options)) {
172
                $name = $options;
173
                unset($options);
174
            }
175
176 3
            if (!is_string($name)) {
177
                throw new InvalidArgumentException(
178
                    'Name of filter could not be found. '
179
                    . 'Did you provide the `name` option to the filter config?'
180
                );
181
            }
182
183 3
            if (is_numeric($alias)) {
184
                $alias = $name;
185
            }
186
187
            // Filter Id should have optional filter indicator "?"
188 3
            $filterId = ltrim($alias, '?');
189
190 3
            if (!$fm->has($filterId)) {
191 3
                if (is_array($option) && !empty($option)) {
192
                    /** @var class-string $name */
193
                    $r      = new \ReflectionClass($name);
194
                    /** @var \Assetic\Contracts\Filter\FilterInterface $filter */
195
                    $filter = $r->newInstanceArgs($option);
196 3
                } elseif ($option) {
197
                    /** @var \Assetic\Contracts\Filter\FilterInterface $filter */
198
                    $filter = new $name($option);
199
                } else {
200
                    /** @var \Assetic\Contracts\Filter\FilterInterface $filter */
201 3
                    $filter = new $name();
202
                }
203
204 3
                $fm->set($filterId, $filter);
205
            }
206
207 3
            $result[] = $alias;
208
        }
209
210 3
        return $result;
211
    }
212
213
    public function setupRenderer(Renderer $renderer): bool
214
    {
215
        $controllerConfig = $this->getControllerConfig();
216
        $actionConfig     = $this->getActionConfig();
217
        $config           = array_merge($controllerConfig, $actionConfig);
218
219
        if (count($config) == 0) {
220
            $config = $this->getRouterConfig();
221
        }
222
223
        // If we don't have any assets listed by now, or if we are mixing in
224
        // the default assets, then merge in the default assets to the config array
225
        $defaultConfig = $this->getDefaultConfig();
226
        if (count($config) == 0 || (isset($defaultConfig['options']['mixin']) && $defaultConfig['options']['mixin'])) {
227
            $config = array_merge($defaultConfig['assets'], $config);
228
        }
229
230
        if (count($config) > 0) {
231
            $this->setupRendererFromOptions($renderer, $config);
232
233
            return true;
234
        }
235
236
        return false;
237
    }
238
239
    public function getDefaultConfig(): array
240
    {
241
        $defaultDefinition = $this->configuration->getDefault();
242
243
        return $defaultDefinition ? $defaultDefinition : [];
244
    }
245
246
    /**
247
     * @return array|mixed
248
     */
249
    public function getRouterConfig()
250
    {
251
        $assetOptions = $this->configuration->getRoute($this->getRouteName());
252
253
        return $assetOptions ? $assetOptions : [];
254
    }
255
256
    public function getControllerConfig(): array
257
    {
258
        $assetOptions = [];
259
260
        $controllerName = $this->getControllerName();
261
        if (null !== $controllerName) {
262
            $assetOptions = $this->configuration->getController($controllerName);
263
            if ($assetOptions) {
264
                if (array_key_exists('actions', $assetOptions)) {
265
                    unset($assetOptions['actions']);
266
                }
267
            } else {
268
                $assetOptions = [];
269
            }
270
        }
271
272
        return $assetOptions;
273
    }
274
275
    public function getActionConfig(): array
276
    {
277
        $actionAssets   = [];
278
        $controllerName = $this->getControllerName();
279
        if (null !== $controllerName) {
280
            $assetOptions = $this->configuration->getController($controllerName);
281
            $actionName   = $this->getActionName();
282
            if (null !== $actionName && $assetOptions && array_key_exists('actions', $assetOptions) && array_key_exists($actionName, $assetOptions['actions'])
283
            ) {
284
                $actionAssets = $assetOptions['actions'][$actionName];
285
            }
286
        }
287
288
        return $actionAssets;
289
    }
290
291
    public function setupRendererFromOptions(Renderer $renderer, array $options): void
292
    {
293
        if (!$this->hasStrategyForRenderer($renderer)) {
294
            throw new Exception\InvalidArgumentException(sprintf(
295
                    'no strategy defined for renderer "%s"',
296
                    $this->getRendererName($renderer)
297
            ));
298
        }
299
300
        $strategy = $this->getStrategyForRenderer($renderer);
301
        if (null !== $strategy) {
302
            while ($assetAlias = array_shift($options)) {
303
                $assetAlias = ltrim($assetAlias, '@');
304
305
                /** @var AssetInterface $asset */
306
                $asset = $this->getAssetManager()->get($assetAlias);
307
                // Prepare view strategy
308
                $strategy->setupAsset($asset);
309
            }
310
        }
311
    }
312
313 1
    public function hasStrategyForRenderer(Renderer $renderer): bool
314
    {
315 1
        $rendererName = $this->getRendererName($renderer);
316
317 1
        return (bool) $this->configuration->getStrategyNameForRenderer($rendererName);
318
    }
319
320
    /**
321
     * Get strategy to setup assets for given $renderer.
322
     *
323
     * @throws Exception\DomainException
324
     * @throws Exception\InvalidArgumentException
325
     */
326 3
    public function getStrategyForRenderer(Renderer $renderer): ?StrategyInterface
327
    {
328 3
        if (!$this->hasStrategyForRenderer($renderer)) {
329 1
            return null;
330
        }
331
332 2
        $rendererName = $this->getRendererName($renderer);
333 2
        if (!isset($this->strategy[$rendererName])) {
334 2
            $strategyClass = $this->configuration->getStrategyNameForRenderer($rendererName);
335
336 2
            if (null === $strategyClass) {
337
                throw new Exception\InvalidArgumentException(
338
                    sprintf(
339
                        'No strategy defined for renderer "%s"',
340
                        get_class($renderer)
341
                    )
342
                );
343
            }
344
345 2
            if (!class_exists($strategyClass, true)) {
346 1
                throw new Exception\InvalidArgumentException(
347 1
                    sprintf(
348
                        'strategy class "%s" dosen\'t exists',
349
                        $strategyClass
350
                    )
351
                );
352
            }
353
354 1
            $instance = new $strategyClass();
355
356 1
            if (!($instance instanceof StrategyInterface)) {
357
                throw new Exception\DomainException(
358
                    sprintf(
359
                        'strategy class "%s" is not instanceof "Fabiang\AsseticBundle\View\StrategyInterface"',
360
                        $strategyClass
361
                    )
362
                );
363
            }
364
365 1
            $this->strategy[$rendererName] = $instance;
366
        }
367
368
        /** @var \Fabiang\AsseticBundle\View\StrategyInterface $strategy */
369 1
        $strategy = $this->strategy[$rendererName];
370 1
        $strategy->setBaseUrl($this->configuration->getBaseUrl());
371 1
        $strategy->setBasePath($this->configuration->getBasePath());
372 1
        $strategy->setDebug($this->configuration->isDebug());
373 1
        $strategy->setCombine($this->configuration->isCombine());
374 1
        $strategy->setRenderer($renderer);
375
376 1
        return $strategy;
377
    }
378
379
    /**
380
     * Get renderer name from $renderer object.
381
     */
382 1
    public function getRendererName(Renderer $renderer): string
383
    {
384 1
        return get_class($renderer);
385
    }
386
387
    /**
388
     * Gets the service configuration.
389
     */
390 1
    public function getConfiguration(): Configuration
391
    {
392 1
        return $this->configuration;
393
    }
394
395 4
    public function createAssetFactory(array $configuration): AssetFactory
396
    {
397 4
        $factory = new AssetFactory($configuration['root_path']);
398 4
        $factory->setAssetManager($this->getAssetManager());
399 4
        $factory->setFilterManager($this->getFilterManager());
400 4
        $worker  = $this->getCacheBusterStrategy();
401 4
        if ($worker instanceof WorkerInterface) {
402 1
            $factory->addWorker($worker);
403
        }
404
        /**
405
         * @psalm-suppress InvalidArgument Upstream type-hint error
406
         */
407 4
        $factory->setDebug($this->configuration->isDebug());
408
409 4
        return $factory;
410
    }
411
412 3
    public function moveRaw(
413
        AssetCollection $asset,
414
        ?string $targetPath,
415
        AssetFactory $factory,
416
        bool $disableSourcePath = false
417
    ): void
418
    {
419 3
        foreach ($asset as $value) {
420 3
            $sourcePath = $value->getSourcePath() ?? '';
421
422
            /** @var AssetInterface $value */
423 3
            if ($disableSourcePath) {
424 3
                $value->setTargetPath(( $targetPath ?? '' ) . basename($sourcePath));
425
            } else {
426 3
                $value->setTargetPath(( $targetPath ?? '' ) . $sourcePath);
427
            }
428
429 3
            $value = $this->cacheAsset($value);
430 3
            $this->writeAsset($value, $factory);
431
        }
432
    }
433
434 3
    public function prepareCollection(array $options, string $name, AssetFactory $factory): void
435
    {
436 3
        $assets            = isset($options['assets']) ? $options['assets'] : [];
437 3
        $filters           = isset($options['filters']) ? $options['filters'] : [];
438 3
        $options           = isset($options['options']) ? $options['options'] : [];
439 3
        $options['output'] = isset($options['output']) ? $options['output'] : $name;
440 3
        $moveRaw           = isset($options['move_raw']) && $options['move_raw'];
441 3
        $targetPath        = !empty($options['targetPath']) ? $options['targetPath'] : '';
442 3
        if (substr($targetPath, -1) != DIRECTORY_SEPARATOR) {
443 3
            $targetPath .= DIRECTORY_SEPARATOR;
444
        }
445
446 3
        $filters = $this->initFilters($filters);
447 3
        $asset   = $factory->createAsset($assets, $filters, $options);
448
449
        // Allow to move all files 1:1 to new directory
450
        // its particularly useful when this assets are i.e. images.
451 3
        if ($moveRaw) {
452 3
            if (isset($options['disable_source_path'])) {
453 3
                $this->moveRaw($asset, $targetPath, $factory, $options['disable_source_path']);
454
            } else {
455 3
                $this->moveRaw($asset, $targetPath, $factory);
456
            }
457
        } else {
458 3
            $asset = $this->cacheAsset($asset);
459 3
            $this->getAssetManager()->set($name, $asset);
460
            // Save asset on disk
461 3
            $this->writeAsset($asset, $factory);
462
        }
463
    }
464
465
    /**
466
     * Write $asset to public directory.
467
     *
468
     * @param AssetInterface       $asset     Asset to write
469
     * @param AssetFactory $factory   The factory this asset was generated with
470
     */
471 3
    public function writeAsset(AssetInterface $asset, AssetFactory $factory): void
472
    {
473
        // We're not interested in saving assets on request
474 3
        if (!$this->configuration->getBuildOnRequest()) {
475
            return;
476
        }
477
478
        // Write asset on disk on every request
479 3
        if (!$this->configuration->getWriteIfChanged()) {
480
            $this->write($asset, $factory);
481
482
            return;
483
        }
484
485 3
        $created   = false;
486 3
        $isChanged = false;
487
488 3
        $targetPath = $asset->getTargetPath();
489 3
        if (null !== $targetPath) {
490 3
            $target = $this->configuration->getWebPath($targetPath);
491
492 3
            if (null !== $target) {
0 ignored issues
show
introduced by
The condition null !== $target is always true.
Loading history...
493 3
                $created   = is_file($target);
494 3
                $isChanged = $created && filemtime($target) < $factory->getLastModified($asset);
495
            }
496
        }
497
498
        // And long requested optimization
499 3
        if (!$created || $isChanged) {
500 3
            $this->write($asset, $factory);
501
        }
502
    }
503
504
    /**
505
     * @param AssetInterface       $asset     Asset to write
506
     * @param AssetFactory $factory   The factory this asset was generated with
507
     */
508 3
    protected function write(AssetInterface $asset, AssetFactory $factory): void
509
    {
510 3
        $umask = $this->configuration->getUmask();
511 3
        if (null !== $umask) {
512
            $umask = umask($umask);
513
        }
514
515 3
        if ($this->configuration->isDebug() && !$this->configuration->isCombine() && ($asset instanceof AssetCollection)
516
        ) {
517
            foreach ($asset as $item) {
518
                $this->writeAsset($item, $factory);
519
            }
520
        } else {
521 3
            $this->getAssetWriter()->writeAsset($asset);
522
        }
523
524 3
        if (null !== $umask) {
525
            umask($umask);
526
        }
527
    }
528
529
}
530