Passed
Pull Request — master (#80)
by Evgeniy
02:28
created

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

214
                        /** @scrutinizer ignore-call */ 
215
                        $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...
215 4
                            $file,
216 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

216
                            /** @scrutinizer ignore-type */ $bundle->basePath,
Loading history...
217 4
                            $bundle->converterOptions,
218
                        );
219
220 4
                        $bundle->css[$i] = $css;
221
                    }
222
                }
223 14
            } elseif (AssetUtil::isRelative($css)) {
224 14
                $baseCss = $this->aliases->get("{$bundle->basePath}/{$css}");
225 14
                if (is_file("$baseCss")) {
226
                    /**
227
                     * @psalm-suppress PossiblyNullArgument
228
                     * @psalm-suppress PossiblyNullReference
229
                     */
230 13
                    $bundle->css[$i] = $this->converter->convert(
231 13
                        $css,
232 13
                        $bundle->basePath,
233 13
                        $bundle->converterOptions
234
                    );
235
                }
236
            }
237
        }
238 15
    }
239
240
    /**
241
     * Convert files from TypeScript and other formats into JavaScript.
242
     *
243
     * @param AssetBundle $bundle
244
     */
245 15
    private function convertJs(AssetBundle $bundle): void
246
    {
247 15
        foreach ($bundle->js as $i => $js) {
248 15
            if (is_array($js)) {
249 5
                $file = $js[0];
250 5
                if (AssetUtil::isRelative($file)) {
251 5
                    $baseFile = $this->aliases->get("{$bundle->basePath}/{$file}");
252 5
                    if (is_file($baseFile)) {
253
                        /**
254
                         * @psalm-suppress PossiblyNullArgument
255
                         * @psalm-suppress PossiblyNullReference
256
                         */
257 5
                        $js[0] = $this->converter->convert(
258 5
                            $file,
259 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

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