Passed
Push — master ( d1c5a6...bac8bf )
by Fabian
03:36 queued 12s
created

Service::moveRaw()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 19
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 4
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
    public function __construct(Configuration $configuration)
40
    {
41
        $this->configuration = $configuration;
42
    }
43
44
    public function setRouteName(string $routeName): void
45
    {
46
        $this->routeName = $routeName;
47
    }
48
49
    public function getRouteName(): string
50
    {
51
        return $this->routeName;
52
    }
53
54
    public function setAssetManager(AssetManager $assetManager): void
55
    {
56
        $this->assetManager = $assetManager;
57
    }
58
59
    public function getAssetManager(): AssetManager
60
    {
61
        if (null === $this->assetManager) {
62
            $this->assetManager = new AssetManager();
63
        }
64
65
        return $this->assetManager;
66
    }
67
68
    public function getAssetWriter(): AssetWriter
69
    {
70
        if (null === $this->assetWriter) {
71
            $webPath = $this->configuration->getWebPath();
72
            $this->assetWriter = new AssetWriter($webPath ?? '');
73
        }
74
75
        return $this->assetWriter;
76
    }
77
78
    public function setAssetWriter(AssetWriter $assetWriter): void
79
    {
80
        $this->assetWriter = $assetWriter;
81
    }
82
83
    public function getCacheBusterStrategy(): ?WorkerInterface
84
    {
85
        return $this->cacheBusterStrategy;
86
    }
87
88
    public function setCacheBusterStrategy(?WorkerInterface $cacheBusterStrategy): void
89
    {
90
        $this->cacheBusterStrategy = $cacheBusterStrategy;
91
    }
92
93
    public function setFilterManager(AsseticFilterManager $filterManager): void
94
    {
95
        $this->filterManager = $filterManager;
96
    }
97
98
    public function getFilterManager(): AsseticFilterManager
99
    {
100
        if (null === $this->filterManager) {
101
            $this->filterManager = new AsseticFilterManager();
102
        }
103
104
        return $this->filterManager;
105
    }
106
107
    public function setControllerName(?string $controllerName): void
108
    {
109
        $this->controllerName = $controllerName;
110
    }
111
112
    public function getControllerName(): ?string
113
    {
114
        return $this->controllerName;
115
    }
116
117
    public function setActionName(?string $actionName): void
118
    {
119
        $this->actionName = $actionName;
120
    }
121
122
    public function getActionName(): ?string
123
    {
124
        return $this->actionName;
125
    }
126
127
    /**
128
     * Build collection of assets.
129
     */
130
    public function build(): void
131
    {
132
        $moduleConfiguration = $this->configuration->getModules();
133
        foreach ($moduleConfiguration as $configuration) {
134
            $factory     = $this->createAssetFactory($configuration);
135
            $collections = (array) $configuration['collections'];
136
            foreach ($collections as $name => $options) {
137
                $this->prepareCollection($options, $name, $factory);
138
            }
139
        }
140
    }
141
142
    private function cacheAsset(AssetInterface $asset): AssetInterface
143
    {
144
        if ($this->configuration->getCacheEnabled()) {
145
            return new AssetCache(
146
                $asset,
147
                new FilesystemCache($this->configuration->getCachePath())
148
            );
149
        }
150
        return $asset;
151
    }
152
153
    private function initFilters(array $filters): array
154
    {
155
        $result = [];
156
157
        $fm = $this->getFilterManager();
158
159
        foreach ($filters as $alias => $options) {
160
            $option = null;
161
            $name   = null;
162
            if (is_array($options)) {
163
                if (!isset($options['name'])) {
164
                    throw new Exception\InvalidArgumentException(
165
                        'Filter "' . $alias . '" required option "name"'
166
                    );
167
                }
168
169
                $name   = $options['name'];
170
                $option = isset($options['option']) ? $options['option'] : null;
171
            } elseif (is_string($options)) {
172
                $name = $options;
173
                unset($options);
174
            }
175
176
            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
            if (is_numeric($alias)) {
184
                $alias = $name;
185
            }
186
187
            // Filter Id should have optional filter indicator "?"
188
            $filterId = ltrim($alias, '?');
189
190
            if (!$fm->has($filterId)) {
191
                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
                } elseif ($option) {
197
                    /** @var \Assetic\Contracts\Filter\FilterInterface $filter */
198
                    $filter = new $name($option);
199
                } else {
200
                    /** @var \Assetic\Contracts\Filter\FilterInterface $filter */
201
                    $filter = new $name();
202
                }
203
204
                $fm->set($filterId, $filter);
205
            }
206
207
            $result[] = $alias;
208
        }
209
210
        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
    public function hasStrategyForRenderer(Renderer $renderer): bool
