Passed
Push — master ( f73a03...016f54 )
by Sergei
04:19 queued 01:50
created

AssetRegistrar::getType()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

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

205
                        /** @scrutinizer ignore-call */ 
206
                        $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...
206
                            $file,
207 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

207
                            /** @scrutinizer ignore-type */ $bundle->basePath,
Loading history...
208 4
                            $bundle->converterOptions,
209
                        );
210
211 4
                        $bundle->css[$i] = $css;
212
                    }
213
                }
214 14
            } elseif (AssetUtil::isRelative($css)) {
215 14
                $baseCss = $this->aliases->get("{$bundle->basePath}/{$css}");
216 14
                if (is_file("$baseCss")) {
217 13
                    $bundle->css[$i] = $this->converter->convert(
218
                        $css,
219 13
                        $bundle->basePath,
220 13
                        $bundle->converterOptions
221
                    );
222
                }
223
            }
224
        }
225
    }
226
227
    /**
228
     * Convert files from TypeScript and other formats into JavaScript.
229
     */
230 15
    private function convertJs(AssetBundle $bundle): void
231
    {
232
        /**
233
         * @psalm-var AssetConverterInterface $this->converter
234
         * @psalm-var string $bundle->basePath
235
         * @psalm-var ConverterOptions $bundle->converterOptions
236
         *
237
         * @var JsFile|string $js
238
         */
239 15
        foreach ($bundle->js as $i => $js) {
240 15
            if (is_array($js)) {
241 5
                $file = $js[0];
242 5
                if (AssetUtil::isRelative($file)) {
243 5
                    $baseFile = $this->aliases->get("{$bundle->basePath}/{$file}");
244 5
                    if (is_file($baseFile)) {
245 5
                        $js[0] = $this->converter->convert(
246
                            $file,
247 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

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