Passed
Push — master ( 506152...7c24f3 )
by Alexander
05:14 queued 01:53
created

AssetManager::getAssetUrl()   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 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Assets;
6
7
use RuntimeException;
8
use Yiisoft\Aliases\Aliases;
9
use Yiisoft\Assets\Exception\InvalidConfigException;
10
11
use function array_key_exists;
12
use function array_merge;
13
use function array_shift;
14
use function array_unshift;
15
use function in_array;
16
use function is_array;
17
use function is_file;
18
use function is_int;
19
20
/**
21
 * AssetManager manages asset bundle configuration and loading.
22
 */
23
final class AssetManager
24
{
25
    /**
26
     * @var string[] List of names of allowed asset bundles. If the array is empty, then any asset bundles are allowed.
27
     */
28
    private array $allowedBundleNames;
29
30
    /**
31
     * @var array The asset bundle configurations. This property is provided to customize asset bundles.
32
     */
33
    private array $customizedBundles;
34
35
    /**
36
     * @var array AssetBundle[] list of the registered asset bundles.
37
     * The keys are the bundle names, and the values are the registered {@see AssetBundle} objects.
38
     *
39
     * {@see registerAssetBundle()}
40
     */
41
    private array $registeredBundles = [];
42
43
    private array $loadedBundles = [];
44
    private array $dummyBundles = [];
45
    private array $cssFiles = [];
46
    private array $jsFiles = [];
47
    private array $jsStrings = [];
48
    private array $jsVar = [];
49
    private ?AssetConverterInterface $converter = null;
50
    private ?AssetPublisherInterface $publisher = null;
51
    private AssetLoaderInterface $loader;
52
    private Aliases $aliases;
53
54
    /**
55
     * @param Aliases $aliases The aliases instance.
56
     * @param AssetLoaderInterface $loader The loader instance.
57
     * @param string[] $allowedBundleNames List of names of allowed asset bundles. If the array is empty, then any
58
     * asset bundles are allowed. If the names of allowed asset bundles were specified, only these asset bundles
59
     * or their dependencies can be registered {@see register()} and obtained {@see getBundle()}. Also, specifying
60
     * names allows to export {@see export()} asset bundles automatically without first registering them manually.
61
     * @param array $customizedBundles The asset bundle configurations. Provided to customize asset bundles.
62
     * When a bundle is being loaded by {@see getBundle()}, if it has a corresponding configuration specified
63
     * here, the configuration will be applied to the bundle. The array keys are the asset class bundle names
64
     * (without leading backslash). If a value is false, it means the corresponding asset bundle is disabled
65
     * and {@see getBundle()} should return an instance of the specified asset bundle with empty property values.
66
     */
67 90
    public function __construct(
68
        Aliases $aliases,
69
        AssetLoaderInterface $loader,
70
        array $allowedBundleNames = [],
71
        array $customizedBundles = []
72
    ) {
73 90
        $this->aliases = $aliases;
74 90
        $this->loader = $loader;
75 90
        $this->allowedBundleNames = $allowedBundleNames;
76 90
        $this->customizedBundles = $customizedBundles;
77 90
    }
78
79
    /**
80
     * Returns a cloned named asset bundle.
81
     *
82
     * This method will first look for the bundle in {@see $customizedBundles}.
83
     * If not found, it will treat `$name` as the class of the asset bundle and create a new instance of it.
84
     * If `$name` is not a class name, an {@see AssetBundle} instance will be created.
85
     *
86
     * Cloning is used to prevent an asset bundle instance from being modified in a non-context of the asset manager.
87
     *
88
     * @param string $name The class name of the asset bundle (without the leading backslash).
89
     *
90
     * @throws InvalidConfigException For invalid asset bundle configuration.
91
     *
92
     * @return AssetBundle The asset bundle instance.
93
     */
94 9
    public function getBundle(string $name): AssetBundle
95
    {
96 9
        if (!empty($this->allowedBundleNames)) {
97 4
            $this->checkAllowedBundleName($name);
98
        }
99
100 9
        $bundle = $this->loadBundle($name);
101 9
        $bundle = $this->publishBundle($bundle);
102
103 9
        return clone $bundle;
104
    }
105
106
    /**
107
     * Returns the actual URL for the specified asset.
108
     *
109
     * @param string $name The asset bundle name.
110
     * @param string $path The asset path.
111
     *
112
     * @throws InvalidConfigException If asset files are not found.
113
     *
114
     * @return string The actual URL for the specified asset.
115
     */
116 1
    public function getAssetUrl(string $name, string $path): string
117
    {
118 1
        return $this->loader->getAssetUrl($this->getBundle($name), $path);
119
    }
120
121
    /**
122
     * Return config array CSS AssetBundle.
123
     *
124
     * @return array
125
     */
126 16
    public function getCssFiles(): array
127
    {
128 16
        return $this->cssFiles;
129
    }
130
131
    /**
132
     * Returns config array JS AssetBundle.
133
     *
134
     * @return array
135
     */
136 26
    public function getJsFiles(): array
137
    {
138 26
        return $this->jsFiles;
139
    }
140
141
    /**
142
     * Returns JS code blocks.
143
     *
144
     * @return array
145
     */
146 1
    public function getJsStrings(): array
147
    {
148 1
        return $this->jsStrings;
149
    }
150
151
    /**
152
     * Returns JS variables.
153
     *
154
     * @return array
155
     */
156 1
    public function getJsVar(): array
157
    {
158 1
        return $this->jsVar;
159
    }
160
161
    /**
162
     * Returns a new instance with the specified converter.
163
     *
164
     * @param AssetConverterInterface $converter
165
     *
166
     * @return self
167
     */
168 90
    public function withConverter(AssetConverterInterface $converter): self
169
    {
170 90
        $new = clone $this;
171 90
        $new->converter = $converter;
172 90
        return $new;
173
    }
174
175
    /**
176
     * Returns a new instance with the specified loader.
177
     *
178
     * @param AssetLoaderInterface $loader
179
     *
180
     * @return self
181
     */
182 25
    public function withLoader(AssetLoaderInterface $loader): self
183
    {
184 25
        $new = clone $this;
185 25
        $new->loader = $loader;
186 25
        return $new;
187
    }
188
189
    /**
190
     * Returns a new instance with the specified publisher.
191
     *
192
     * @param AssetPublisherInterface $publisher
193
     *
194
     * @return self
195
     */
196 90
    public function withPublisher(AssetPublisherInterface $publisher): self
197
    {
198 90
        $new = clone $this;
199 90
        $new->publisher = $publisher;
200 90
        return $new;
201
    }
202
203
    /**
204
     * Exports registered asset bundles.
205
     *
206
     * When using the allowed asset bundles, the export result will always be the same,
207
     * since the asset bundles are registered before the export. If do not use the allowed asset bundles mode,
208
     * must register {@see register()} all the required asset bundles before exporting.
209
     *
210
     * @param AssetExporterInterface $exporter The exporter instance.
211
     *
212
     * @throws InvalidConfigException If an error occurs during registration when using allowed asset bundles.
213
     * @throws RuntimeException If no asset bundles were registered or an error occurred during the export.
214
     */
215 8
    public function export(AssetExporterInterface $exporter): void
216
    {
217 8
        if (!empty($this->allowedBundleNames)) {
218 3
            $this->registerAllAllowed();
219
        }
220
221 8
        if (empty($this->registeredBundles)) {
222 1
            throw new RuntimeException('Not a single asset bundle was registered.');
223
        }
224
225 7
        $exporter->export($this->registeredBundles);
226 7
    }
227
228
    /**
229
     * Registers asset bundles by names.
230
     *
231
     * @param string[] $names
232
     * @param int|null $position
233
     *
234
     * @throws InvalidConfigException
235
     * @throws RuntimeException
236
     */
237 90
    public function register(array $names, ?int $position = null): void
238
    {
239 90
        if (!empty($this->allowedBundleNames)) {
240 3
            foreach ($names as $name) {
241 3
                $this->checkAllowedBundleName($name);
242
            }
243
        }
244
245 90
        foreach ($names as $name) {
246 40
            $this->registerAssetBundle($name, $position);
247 36
            $this->registerFiles($name);
248
        }
249 90
    }
250
251
    /**
252
     * Registers all allowed asset bundles.
253
     *
254
     * @throws InvalidConfigException
255
     * @throws RuntimeException
256
     */
257 5
    public function registerAllAllowed(): void
258
    {
259 5
        if (empty($this->allowedBundleNames)) {
260 1
            throw new RuntimeException('The allowed names of the asset bundles were not set.');
261
        }
262
263 4
        foreach ($this->allowedBundleNames as $name) {
264 4
            $this->registerAssetBundle($name);
265 4
            $this->registerFiles($name);
266
        }
267 4
    }
268
269
    /**
270
     * Returns whether the asset bundle is registered.
271
     *
272
     * @param string $name The class name of the asset bundle (without the leading backslash).
273
     *
274
     * @return bool Whether the asset bundle is registered.
275
     */
276 4
    public function isRegisteredBundle(string $name): bool
277
    {
278 4
        return isset($this->registeredBundles[$name]);
279
    }
280
281
    /**
282
     * Registers a CSS file.
283
     *
284
     * @param string $url The CSS file to be registered.
285
     * @param array $options The HTML attributes for the link tag.
286
     * @param string|null $key The key that identifies the CSS file.
287
     */
288 35
    private function registerCssFile(string $url, array $options = [], string $key = null): void
289
    {
290 35
        $key = $key ?: $url;
291
292 35
        $this->cssFiles[$key]['url'] = $url;
293 35
        $this->cssFiles[$key]['attributes'] = $options;
294 35
    }
295
296
    /**
297
     * Registers a JS file.
298
     *
299
     * @param string $url The JS file to be registered.
300
     * @param array $options The HTML attributes for the script tag. The following options are specially handled and
301
     * are not treated as HTML attributes:
302
     *
303
     * - `position`: specifies where the JS script tag should be inserted in a page. The possible values are:
304
     *     * {@see \Yiisoft\View\WebView::POSITION_HEAD} In the head section.
305
     *     * {@see \Yiisoft\View\WebView::POSITION_BEGIN} At the beginning of the body section.
306
     *     * {@see \Yiisoft\View\WebView::POSITION_END} At the end of the body section. This is the default value.
307
     * @param string|null $key The key that identifies the JS file.
308
     */
309 45
    private function registerJsFile(string $url, array $options = [], string $key = null): void
310
    {
311 45
        $key = $key ?: $url;
312
313 45
        if (!array_key_exists('position', $options)) {
314 39
            $options = array_merge(['position' => 3], $options);
315
        }
316
317 45
        $this->jsFiles[$key]['url'] = $url;
318 45
        $this->jsFiles[$key]['attributes'] = $options;
319 45
    }
320
321
    /**
322
     * Registers a JavaScript code block.
323
     *
324
     * @param string $jsString The JavaScript code block to be registered.
325
     * @param array $options The HTML attributes for the script tag. The following options are specially handled and
326
     * are not treated as HTML attributes:
327
     *
328
     * - `position`: specifies where the JS script tag should be inserted in a page. The possible values are:
329
     *     * {@see \Yiisoft\View\WebView::POSITION_HEAD} In the head section.
330
     *     * {@see \Yiisoft\View\WebView::POSITION_BEGIN} At the beginning of the body section.
331
     *     * {@see \Yiisoft\View\WebView::POSITION_END} At the end of the body section. This is the default value.
332
     * @param string|null $key The key that identifies the JS code block. If null, it will use $jsString as the key.
333
     * If two JS code blocks are registered with the same key, the latter will overwrite the former.
334
     */
335 4
    private function registerJsString(string $jsString, array $options = [], string $key = null): void
336
    {
337 4
        $key = $key ?: $jsString;
338
339 4
        if (!array_key_exists('position', $options)) {
340 4
            $options = array_merge(['position' => 3], $options);
341
        }
342
343 4
        $this->jsStrings[$key]['string'] = $jsString;
344 4
        $this->jsStrings[$key]['attributes'] = $options;
345 4
    }
346
347
    /**
348
     * Registers a JS variable.
349
     *
350
     * @param string $varName The variable name.
351
     * @param array|string $jsVar The JS code block to be registered.
352
     * @param array $options The HTML attributes for the script tag. The following options are specially handled and
353
     * are not treated as HTML attributes:
354
     *
355
     * - `position`: specifies where the JS script tag should be inserted in a page. The possible values are:
356
     *     * {@see \Yiisoft\View\WebView::POSITION_HEAD} In the head section. This is the default value.
357
     *     * {@see \Yiisoft\View\WebView::POSITION_BEGIN} At the beginning of the body section.
358
     *     * {@see \Yiisoft\View\WebView::POSITION_END} At the end of the body section.
359
     */
360 4
    private function registerJsVar(string $varName, $jsVar, array $options = []): void
361
    {
362 4
        if (!array_key_exists('position', $options)) {
363 4
            $options = array_merge(['position' => 1], $options);
364
        }
365
366 4
        $this->jsVar[$varName]['variables'] = $jsVar;
367 4
        $this->jsVar[$varName]['attributes'] = $options;
368 4
    }
369
370
    /**
371
     * Converter SASS, SCSS, Stylus and other formats to CSS.
372
     *
373
     * @param AssetBundle $bundle
374
     */
375 15
    private function convertCss(AssetBundle $bundle): void
376
    {
377 15
        foreach ($bundle->css as $i => $css) {
378 14
            if (is_array($css)) {
379 1
                $file = array_shift($css);
380 1
                if (AssetUtil::isRelative($file)) {
381 1
                    $css = array_merge($bundle->cssOptions, $css);
382 1
                    $baseFile = $this->aliases->get("{$bundle->basePath}/{$file}");
383 1
                    if (is_file($baseFile)) {
384
                        /**
385
                         * @psalm-suppress PossiblyNullArgument
386
                         * @psalm-suppress PossiblyNullReference
387
                         */
388 1
                        array_unshift($css, $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

388
                        array_unshift($css, $this->converter->/** @scrutinizer ignore-call */ 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...
389 1
                            $file,
390 1
                            $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

390
                            /** @scrutinizer ignore-type */ $bundle->basePath,
Loading history...
391 1
                            $bundle->converterOptions,
392
                        ));
393
394 1
                        $bundle->css[$i] = $css;
395
                    }
396
                }
397 14
            } elseif (AssetUtil::isRelative($css)) {
398 14
                $baseCss = $this->aliases->get("{$bundle->basePath}/{$css}");
399 14
                if (is_file("$baseCss")) {
400
                    /**
401
                     * @psalm-suppress PossiblyNullArgument
402
                     * @psalm-suppress PossiblyNullReference
403
                     */
404 13
                    $bundle->css[$i] = $this->converter->convert(
405 13
                        $css,
406 13
                        $bundle->basePath,
407 13
                        $bundle->converterOptions
408
                    );
409
                }
410
            }
411
        }
412 15
    }
413
414
    /**
415
     * Convert files from TypeScript and other formats into JavaScript.
416
     *
417
     * @param AssetBundle $bundle
418
     */
419 15
    private function convertJs(AssetBundle $bundle): void
420
    {
421 15
        foreach ($bundle->js as $i => $js) {
422 15
            if (is_array($js)) {
423 2
                $file = array_shift($js);
424 2
                if (AssetUtil::isRelative($file)) {
425 2
                    $js = array_merge($bundle->jsOptions, $js);
426 2
                    $baseFile = $this->aliases->get("{$bundle->basePath}/{$file}");
427 2
                    if (is_file($baseFile)) {
428
                        /**
429
                         * @psalm-suppress PossiblyNullArgument
430
                         * @psalm-suppress PossiblyNullReference
431
                         */
432 2
                        array_unshift($js, $this->converter->convert(
433 2
                            $file,
434 2
                            $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

434
                            /** @scrutinizer ignore-type */ $bundle->basePath,
Loading history...
435 2
                            $bundle->converterOptions
436
                        ));
437
438 2
                        $bundle->js[$i] = $js;
439
                    }
440
                }
441 15
            } elseif (AssetUtil::isRelative($js)) {
442 15
                $baseJs = $this->aliases->get("{$bundle->basePath}/{$js}");
443 15
                if (is_file($baseJs)) {
444
                    /**
445
                     * @psalm-suppress PossiblyNullArgument
446
                     * @psalm-suppress PossiblyNullReference
447
                     */
448 14
                    $bundle->js[$i] = $this->converter->convert($js, $bundle->basePath);
449
                }
450
            }
451
        }
452 15
    }
453
454
    /**
455
     * Registers the named asset bundle.
456
     *
457
     * All dependent asset bundles will be registered.
458
     *
459
     * @param string $name The class name of the asset bundle (without the leading backslash).
460
     * @param int|null $position If set, this forces a minimum position for javascript files.
461
     * This will adjust depending assets javascript file position or fail if requirement can not be met.
462
     * If this is null, asset bundles position settings will not be changed.
463
     *
464
     * {@see registerJsFile()} For more details on javascript position.
465
     *
466
     * @throws InvalidConfigException If the asset or the asset file paths to be published does not exist.
467
     * @throws RuntimeException If the asset bundle does not exist or a circular dependency is detected.
468
     */
469 44
    private function registerAssetBundle(string $name, int $position = null): void
470
    {
471 44
        if (!isset($this->registeredBundles[$name])) {
472 44
            $bundle = $this->publishBundle($this->loadBundle($name));
473
474 43
            $this->registeredBundles[$name] = false;
475
476 43
            $pos = $bundle->jsOptions['position'] ?? null;
477
478 43
            foreach ($bundle->depends as $dep) {
479 31
                $this->registerAssetBundle($dep, $pos);
480
            }
481
482 42
            unset($this->registeredBundles[$name]);
483 42
            $this->registeredBundles[$name] = $bundle;
484 12
        } elseif ($this->registeredBundles[$name] === false) {
485 1
            throw new RuntimeException("A circular dependency is detected for bundle \"{$name}\".");
486
        } else {
487 11
            $bundle = $this->registeredBundles[$name];
488
        }
489
490 42
        if ($position !== null) {
491 11
            $pos = $bundle->jsOptions['position'] ?? null;
492
493 11
            if ($pos === null) {
494 10
                $bundle->jsOptions['position'] = $pos = $position;
495 5
            } elseif ($pos > $position) {
496 4
                throw new RuntimeException(
497 4
                    "An asset bundle that depends on \"{$name}\" has a higher JavaScript file " .
498 4
                    "position configured than \"{$name}\"."
499
                );
500
            }
501
502
            // update position for all dependencies
503 11
            foreach ($bundle->depends as $dep) {
504 7
                $this->registerAssetBundle($dep, $pos);
505
            }
506
        }
507 42
    }
508
509
    /**
510
     * Register assets from a named bundle and its dependencies.
511
     *
512
     * @param string $bundleName The asset bundle name.
513
     *
514
     * @throws InvalidConfigException If asset files are not found.
515
     */
516 40
    private function registerFiles(string $bundleName): void
517
    {
518 40
        if (!isset($this->registeredBundles[$bundleName])) {
519
            return;
520
        }
521
522 40
        $bundle = $this->registeredBundles[$bundleName];
523
524 40
        foreach ($bundle->depends as $dep) {
525 28
            $this->registerFiles($dep);
526
        }
527
528 40
        $this->registerAssetFiles($bundle);
529 37
    }
530
531
    /**
532
     * Registers asset files from a bundle considering dependencies.
533
     *
534
     * @param AssetBundle $bundle
535
     *
536
     * @throws InvalidConfigException If asset files are not found.
537
     */
538 40
    private function registerAssetFiles(AssetBundle $bundle): void
539
    {
540 40
        if (isset($bundle->basePath, $bundle->baseUrl) && null !== $this->converter) {
541 15
            $this->convertCss($bundle);
542 15
            $this->convertJs($bundle);
543
        }
544
545 40
        foreach ($bundle->js as $js) {
546 38
            if (is_array($js)) {
547 3
                $file = array_shift($js);
548 3
                $options = array_merge($bundle->jsOptions, $js);
549 3
                $this->registerJsFile($this->loader->getAssetUrl($bundle, $file), $options);
550 37
            } elseif ($js !== null) {
551 37
                $this->registerJsFile($this->loader->getAssetUrl($bundle, $js), $bundle->jsOptions);
552
            }
553
        }
554
555 37
        foreach ($bundle->jsStrings as $key => $jsString) {
556 4
            $key = is_int($key) ? $jsString : $key;
557 4
            if (is_array($jsString)) {
558 4
                $string = array_shift($jsString);
559 4
                $this->registerJsString($string, $jsString, $key);
560 4
            } elseif ($jsString !== null) {
561 4
                $this->registerJsString($jsString, $bundle->jsOptions, $key);
562
            }
563
        }
564
565 37
        foreach ($bundle->jsVar as $key => $jsVar) {
566 4
            $this->registerJsVar($key, $jsVar, $jsVar);
567
        }
568
569 37
        foreach ($bundle->css as $css) {
570 25
            if (is_array($css)) {
571 1
                $file = array_shift($css);
572 1
                $options = array_merge($bundle->cssOptions, $css);
573 1
                $this->registerCssFile($this->loader->getAssetUrl($bundle, $file), $options);
574 25
            } elseif ($css !== null) {
575 25
                $this->registerCssFile($this->loader->getAssetUrl($bundle, $css), $bundle->cssOptions);
576
            }
577
        }
578 37
    }
579
580
    /**
581
     * Loads an asset bundle class by name.
582
     *
583
     * @param string $name The asset bundle name.
584
     *
585
     * @throws InvalidConfigException For invalid asset bundle configuration.
586
     *
587
     * @return AssetBundle The asset bundle instance.
588
     */
589 47
    private function loadBundle(string $name): AssetBundle
590
    {
591 47
        if (isset($this->loadedBundles[$name])) {
592 8
            return $this->loadedBundles[$name];
593
        }
594
595 47
        if (!isset($this->customizedBundles[$name])) {
596 42
            return $this->loadedBundles[$name] = $this->loader->loadBundle($name);
597
        }
598
599 20
        if ($this->customizedBundles[$name] instanceof AssetBundle) {
600 1
            return $this->loadedBundles[$name] = $this->customizedBundles[$name];
601
        }
602
603 19
        if (is_array($this->customizedBundles[$name])) {
604 17
            return $this->loadedBundles[$name] = $this->loader->loadBundle($name, $this->customizedBundles[$name]);
605
        }
606
607 2
        if ($this->customizedBundles[$name] === false) {
608 1
            return $this->dummyBundles[$name] ??= $this->loader->loadBundle($name, (array) (new AssetBundle()));
609
        }
610
611 1
        throw new InvalidConfigException("Invalid configuration of the \"{$name}\" asset bundle.");
612
    }
613
614
    /**
615
     * Publishes a asset bundle.
616
     *
617
     * @param AssetBundle $bundle The asset bundle to publish.
618
     *
619
     * @throws InvalidConfigException If the asset or the asset file paths to be published does not exist.
620
     *
621
     * @return AssetBundle The published asset bundle.
622
     */
623 46
    private function publishBundle(AssetBundle $bundle): AssetBundle
624
    {
625 46
        if (!$bundle->cdn && $this->publisher !== null && !empty($bundle->sourcePath)) {
626 13
            [$bundle->basePath, $bundle->baseUrl] = $this->publisher->publish($bundle);
627
        }
628
629 46
        return $bundle;
630
    }
631
632
    /**
633
     * Checks whether asset bundle are allowed by name {@see $allowedBundleNames}.
634
     *
635
     * @param string $name The asset bundle name to check.
636
     *
637
     * @throws InvalidConfigException For invalid asset bundle configuration.
638
     * @throws RuntimeException If The asset bundle name is not allowed.
639
     */
640 4
    public function checkAllowedBundleName(string $name): void
641
    {
642 4
        if (isset($this->loadedBundles[$name]) || in_array($name, $this->allowedBundleNames, true)) {
643 4
            return;
644
        }
645
646 3
        foreach ($this->allowedBundleNames as $bundleName) {
647 3
            if ($this->isAllowedBundleDependencies($name, $this->loadBundle($bundleName))) {
648 1
                return;
649
            }
650
        }
651
652 3
        throw new RuntimeException("The \"{$name}\" asset bundle is not allowed.");
653
    }
654
655
    /**
656
     * Recursively checks whether the asset bundle name is allowed in dependencies.
657
     *
658
     * @param string $name The asset bundle name to check.
659
     * @param AssetBundle $bundle The asset bundle to check.
660
     *
661
     * @throws InvalidConfigException For invalid asset bundle configuration.
662
     *
663
     * @return bool Whether the asset bundle name is allowed in dependencies.
664
     */
665 3
    private function isAllowedBundleDependencies(string $name, AssetBundle $bundle): bool
666
    {
667 3
        foreach ($bundle->depends as $depend) {
668 2
            if ($name === $depend || $this->isAllowedBundleDependencies($name, $this->loadBundle($depend))) {
669 1
                return true;
670
            }
671
        }
672
673 3
        return false;
674
    }
675
}
676