Passed
Push — master ( 077456...b9061e )
by Evgeniy
02:14
created

AssetRegistrar::convertJs()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 7

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 15
c 1
b 0
f 0
nc 7
nop 1
dl 0
loc 24
ccs 16
cts 16
cp 1
crap 7
rs 8.8333
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Assets;
6
7
use Yiisoft\Aliases\Aliases;
8
use Yiisoft\Assets\Exception\InvalidConfigException;
9
10
use function array_key_exists;
11
use function array_values;
12
use function get_class;
13
use function gettype;
14
use function is_array;
15
use function is_int;
16
use function is_object;
17
use function is_string;
18
use function sprintf;
19
20
/**
21
 * AssetRegistrar registers asset files, code blocks and variables from a bundle considering dependencies.
22
 *
23
 * @internal
24
 *
25
 * @psalm-import-type CssFile from AssetManager
26
 * @psalm-import-type CssString from AssetManager
27
 * @psalm-import-type JsFile from AssetManager
28
 * @psalm-import-type JsString from AssetManager
29
 * @psalm-import-type JsVar from AssetManager
30
 * @psalm-import-type ConverterOptions from AssetConverterInterface
31
 */
32
final class AssetRegistrar
33
{
34
    private Aliases $aliases;
35
    private AssetLoaderInterface $loader;
36
    private ?AssetConverterInterface $converter = null;
37
38
    /**
39
     * @psalm-var CssFile[]
40
     */
41
    private array $cssFiles = [];
42
43
    /**
44
     * @psalm-var CssString[]
45
     */
46
    private array $cssStrings = [];
47
48
    /**
49
     * @psalm-var JsFile[]
50
     */
51
    private array $jsFiles = [];
52
53
    /**
54
     * @psalm-var JsString[]
55
     */
56
    private array $jsStrings = [];
57
58
    private array $jsVars = [];
59
60 97
    public function __construct(Aliases $aliases, AssetLoaderInterface $loader)
61
    {
62 97
        $this->aliases = $aliases;
63 97
        $this->loader = $loader;
64 97
    }
65
66
    /**
67
     * Return config array CSS AssetBundle.
68
     *
69
     * @psalm-return CssFile[]
70
     */
71 12
    public function getCssFiles(): array
72
    {
73 12
        return $this->cssFiles;
74
    }
75
76
    /**
77
     * Returns CSS blocks.
78
     *
79
     * @return array
80
     * @psalm-return CssString[]
81
     */
82 2
    public function getCssStrings(): array
83
    {
84 2
        return $this->cssStrings;
85
    }
86
87
    /**
88
     * Returns config array JS AssetBundle.
89
     *
90
     * @psalm-return JsFile[]
91
     */
92 22
    public function getJsFiles(): array
93
    {
94 22
        return $this->jsFiles;
95
    }
96
97
    /**
98
     * Returns JS code blocks.
99
     *
100
     * @return array
101
     * @psalm-return JsString[]
102
     */
103 3
    public function getJsStrings(): array
104
    {
105 3
        return $this->jsStrings;
106
    }
107
108
    /**
109
     * Returns JS variables.
110
     *
111
     * @return array
112
     */
113 3
    public function getJsVars(): array
114
    {
115 3
        return array_values($this->jsVars);
116
    }
117
118
    /**
119
     * Returns a new instance with the specified converter.
120
     *
121
     * @param AssetConverterInterface $converter
122
     *
123
     * @return self
124
     */
125 97
    public function withConverter(AssetConverterInterface $converter): self
126
    {
127 97
        $new = clone $this;
128 97
        $new->converter = $converter;
129 97
        return $new;
130
    }
131
132
    /**
133
     * Returns a new instance with the specified loader.
134
     *
135
     * @param AssetLoaderInterface $loader
136
     *
137
     * @return self
138
     */
139 5
    public function withLoader(AssetLoaderInterface $loader): self
140
    {
141 5
        $new = clone $this;
142 5
        $new->loader = $loader;
143 5
        return $new;
144
    }
145
146
    /**
147
     * Registers asset files from a bundle considering dependencies.
148
     *
149
     * @param AssetBundle $bundle
150
     *
151
     * @throws InvalidConfigException If asset files are not found.
152
     */
153 60
    public function register(AssetBundle $bundle): void
154
    {
155 60
        if (isset($bundle->basePath, $bundle->baseUrl) && $this->converter !== null) {
156 15
            $this->convertCss($bundle);
157 15
            $this->convertJs($bundle);
158
        }
159
160
        /** @var JsFile|string $js */
161 60
        foreach ($bundle->js as $key => $js) {
162 49
            $this->registerJsFile(
163 49
                $bundle,
164 49
                is_string($key) ? $key : null,
165
                $js,
166
            );
167
        }
168
169
        /** @var mixed $jsString */
170 54
        foreach ($bundle->jsStrings as $key => $jsString) {
171 7
            $this->registerJsString(
172 7
                $bundle,
173 7
                is_string($key) ? $key : null,
174
                $jsString,
175
            );
176
        }
177
178
        /** @var JsVar|string $jsVar */
179 53
        foreach ($bundle->jsVars as $name => $jsVar) {
180 11
            if (is_string($name)) {
181 6
                $this->registerJsVar($name, $jsVar, $bundle->jsPosition);
182
            } else {
183 11
                $this->registerJsVarByConfig($jsVar, $bundle->jsPosition);
184
            }
185
        }
186
187
        /** @var CssFile|string $css */
188 48
        foreach ($bundle->css as $key => $css) {
189 36
            $this->registerCssFile(
190 36
                $bundle,
191 36
                is_string($key) ? $key : null,
192
                $css,
193
            );
194
        }
195
196
        /** @var mixed $cssString */
197 45
        foreach ($bundle->cssStrings as $key => $cssString) {
198 6
            $this->registerCssString(
199 6
                $bundle,
200 6
                is_string($key) ? $key : null,
201
                $cssString,
202
            );
203
        }
204 45
    }
205
206
    /**
207
     * Converter SASS, SCSS, Stylus and other formats to CSS.
208
     *
209
     * @param AssetBundle $bundle
210
     */
211 15
    private function convertCss(AssetBundle $bundle): void
212
    {
213
        /** @var CssFile|string $css */
214 15
        foreach ($bundle->css as $i => $css) {
215 14
            if (is_array($css)) {
216 4
                $file = $css[0];
217 4
                if (AssetUtil::isRelative($file)) {
218 4
                    $baseFile = $this->aliases->get("{$bundle->basePath}/{$file}");
219 4
                    if (is_file($baseFile)) {
220
                        /** @psalm-suppress PossiblyNullArgument,PossiblyNullReference,MixedArgumentTypeCoercion */
221 4
                        $css[0] = $this->converter->convert(
0 ignored issues
show
Bug introduced by
The method convert() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

221
                        /** @scrutinizer ignore-call */ 
222
                        $css[0] = $this->converter->convert(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
222 4
                            $file,
223 4
                            $bundle->basePath,
0 ignored issues
show
Bug introduced by
It seems like $bundle->basePath can also be of type null; however, parameter $basePath of Yiisoft\Assets\AssetConverterInterface::convert() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

223
                            /** @scrutinizer ignore-type */ $bundle->basePath,
Loading history...
224 4
                            $bundle->converterOptions,
225
                        );
226
227 4
                        $bundle->css[$i] = $css;
228
                    }
229
                }
230 14
            } elseif (AssetUtil::isRelative($css)) {
231 14
                $baseCss = $this->aliases->get("{$bundle->basePath}/{$css}");
232 14
                if (is_file("$baseCss")) {
233
                    /** @psalm-suppress PossiblyNullArgument,PossiblyNullReference,MixedArgumentTypeCoercion */
234 13
                    $bundle->css[$i] = $this->converter->convert(
235 13
                        $css,
236 13
                        $bundle->basePath,
237 13
                        $bundle->converterOptions
238
                    );
239
                }
240
            }
241
        }
242 15
    }
243
244
    /**
245
     * Convert files from TypeScript and other formats into JavaScript.
246
     *
247
     * @param AssetBundle $bundle
248
     */
249 15
    private function convertJs(AssetBundle $bundle): void
250
    {
251
        /** @var JsFile|string $js */
252 15
        foreach ($bundle->js as $i => $js) {
253 15
            if (is_array($js)) {
254 5
                $file = $js[0];
255 5
                if (AssetUtil::isRelative($file)) {
256 5
                    $baseFile = $this->aliases->get("{$bundle->basePath}/{$file}");
257 5
                    if (is_file($baseFile)) {
258
                        /** @psalm-suppress PossiblyNullArgument,PossiblyNullReference,MixedArgumentTypeCoercion */
259 5
                        $js[0] = $this->converter->convert(
260 5
                            $file,
261 5
                            $bundle->basePath,
0 ignored issues
show
Bug introduced by
It seems like $bundle->basePath can also be of type null; however, parameter $basePath of Yiisoft\Assets\AssetConverterInterface::convert() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

261
                            /** @scrutinizer ignore-type */ $bundle->basePath,
Loading history...
262 5
                            $bundle->converterOptions
263
                        );
264
265 5
                        $bundle->js[$i] = $js;
266
                    }
267
                }
268 15
            } elseif (AssetUtil::isRelative($js)) {
269 15
                $baseJs = $this->aliases->get("{$bundle->basePath}/{$js}");
270 15
                if (is_file($baseJs)) {
271
                    /** @psalm-suppress PossiblyNullArgument,PossiblyNullReference */
272 14
                    $bundle->js[$i] = $this->converter->convert($js, $bundle->basePath);
273
                }
274
            }
275
        }
276 15
    }
277
278
    /**
279
     * Registers a CSS file.
280
     *
281
     * @param array|string $css
282
     *
283
     * @throws InvalidConfigException
284
     */
285 36
    private function registerCssFile(AssetBundle $bundle, ?string $key, $css): void
286
    {
287 36
        if (is_array($css)) {
288 10
            if (!array_key_exists(0, $css)) {
289 1
                throw new InvalidConfigException('Do not set in array CSS URL.');
290
            }
291 9
            $url = $css[0];
292
        } else {
293 33
            $url = $css;
294
        }
295
296 35
        if (!is_string($url)) {
297 1
            throw new InvalidConfigException(
298 1
                sprintf(
299 1
                    'CSS file should be string. Got %s.',
300 1
                    $this->getType($url),
301
                )
302
            );
303
        }
304
305 34
        if ($url === '') {
306 1
            throw new InvalidConfigException('CSS file should be non empty string.');
307
        }
308
309 33
        $url = $this->loader->getAssetUrl($bundle, $url);
310
311 33
        if (is_array($css)) {
312 7
            $css[0] = $url;
313
        } else {
314 33
            $css = [$url];
315
        }
316
317 33
        if ($bundle->cssPosition !== null && !isset($css[1])) {
318 3
            $css[1] = $bundle->cssPosition;
319
        }
320
321
        /** @psalm-var CssFile */
322 33
        $css = $this->mergeOptionsWithArray($bundle->cssOptions, $css);
323
324 33
        $this->cssFiles[$key ?: $url] = $css;
325 33
    }
326
327
    /**
328
     * Registers a CSS string.
329
     *
330
     * @param mixed $cssString
331
     *
332
     * @throws InvalidConfigException
333
     */
334 6
    private function registerCssString(AssetBundle $bundle, ?string $key, $cssString): void
335
    {
336 6
        if (is_array($cssString)) {
337 6
            $config = $cssString;
338 6
            if (!array_key_exists(0, $config)) {
339 6
                throw new InvalidConfigException('CSS string do not set in array.');
340
            }
341
        } else {
342 6
            $config = [$cssString];
343
        }
344
345 6
        if ($bundle->cssPosition !== null && !isset($config[1])) {
346 1
            $config[1] = $bundle->cssPosition;
347
        }
348
349
        /** @psalm-var CssString */
350 6
        $config = $this->mergeOptionsWithArray($bundle->cssOptions, $config);
351
352 6
        if ($key === null) {
353 6
            $this->cssStrings[] = $config;
354
        } else {
355 6
            $this->cssStrings[$key] = $config;
356
        }
357 6
    }
358
359
    /**
360
     * Registers a JS file.
361
     *
362
     * @param array|string $js
363
     *
364
     * @throws InvalidConfigException
365
     */
366 49
    private function registerJsFile(AssetBundle $bundle, ?string $key, $js): void
367
    {
368 49
        if (is_array($js)) {
369 12
            if (!array_key_exists(0, $js)) {
370 1
                throw new InvalidConfigException('Do not set in array JS URL.');
371
            }
372 11
            $url = $js[0];
373
        } else {
374 45
            $url = $js;
375
        }
376
377 48
        if (!is_string($url)) {
378 1
            throw new InvalidConfigException(
379 1
                sprintf(
380 1
                    'JS file should be string. Got %s.',
381 1
                    $this->getType($url),
382
                )
383
            );
384
        }
385
386 47
        if ($url === '') {
387 1
            throw new InvalidConfigException('JS file should be non empty string.');
388
        }
389
390 46
        $url = $this->loader->getAssetUrl($bundle, $url);
391
392 43
        if (is_array($js)) {
393 9
            $js[0] = $url;
394
        } else {
395 42
            $js = [$url];
396
        }
397
398 43
        if ($bundle->jsPosition !== null && !isset($js[1])) {
399 10
            $js[1] = $bundle->jsPosition;
400
        }
401
402
        /** @psalm-var JsFile */
403 43
        $js = $this->mergeOptionsWithArray($bundle->jsOptions, $js);
404
405 43
        $this->jsFiles[$key ?: $url] = $js;
406 43
    }
407
408
    /**
409
     * Registers a JS string.
410
     *
411
     * @param mixed $jsString
412
     *
413
     * @throws InvalidConfigException
414
     */
415 7
    private function registerJsString(AssetBundle $bundle, ?string $key, $jsString): void
416
    {
417 7
        if (is_array($jsString)) {
418 6
            if (!array_key_exists(0, $jsString)) {
419 6
                throw new InvalidConfigException('JavaScript string do not set in array.');
420
            }
421
        } else {
422 7
            $jsString = [$jsString];
423
        }
424
425 7
        if ($bundle->jsPosition !== null && !isset($jsString[1])) {
426 1
            $jsString[1] = $bundle->jsPosition;
427
        }
428
429
        /** @psalm-var JsString */
430 7
        $jsString = $this->mergeOptionsWithArray($bundle->jsOptions, $jsString);
431
432 6
        if ($key === null) {
433 6
            $this->jsStrings[] = $jsString;
434
        } else {
435 6
            $this->jsStrings[$key] = $jsString;
436
        }
437 6
    }
438
439
    /**
440
     * Registers a JavaScript variable.
441
     *
442
     * @param mixed $value
443
     */
444 6
    private function registerJsVar(string $name, $value, ?int $position): void
445
    {
446 6
        $config = [$name, $value];
447
448 6
        if ($position !== null) {
449 6
            $config[2] = $position;
450
        }
451
452 6
        $this->jsVars[$name] = $config;
453 6
    }
454
455
    /**
456
     * Registers a JavaScript variable by config.
457
     *
458
     * @param mixed $config
459
     *
460
     * @throws InvalidConfigException
461
     */
462 11
    private function registerJsVarByConfig($config, ?int $bundleJsPosition): void
463
    {
464 11
        if (!is_array($config)) {
465 1
            throw new InvalidConfigException(
466 1
                sprintf(
467 1
                    'Without string key JavaScript variable should be array. Got %s.',
468 1
                    $this->getType($config),
469
                )
470
            );
471
        }
472
473 10
        if (!array_key_exists(0, $config)) {
474 1
            throw new InvalidConfigException('Do not set JavaScript variable name.');
475
        }
476 9
        $name = $config[0];
477
478 9
        if (!is_string($name)) {
479 1
            throw new InvalidConfigException(
480 1
                sprintf(
481 1
                    'JavaScript variable name should be string. Got %s.',
482 1
                    $this->getType($name),
483
                )
484
            );
485
        }
486
487 8
        if (!array_key_exists(1, $config)) {
488 1
            throw new InvalidConfigException('Do not set JavaScript variable value.');
489
        }
490
        /** @var mixed */
491 7
        $value = $config[1];
492
493 7
        $position = $config[2] ?? $bundleJsPosition;
494 7
        if (!is_int($position)) {
495 1
            throw new InvalidConfigException(
496 1
                sprintf(
497 1
                    'JavaScript variable position should be integer. Got %s.',
498 1
                    $this->getType($position),
499
                )
500
            );
501
        }
502
503 6
        $this->registerJsVar($name, $value, $position);
504 6
    }
505
506
    /**
507
     * @throws InvalidConfigException
508
     */
509 44
    private function mergeOptionsWithArray(array $options, array $array): array
510
    {
511
        /** @var mixed $value */
512 44
        foreach ($options as $key => $value) {
513 9
            if (is_int($key)) {
514 1
                throw new InvalidConfigException(
515 1
                    'JavaScript or CSS options should be list of key/value pairs with string keys. Got integer key.'
516
                );
517
            }
518
519 8
            if (!array_key_exists($key, $array)) {
520
                /** @var mixed */
521 8
                $array[$key] = $value;
522
            }
523
        }
524
525 43
        return $array;
526
    }
527
528
    /**
529
     * @param mixed $value
530
     */
531 5
    private function getType($value): string
532
    {
533 5
        return is_object($value) ? get_class($value) : gettype($value);
534
    }
535
}
536