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 | 103 | public function __construct( |
|||
59 | private Aliases $aliases, |
||||
60 | private AssetLoaderInterface $loader |
||||
61 | ) { |
||||
62 | 103 | } |
|||
63 | |||||
64 | /** |
||||
65 | * @return array Config array of CSS files. |
||||
66 | * |
||||
67 | * @psalm-return CssFile[] |
||||
68 | */ |
||||
69 | 12 | public function getCssFiles(): array |
|||
70 | { |
||||
71 | 12 | return $this->cssFiles; |
|||
72 | } |
||||
73 | |||||
74 | /** |
||||
75 | * @return array CSS blocks. |
||||
76 | * |
||||
77 | * @psalm-return CssString[] |
||||
78 | */ |
||||
79 | 2 | public function getCssStrings(): array |
|||
80 | { |
||||
81 | 2 | return $this->cssStrings; |
|||
82 | } |
||||
83 | |||||
84 | /** |
||||
85 | * @return array Config array of JavaScript files. |
||||
86 | * |
||||
87 | * @psalm-return JsFile[] |
||||
88 | */ |
||||
89 | 24 | public function getJsFiles(): array |
|||
90 | { |
||||
91 | 24 | return $this->jsFiles; |
|||
92 | } |
||||
93 | |||||
94 | /** |
||||
95 | * @return array JavaScript code blocks. |
||||
96 | * |
||||
97 | * @psalm-return JsString[] |
||||
98 | */ |
||||
99 | 3 | public function getJsStrings(): array |
|||
100 | { |
||||
101 | 3 | return $this->jsStrings; |
|||
102 | } |
||||
103 | |||||
104 | /** |
||||
105 | * @return array JavaScript variables. |
||||
106 | * |
||||
107 | * @psalm-return list<JsVar> |
||||
108 | */ |
||||
109 | 3 | public function getJsVars(): array |
|||
110 | { |
||||
111 | 3 | return array_values($this->jsVars); |
|||
112 | } |
||||
113 | |||||
114 | /** |
||||
115 | * @return self A new instance with the specified converter. |
||||
116 | */ |
||||
117 | 103 | public function withConverter(AssetConverterInterface $converter): self |
|||
118 | { |
||||
119 | 103 | $new = clone $this; |
|||
120 | 103 | $new->converter = $converter; |
|||
121 | 103 | return $new; |
|||
122 | } |
||||
123 | |||||
124 | /** |
||||
125 | * @return self A new instance with the specified loader. |
||||
126 | */ |
||||
127 | 5 | public function withLoader(AssetLoaderInterface $loader): self |
|||
128 | { |
||||
129 | 5 | $new = clone $this; |
|||
130 | 5 | $new->loader = $loader; |
|||
131 | 5 | return $new; |
|||
132 | } |
||||
133 | |||||
134 | /** |
||||
135 | * Registers assets from a bundle considering dependencies. |
||||
136 | * |
||||
137 | * @throws InvalidConfigException If asset files are not found. |
||||
138 | */ |
||||
139 | 62 | public function register(AssetBundle $bundle): void |
|||
140 | { |
||||
141 | 62 | if (isset($bundle->basePath, $bundle->baseUrl) && $this->converter !== null) { |
|||
142 | 15 | $this->convertCss($bundle); |
|||
143 | 15 | $this->convertJs($bundle); |
|||
144 | } |
||||
145 | |||||
146 | /** @var JsFile|string $js */ |
||||
147 | 62 | foreach ($bundle->js as $key => $js) { |
|||
148 | 51 | $this->registerJsFile( |
|||
149 | 51 | $bundle, |
|||
150 | 51 | is_string($key) ? $key : null, |
|||
151 | 51 | $js, |
|||
152 | 51 | ); |
|||
153 | } |
||||
154 | |||||
155 | 56 | foreach ($bundle->jsStrings as $key => $jsString) { |
|||
156 | 7 | $this->registerJsString( |
|||
157 | 7 | $bundle, |
|||
158 | 7 | is_string($key) ? $key : null, |
|||
159 | 7 | $jsString, |
|||
160 | 7 | ); |
|||
161 | } |
||||
162 | |||||
163 | /** @var JsVar|string $jsVar */ |
||||
164 | 55 | foreach ($bundle->jsVars as $name => $jsVar) { |
|||
165 | 11 | if (is_string($name)) { |
|||
166 | 6 | $this->registerJsVar($name, $jsVar, $bundle->jsPosition); |
|||
167 | } else { |
||||
168 | 11 | $this->registerJsVarByConfig($jsVar, $bundle->jsPosition); |
|||
169 | } |
||||
170 | } |
||||
171 | |||||
172 | /** @var CssFile|string $css */ |
||||
173 | 50 | foreach ($bundle->css as $key => $css) { |
|||
174 | 36 | $this->registerCssFile( |
|||
175 | 36 | $bundle, |
|||
176 | 36 | is_string($key) ? $key : null, |
|||
177 | 36 | $css, |
|||
178 | 36 | ); |
|||
179 | } |
||||
180 | |||||
181 | 47 | foreach ($bundle->cssStrings as $key => $cssString) { |
|||
182 | 6 | $this->registerCssString( |
|||
183 | 6 | $bundle, |
|||
184 | 6 | is_string($key) ? $key : null, |
|||
185 | 6 | $cssString, |
|||
186 | 6 | ); |
|||
187 | } |
||||
188 | } |
||||
189 | |||||
190 | /** |
||||
191 | * Converter SASS, SCSS, Stylus and other formats to CSS. |
||||
192 | */ |
||||
193 | 15 | private function convertCss(AssetBundle $bundle): void |
|||
194 | { |
||||
195 | /** |
||||
196 | * @psalm-var AssetConverterInterface $this->converter |
||||
197 | * @psalm-var string $bundle->basePath |
||||
198 | * @psalm-var ConverterOptions $bundle->converterOptions |
||||
199 | * |
||||
200 | * @var CssFile|string $css |
||||
201 | */ |
||||
202 | 15 | foreach ($bundle->css as $i => $css) { |
|||
203 | 14 | if (is_array($css)) { |
|||
204 | 4 | $file = $css[0]; |
|||
205 | 4 | if (AssetUtil::isRelative($file)) { |
|||
206 | 4 | $baseFile = $this->aliases->get("{$bundle->basePath}/{$file}"); |
|||
207 | 4 | if (is_file($baseFile)) { |
|||
208 | 4 | $css[0] = $this->converter->convert( |
|||
0 ignored issues
–
show
|
|||||
209 | 4 | $file, |
|||
210 | 4 | $bundle->basePath, |
|||
0 ignored issues
–
show
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
![]() |
|||||
211 | 4 | $bundle->converterOptions, |
|||
212 | 4 | ); |
|||
213 | |||||
214 | 4 | $bundle->css[$i] = $css; |
|||
215 | } |
||||
216 | } |
||||
217 | 14 | } elseif (AssetUtil::isRelative($css)) { |
|||
218 | 14 | $baseCss = $this->aliases->get("{$bundle->basePath}/{$css}"); |
|||
219 | 14 | if (is_file("$baseCss")) { |
|||
220 | 13 | $bundle->css[$i] = $this->converter->convert( |
|||
221 | 13 | $css, |
|||
222 | 13 | $bundle->basePath, |
|||
223 | 13 | $bundle->converterOptions |
|||
224 | 13 | ); |
|||
225 | } |
||||
226 | } |
||||
227 | } |
||||
228 | } |
||||
229 | |||||
230 | /** |
||||
231 | * Convert files from TypeScript and other formats into JavaScript. |
||||
232 | */ |
||||
233 | 15 | private function convertJs(AssetBundle $bundle): void |
|||
234 | { |
||||
235 | /** |
||||
236 | * @psalm-var AssetConverterInterface $this->converter |
||||
237 | * @psalm-var string $bundle->basePath |
||||
238 | * @psalm-var ConverterOptions $bundle->converterOptions |
||||
239 | * |
||||
240 | * @var JsFile|string $js |
||||
241 | */ |
||||
242 | 15 | foreach ($bundle->js as $i => $js) { |
|||
243 | 15 | if (is_array($js)) { |
|||
244 | 5 | $file = $js[0]; |
|||
245 | 5 | if (AssetUtil::isRelative($file)) { |
|||
246 | 5 | $baseFile = $this->aliases->get("{$bundle->basePath}/{$file}"); |
|||
247 | 5 | if (is_file($baseFile)) { |
|||
248 | 5 | $js[0] = $this->converter->convert( |
|||
249 | 5 | $file, |
|||
250 | 5 | $bundle->basePath, |
|||
0 ignored issues
–
show
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
![]() |
|||||
251 | 5 | $bundle->converterOptions |
|||
252 | 5 | ); |
|||
253 | |||||
254 | 5 | $bundle->js[$i] = $js; |
|||
255 | } |
||||
256 | } |
||||
257 | 15 | } elseif (AssetUtil::isRelative($js)) { |
|||
258 | 15 | $baseJs = $this->aliases->get("{$bundle->basePath}/{$js}"); |
|||
259 | 15 | if (is_file($baseJs)) { |
|||
260 | 14 | $bundle->js[$i] = $this->converter->convert($js, $bundle->basePath); |
|||
261 | } |
||||
262 | } |
||||
263 | } |
||||
264 | } |
||||
265 | |||||
266 | /** |
||||
267 | * Registers a CSS file. |
||||
268 | * |
||||
269 | * @throws InvalidConfigException |
||||
270 | */ |
||||
271 | 36 | private function registerCssFile(AssetBundle $bundle, ?string $key, array|string $css): void |
|||
272 | { |
||||
273 | 36 | if (is_array($css)) { |
|||
0 ignored issues
–
show
|
|||||
274 | 10 | if (!array_key_exists(0, $css)) { |
|||
275 | 1 | throw new InvalidConfigException('Do not set in array CSS URL.'); |
|||
276 | } |
||||
277 | 9 | $url = $css[0]; |
|||
278 | } else { |
||||
279 | 33 | $url = $css; |
|||
280 | } |
||||
281 | |||||
282 | 35 | if (!is_string($url)) { |
|||
283 | 1 | throw new InvalidConfigException( |
|||
284 | 1 | sprintf( |
|||
285 | 1 | 'CSS file should be string. Got %s.', |
|||
286 | 1 | get_debug_type($url), |
|||
287 | 1 | ) |
|||
288 | 1 | ); |
|||
289 | } |
||||
290 | |||||
291 | 34 | if ($url === '') { |
|||
292 | 1 | throw new InvalidConfigException('CSS file should be non empty string.'); |
|||
293 | } |
||||
294 | |||||
295 | 33 | $url = $this->loader->getAssetUrl($bundle, $url); |
|||
296 | |||||
297 | 33 | if (is_array($css)) { |
|||
0 ignored issues
–
show
|
|||||
298 | 7 | $css[0] = $url; |
|||
299 | } else { |
||||
300 | 33 | $css = [$url]; |
|||
301 | } |
||||
302 | |||||
303 | 33 | if ($bundle->cssPosition !== null && !isset($css[1])) { |
|||
304 | 3 | $css[1] = $bundle->cssPosition; |
|||
305 | } |
||||
306 | |||||
307 | /** @psalm-var CssFile */ |
||||
308 | 33 | $css = $this->mergeOptionsWithArray($bundle->cssOptions, $css); |
|||
309 | |||||
310 | /** @psalm-suppress MixedPropertyTypeCoercion It's Psalm bug https://github.com/vimeo/psalm/issues/9810 */ |
||||
311 | 33 | $this->cssFiles[$key ?: $url] = $css; |
|||
312 | } |
||||
313 | |||||
314 | /** |
||||
315 | * Registers a CSS string. |
||||
316 | * |
||||
317 | * @throws InvalidConfigException |
||||
318 | */ |
||||
319 | 6 | private function registerCssString(AssetBundle $bundle, ?string $key, mixed $cssString): void |
|||
320 | { |
||||
321 | 6 | if (is_array($cssString)) { |
|||
322 | 6 | $config = $cssString; |
|||
323 | 6 | if (!array_key_exists(0, $config)) { |
|||
324 | 6 | throw new InvalidConfigException('CSS string do not set in array.'); |
|||
325 | } |
||||
326 | } else { |
||||
327 | 6 | $config = [$cssString]; |
|||
328 | } |
||||
329 | |||||
330 | 6 | if ($bundle->cssPosition !== null && !isset($config[1])) { |
|||
331 | 1 | $config[1] = $bundle->cssPosition; |
|||
332 | } |
||||
333 | |||||
334 | /** @psalm-var CssString */ |
||||
335 | 6 | $config = $this->mergeOptionsWithArray($bundle->cssOptions, $config); |
|||
336 | |||||
337 | 6 | if ($key === null) { |
|||
338 | /** @psalm-suppress MixedPropertyTypeCoercion It's Psalm bug https://github.com/vimeo/psalm/issues/9810 */ |
||||
339 | 6 | $this->cssStrings[] = $config; |
|||
340 | } else { |
||||
341 | /** @psalm-suppress MixedPropertyTypeCoercion It's Psalm bug https://github.com/vimeo/psalm/issues/9810 */ |
||||
342 | 6 | $this->cssStrings[$key] = $config; |
|||
343 | } |
||||
344 | } |
||||
345 | |||||
346 | /** |
||||
347 | * Registers a JavaScript file. |
||||
348 | * |
||||
349 | * @throws InvalidConfigException |
||||
350 | */ |
||||
351 | 51 | private function registerJsFile(AssetBundle $bundle, ?string $key, array|string $js): void |
|||
352 | { |
||||
353 | 51 | if (is_array($js)) { |
|||
0 ignored issues
–
show
|
|||||
354 | 14 | if (!array_key_exists(0, $js)) { |
|||
355 | 1 | throw new InvalidConfigException('Do not set in array JavaScript URL.'); |
|||
356 | } |
||||
357 | 13 | $url = $js[0]; |
|||
358 | } else { |
||||
359 | 45 | $url = $js; |
|||
360 | } |
||||
361 | |||||
362 | 50 | if (!is_string($url)) { |
|||
363 | 1 | throw new InvalidConfigException( |
|||
364 | 1 | sprintf( |
|||
365 | 1 | 'JavaScript file should be string. Got %s.', |
|||
366 | 1 | get_debug_type($url), |
|||
367 | 1 | ) |
|||
368 | 1 | ); |
|||
369 | } |
||||
370 | |||||
371 | 49 | if ($url === '') { |
|||
372 | 1 | throw new InvalidConfigException('JavaScript file should be non empty string.'); |
|||
373 | } |
||||
374 | |||||
375 | 48 | $url = $this->loader->getAssetUrl($bundle, $url); |
|||
376 | |||||
377 | 45 | if (is_array($js)) { |
|||
0 ignored issues
–
show
|
|||||
378 | 11 | $js[0] = $url; |
|||
379 | } else { |
||||
380 | 42 | $js = [$url]; |
|||
381 | } |
||||
382 | |||||
383 | 45 | if ($bundle->jsPosition !== null && !isset($js[1])) { |
|||
384 | 10 | $js[1] = $bundle->jsPosition; |
|||
385 | } |
||||
386 | |||||
387 | /** @psalm-var JsFile */ |
||||
388 | 45 | $js = $this->mergeOptionsWithArray($bundle->jsOptions, $js); |
|||
389 | |||||
390 | /** @psalm-suppress MixedPropertyTypeCoercion It's Psalm bug https://github.com/vimeo/psalm/issues/9810 */ |
||||
391 | 45 | $this->jsFiles[$key ?: $url] = $js; |
|||
392 | } |
||||
393 | |||||
394 | /** |
||||
395 | * Registers a JavaScript string. |
||||
396 | * |
||||
397 | * @throws InvalidConfigException |
||||
398 | */ |
||||
399 | 7 | private function registerJsString(AssetBundle $bundle, ?string $key, mixed $jsString): void |
|||
400 | { |
||||
401 | 7 | if (is_array($jsString)) { |
|||
402 | 6 | if (!array_key_exists(0, $jsString)) { |
|||
403 | 6 | throw new InvalidConfigException('JavaScript string do not set in array.'); |
|||
404 | } |
||||
405 | } else { |
||||
406 | 7 | $jsString = [$jsString]; |
|||
407 | } |
||||
408 | |||||
409 | 7 | if ($bundle->jsPosition !== null && !isset($jsString[1])) { |
|||
410 | 1 | $jsString[1] = $bundle->jsPosition; |
|||
411 | } |
||||
412 | |||||
413 | /** @psalm-var JsString */ |
||||
414 | 7 | $jsString = $this->mergeOptionsWithArray($bundle->jsOptions, $jsString); |
|||
415 | |||||
416 | 6 | if ($key === null) { |
|||
417 | /** @psalm-suppress MixedPropertyTypeCoercion It's Psalm bug https://github.com/vimeo/psalm/issues/9810 */ |
||||
418 | 6 | $this->jsStrings[] = $jsString; |
|||
419 | } else { |
||||
420 | /** @psalm-suppress MixedPropertyTypeCoercion It's Psalm bug https://github.com/vimeo/psalm/issues/9810 */ |
||||
421 | 6 | $this->jsStrings[$key] = $jsString; |
|||
422 | } |
||||
423 | } |
||||
424 | |||||
425 | /** |
||||
426 | * Registers a JavaScript variable. |
||||
427 | */ |
||||
428 | 6 | private function registerJsVar(string $name, mixed $value, ?int $position): void |
|||
429 | { |
||||
430 | 6 | $config = [$name, $value]; |
|||
431 | |||||
432 | 6 | if ($position !== null) { |
|||
433 | 6 | $config[2] = $position; |
|||
434 | } |
||||
435 | |||||
436 | 6 | $this->jsVars[$name] = $config; |
|||
437 | } |
||||
438 | |||||
439 | /** |
||||
440 | * Registers a JavaScript variable by config. |
||||
441 | * |
||||
442 | * @throws InvalidConfigException |
||||
443 | */ |
||||
444 | 11 | private function registerJsVarByConfig(mixed $config, ?int $bundleJsPosition): void |
|||
445 | { |
||||
446 | 11 | if (!is_array($config)) { |
|||
447 | 1 | throw new InvalidConfigException( |
|||
448 | 1 | sprintf( |
|||
449 | 1 | 'Without string key JavaScript variable should be array. Got %s.', |
|||
450 | 1 | get_debug_type($config), |
|||
451 | 1 | ) |
|||
452 | 1 | ); |
|||
453 | } |
||||
454 | |||||
455 | 10 | if (!array_key_exists(0, $config)) { |
|||
456 | 1 | throw new InvalidConfigException('Do not set JavaScript variable name.'); |
|||
457 | } |
||||
458 | 9 | $name = $config[0]; |
|||
459 | |||||
460 | 9 | if (!is_string($name)) { |
|||
461 | 1 | throw new InvalidConfigException( |
|||
462 | 1 | sprintf( |
|||
463 | 1 | 'JavaScript variable name should be string. Got %s.', |
|||
464 | 1 | get_debug_type($name), |
|||
465 | 1 | ) |
|||
466 | 1 | ); |
|||
467 | } |
||||
468 | |||||
469 | 8 | if (!array_key_exists(1, $config)) { |
|||
470 | 1 | throw new InvalidConfigException('Do not set JavaScript variable value.'); |
|||
471 | } |
||||
472 | 7 | $value = $config[1]; |
|||
473 | |||||
474 | 7 | $position = $config[2] ?? $bundleJsPosition; |
|||
475 | 7 | if (!is_int($position)) { |
|||
476 | 1 | throw new InvalidConfigException( |
|||
477 | 1 | sprintf( |
|||
478 | 1 | 'JavaScript variable position should be integer. Got %s.', |
|||
479 | 1 | get_debug_type($position), |
|||
480 | 1 | ) |
|||
481 | 1 | ); |
|||
482 | } |
||||
483 | |||||
484 | 6 | $this->registerJsVar($name, $value, $position); |
|||
485 | } |
||||
486 | |||||
487 | /** |
||||
488 | * @throws InvalidConfigException |
||||
489 | */ |
||||
490 | 46 | private function mergeOptionsWithArray(array $options, array $array): array |
|||
491 | { |
||||
492 | 46 | foreach ($options as $key => $value) { |
|||
493 | 9 | if (is_int($key)) { |
|||
494 | 1 | throw new InvalidConfigException( |
|||
495 | 1 | 'JavaScript or CSS options should be list of key/value pairs with string keys. Got integer key.' |
|||
496 | 1 | ); |
|||
497 | } |
||||
498 | |||||
499 | 8 | if (!array_key_exists($key, $array)) { |
|||
500 | 8 | $array[$key] = $value; |
|||
501 | } |
||||
502 | } |
||||
503 | |||||
504 | 45 | return $array; |
|||
505 | } |
||||
506 | } |
||||
507 |
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.