314
    {
315
        $rendererName = $this->getRendererName($renderer);
316
317
        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
    public function getStrategyForRenderer(Renderer $renderer): ?StrategyInterface
327
    {
328
        if (!$this->hasStrategyForRenderer($renderer)) {
329
            return null;
330
        }
331
332
        $rendererName = $this->getRendererName($renderer);
333
        if (!isset($this->strategy[$rendererName])) {
334
            $strategyClass = $this->configuration->getStrategyNameForRenderer($rendererName);
335
336
            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
            if (!class_exists($strategyClass, true)) {
346
                throw new Exception\InvalidArgumentException(
347
                    sprintf(
348
                        'strategy class "%s" dosen\'t exists',
349
                        $strategyClass
350
                    )
351
                );
352
            }
353
354
            $instance = new $strategyClass();
355
356
            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
            $this->strategy[$rendererName] = $instance;
366
        }
367
368
        /** @var \Fabiang\AsseticBundle\View\StrategyInterface $strategy */
369
        $strategy = $this->strategy[$rendererName];
370
        $strategy->setBaseUrl($this->configuration->getBaseUrl());
371
        $strategy->setBasePath($this->configuration->getBasePath());
372
        $strategy->setDebug($this->configuration->isDebug());
373
        $strategy->setCombine($this->configuration->isCombine());
374
        $strategy->setRenderer($renderer);
375
376
        return $strategy;
377
    }
378
379
    /**
380
     * Get renderer name from $renderer object.
381
     */
382
    public function getRendererName(Renderer $renderer): string
383
    {
384
        return get_class($renderer);
385
    }
386
387
    /**
388
     * Gets the service configuration.
389
     */
390
    public function getConfiguration(): Configuration
391
    {
392
        return $this->configuration;
393
    }
394
395
    public function createAssetFactory(array $configuration): AssetFactory
396
    {
397
        $factory = new AssetFactory($configuration['root_path']);
398
        $factory->setAssetManager($this->getAssetManager());
399
        $factory->setFilterManager($this->getFilterManager());
400
        $worker  = $this->getCacheBusterStrategy();
401
        if ($worker instanceof WorkerInterface) {
402
            $factory->addWorker($worker);
403
        }
404
        /**
405
         * @psalm-suppress InvalidArgument Upstream type-hint error
406
         */
407
        $factory->setDebug($this->configuration->isDebug());
408
409
        return $factory;
410
    }
411
412
    public function moveRaw(
413
        AssetCollection $asset,
414
        ?string $targetPath,
415
        AssetFactory $factory,
416
        bool $disableSourcePath = false
417
    ): void
418
    {
419
        foreach ($asset as $value) {
420
            $sourcePath = $value->getSourcePath() ?? '';
421
422
            /** @var AssetInterface $value */
423
            if ($disableSourcePath) {
424
                $value->setTargetPath(( $targetPath ?? '' ) . basename($sourcePath));
425
            } else {
426
                $value->setTargetPath(( $targetPath ?? '' ) . $sourcePath);
427
            }
428
429
            $value = $this->cacheAsset($value);
430
            $this->writeAsset($value, $factory);
431
        }
432
    }
433
434
    public function prepareCollection(array $options, string $name, AssetFactory $factory): void
435
    {
436
        $assets            = isset($options['assets']) ? $options['assets'] : [];
437
        $filters           = isset($options['filters']) ? $options['filters'] : [];
438
        $options           = isset($options['options']) ? $options['options'] : [];
439
        $options['output'] = isset($options['output']) ? $options['output'] : $name;
440
        $moveRaw           = isset($options['move_raw']) && $options['move_raw'];
441
        $targetPath        = !empty($options['targetPath']) ? $options['targetPath'] : '';
442
        if (substr($targetPath, -1) != DIRECTORY_SEPARATOR) {
443
            $targetPath .= DIRECTORY_SEPARATOR;
444
        }
445
446
        $filters = $this->initFilters($filters);
447
        $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
        if ($moveRaw) {
452
            if (isset($options['disable_source_path'])) {
453
                $this->moveRaw($asset, $targetPath, $factory, $options['disable_source_path']);
454
            } else {
455
                $this->moveRaw($asset, $targetPath, $factory);
456
            }
457
        } else {
458
            $asset = $this->cacheAsset($asset);
459
            $this->getAssetManager()->set($name, $asset);
460
            // Save asset on disk
461
            $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
    public function writeAsset(AssetInterface $asset, AssetFactory $factory): void
472
    {
473
        // We're not interested in saving assets on request
474
        if (!$this->configuration->getBuildOnRequest()) {
475
            return;
476
        }
477
478
        // Write asset on disk on every request
479
        if (!$this->configuration->getWriteIfChanged()) {
480
            $this->write($asset, $factory);
481
482
            return;
483
        }
484
485
        $created   = false;
486
        $isChanged = false;
487
488
        $targetPath = $asset->getTargetPath();
489
        if (null !== $targetPath) {
490
            $target = $this->configuration->getWebPath($targetPath);
491
492
            if (null !== $target) {
0 ignored issues
show
introduced by
The condition null !== $target is always true.
Loading history...
493
                $created   = is_file($target);
494
                $isChanged = $created && filemtime($target) < $factory->getLastModified($asset);
495
            }
496
        }
497
498
        // And long requested optimization
499
        if (!$created || $isChanged) {
500
            $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
    protected function write(AssetInterface $asset, AssetFactory $factory): void
509
    {
510
        $umask = $this->configuration->getUmask();
511
        if (null !== $umask) {
512
            $umask = umask($umask);
513
        }
514
515
        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
            $this->getAssetWriter()->writeAsset($asset);
522
        }
523
524
        if (null !== $umask) {
525
            umask($umask);
526
        }
527
    }
528
529
}
530