Service::getStrategyForRenderer()   B
last analyzed

Complexity

Conditions 6
Paths 6

Size

Total Lines 51
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 6.042

Importance

Changes 0
Metric Value
eloc 29
dl 0
loc 51
ccs 17
cts 19
cp 0.8947
rs 8.8337
c 0
b 0
f 0
cc 6
nc 6
nop 1
crap 6.042

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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