Passed
Push — master ( 7e03c1...1ea979 )
by Alexander
02:25
created

AssetManager::registerAllAllowed()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

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

371
                        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...
372 1
                            $file,
373 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

373
                            /** @scrutinizer ignore-type */ $bundle->basePath,
Loading history...
374 1
                            $bundle->converterOptions,
375
                        ));
376
377 1
                        $bundle->css[$i] = $css;
378
                    }
379
                }
380 13
            } elseif (AssetUtil::isRelative($css)) {
381 13
                $baseCss = $this->aliases->get("{$bundle->basePath}/{$css}");
382 13
                if (is_file("$baseCss")) {
383
                    /**
384
                     * @psalm-suppress PossiblyNullArgument
385
                     * @psalm-suppress PossiblyNullReference
386
                     */
387 12
                    $bundle->css[$i] = $this->converter->convert(
388 12
                        $css,
389 12
                        $bundle->basePath,
390 12
                        $bundle->converterOptions
391
                    );
392
                }
393
            }
394
        }
395 14
    }
396
397
    /**
398
     * Convert files from TypeScript and other formats into JavaScript.
399
     *
400
     * @param AssetBundle $bundle
401
     */
402 14
    private function convertJs(AssetBundle $bundle): void
403
    {
404 14
        foreach ($bundle->js as $i => $js) {
405 14
            if (is_array($js)) {
406 1
                $file = array_shift($js);
407 1
                if (AssetUtil::isRelative($file)) {
408 1
                    $js = array_merge($bundle->jsOptions, $js);
409 1
                    $baseFile = $this->aliases->get("{$bundle->basePath}/{$file}");
410 1
                    if (is_file($baseFile)) {
411
                        /**
412
                         * @psalm-suppress PossiblyNullArgument
413
                         * @psalm-suppress PossiblyNullReference
414
                         */
415 1
                        array_unshift($js, $this->converter->convert(
416 1
                            $file,
417 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

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