Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Revision 621 | Zur aktuellen Revision | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php declare(strict_types=1);
2
/*
3
 * This file is part of PHPUnit.
4
 *
5
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace PHPUnit\Util;
11
 
12
use const PHP_OS;
13
use const PHP_VERSION;
14
use function addcslashes;
15
use function array_flip;
16
use function array_key_exists;
17
use function array_merge;
18
use function array_unique;
19
use function array_unshift;
20
use function class_exists;
21
use function count;
22
use function explode;
23
use function extension_loaded;
24
use function function_exists;
25
use function get_class;
26
use function ini_get;
27
use function interface_exists;
28
use function is_array;
29
use function is_int;
30
use function method_exists;
31
use function phpversion;
32
use function preg_match;
33
use function preg_replace;
34
use function sprintf;
35
use function strncmp;
36
use function strpos;
37
use function strtolower;
38
use function trim;
39
use function version_compare;
40
use PHPUnit\Framework\CodeCoverageException;
41
use PHPUnit\Framework\ExecutionOrderDependency;
42
use PHPUnit\Framework\InvalidCoversTargetException;
43
use PHPUnit\Framework\SelfDescribing;
44
use PHPUnit\Framework\TestCase;
45
use PHPUnit\Framework\Warning;
46
use PHPUnit\Runner\Version;
47
use PHPUnit\Util\Annotation\Registry;
48
use ReflectionClass;
49
use ReflectionException;
50
use ReflectionMethod;
51
use SebastianBergmann\CodeUnit\CodeUnitCollection;
52
use SebastianBergmann\CodeUnit\InvalidCodeUnitException;
53
use SebastianBergmann\CodeUnit\Mapper;
54
use SebastianBergmann\Environment\OperatingSystem;
55
 
56
/**
57
 * @internal This class is not covered by the backward compatibility promise for PHPUnit
58
 */
59
final class Test
60
{
61
    /**
62
     * @var int
63
     */
64
    public const UNKNOWN = -1;
65
 
66
    /**
67
     * @var int
68
     */
69
    public const SMALL = 0;
70
 
71
    /**
72
     * @var int
73
     */
74
    public const MEDIUM = 1;
75
 
76
    /**
77
     * @var int
78
     */
79
    public const LARGE = 2;
80
 
81
    /**
82
     * @var array
83
     */
84
    private static $hookMethods = [];
85
 
86
    /**
87
     * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
88
     */
89
    public static function describe(\PHPUnit\Framework\Test $test): array
90
    {
91
        if ($test instanceof TestCase) {
92
            return [get_class($test), $test->getName()];
93
        }
94
 
95
        if ($test instanceof SelfDescribing) {
96
            return ['', $test->toString()];
97
        }
98
 
99
        return ['', get_class($test)];
100
    }
101
 
102
    public static function describeAsString(\PHPUnit\Framework\Test $test): string
103
    {
104
        if ($test instanceof SelfDescribing) {
105
            return $test->toString();
106
        }
107
 
108
        return get_class($test);
109
    }
110
 
111
    /**
112
     * @throws CodeCoverageException
113
     *
114
     * @return array|bool
115
     *
116
     * @psalm-param class-string $className
117
     */
118
    public static function getLinesToBeCovered(string $className, string $methodName)
119
    {
120
        $annotations = self::parseTestMethodAnnotations(
121
            $className,
122
            $methodName
123
        );
124
 
125
        if (!self::shouldCoversAnnotationBeUsed($annotations)) {
126
            return false;
127
        }
128
 
129
        return self::getLinesToBeCoveredOrUsed($className, $methodName, 'covers');
130
    }
131
 
132
    /**
133
     * Returns lines of code specified with the @uses annotation.
134
     *
135
     * @throws CodeCoverageException
136
     *
137
     * @psalm-param class-string $className
138
     */
139
    public static function getLinesToBeUsed(string $className, string $methodName): array
140
    {
141
        return self::getLinesToBeCoveredOrUsed($className, $methodName, 'uses');
142
    }
143
 
144
    public static function requiresCodeCoverageDataCollection(TestCase $test): bool
145
    {
146
        $annotations = self::parseTestMethodAnnotations(
147
            get_class($test),
148
            $test->getName(false)
149
        );
150
 
151
        // If there is no @covers annotation but a @coversNothing annotation on
152
        // the test method then code coverage data does not need to be collected
153
        if (isset($annotations['method']['coversNothing'])) {
154
            // @see https://github.com/sebastianbergmann/phpunit/issues/4947#issuecomment-1084480950
155
            // return false;
156
        }
157
 
158
        // If there is at least one @covers annotation then
159
        // code coverage data needs to be collected
160
        if (isset($annotations['method']['covers'])) {
161
            return true;
162
        }
163
 
164
        // If there is no @covers annotation but a @coversNothing annotation
165
        // then code coverage data does not need to be collected
166
        if (isset($annotations['class']['coversNothing'])) {
167
            // @see https://github.com/sebastianbergmann/phpunit/issues/4947#issuecomment-1084480950
168
            // return false;
169
        }
170
 
171
        // If there is no @coversNothing annotation then
172
        // code coverage data may be collected
173
        return true;
174
    }
175
 
176
    /**
177
     * @throws Exception
178
     *
179
     * @psalm-param class-string $className
180
     */
181
    public static function getRequirements(string $className, string $methodName): array
182
    {
183
        return self::mergeArraysRecursively(
184
            Registry::getInstance()->forClassName($className)->requirements(),
185
            Registry::getInstance()->forMethod($className, $methodName)->requirements()
186
        );
187
    }
188
 
189
    /**
190
     * Returns the missing requirements for a test.
191
     *
192
     * @throws Exception
193
     * @throws Warning
194
     *
195
     * @psalm-param class-string $className
196
     */
197
    public static function getMissingRequirements(string $className, string $methodName): array
198
    {
199
        $required = self::getRequirements($className, $methodName);
200
        $missing  = [];
201
        $hint     = null;
202
 
203
        if (!empty($required['PHP'])) {
204
            $operator = new VersionComparisonOperator(empty($required['PHP']['operator']) ? '>=' : $required['PHP']['operator']);
205
 
206
            if (!version_compare(PHP_VERSION, $required['PHP']['version'], $operator->asString())) {
207
                $missing[] = sprintf('PHP %s %s is required.', $operator->asString(), $required['PHP']['version']);
208
                $hint      = 'PHP';
209
            }
210
        } elseif (!empty($required['PHP_constraint'])) {
211
            $version = new \PharIo\Version\Version(self::sanitizeVersionNumber(PHP_VERSION));
212
 
213
            if (!$required['PHP_constraint']['constraint']->complies($version)) {
214
                $missing[] = sprintf(
215
                    'PHP version does not match the required constraint %s.',
216
                    $required['PHP_constraint']['constraint']->asString()
217
                );
218
 
219
                $hint = 'PHP_constraint';
220
            }
221
        }
222
 
223
        if (!empty($required['PHPUnit'])) {
224
            $phpunitVersion = Version::id();
225
 
226
            $operator = new VersionComparisonOperator(empty($required['PHPUnit']['operator']) ? '>=' : $required['PHPUnit']['operator']);
227
 
228
            if (!version_compare($phpunitVersion, $required['PHPUnit']['version'], $operator->asString())) {
229
                $missing[] = sprintf('PHPUnit %s %s is required.', $operator->asString(), $required['PHPUnit']['version']);
230
                $hint      = $hint ?? 'PHPUnit';
231
            }
232
        } elseif (!empty($required['PHPUnit_constraint'])) {
233
            $phpunitVersion = new \PharIo\Version\Version(self::sanitizeVersionNumber(Version::id()));
234
 
235
            if (!$required['PHPUnit_constraint']['constraint']->complies($phpunitVersion)) {
236
                $missing[] = sprintf(
237
                    'PHPUnit version does not match the required constraint %s.',
238
                    $required['PHPUnit_constraint']['constraint']->asString()
239
                );
240
 
241
                $hint = $hint ?? 'PHPUnit_constraint';
242
            }
243
        }
244
 
245
        if (!empty($required['OSFAMILY']) && $required['OSFAMILY'] !== (new OperatingSystem)->getFamily()) {
246
            $missing[] = sprintf('Operating system %s is required.', $required['OSFAMILY']);
247
            $hint      = $hint ?? 'OSFAMILY';
248
        }
249
 
250
        if (!empty($required['OS'])) {
251
            $requiredOsPattern = sprintf('/%s/i', addcslashes($required['OS'], '/'));
252
 
253
            if (!preg_match($requiredOsPattern, PHP_OS)) {
254
                $missing[] = sprintf('Operating system matching %s is required.', $requiredOsPattern);
255
                $hint      = $hint ?? 'OS';
256
            }
257
        }
258
 
259
        if (!empty($required['functions'])) {
260
            foreach ($required['functions'] as $function) {
261
                $pieces = explode('::', $function);
262
 
263
                if (count($pieces) === 2 && class_exists($pieces[0]) && method_exists($pieces[0], $pieces[1])) {
264
                    continue;
265
                }
266
 
267
                if (function_exists($function)) {
268
                    continue;
269
                }
270
 
271
                $missing[] = sprintf('Function %s is required.', $function);
272
                $hint      = $hint ?? 'function_' . $function;
273
            }
274
        }
275
 
276
        if (!empty($required['setting'])) {
277
            foreach ($required['setting'] as $setting => $value) {
278
                if (ini_get($setting) !== $value) {
279
                    $missing[] = sprintf('Setting "%s" must be "%s".', $setting, $value);
280
                    $hint      = $hint ?? '__SETTING_' . $setting;
281
                }
282
            }
283
        }
284
 
285
        if (!empty($required['extensions'])) {
286
            foreach ($required['extensions'] as $extension) {
287
                if (isset($required['extension_versions'][$extension])) {
288
                    continue;
289
                }
290
 
291
                if (!extension_loaded($extension)) {
292
                    $missing[] = sprintf('Extension %s is required.', $extension);
293
                    $hint      = $hint ?? 'extension_' . $extension;
294
                }
295
            }
296
        }
297
 
298
        if (!empty($required['extension_versions'])) {
299
            foreach ($required['extension_versions'] as $extension => $req) {
300
                $actualVersion = phpversion($extension);
301
 
302
                $operator = new VersionComparisonOperator(empty($req['operator']) ? '>=' : $req['operator']);
303
 
304
                if ($actualVersion === false || !version_compare($actualVersion, $req['version'], $operator->asString())) {
305
                    $missing[] = sprintf('Extension %s %s %s is required.', $extension, $operator->asString(), $req['version']);
306
                    $hint      = $hint ?? 'extension_' . $extension;
307
                }
308
            }
309
        }
310
 
311
        if ($hint && isset($required['__OFFSET'])) {
312
            array_unshift($missing, '__OFFSET_FILE=' . $required['__OFFSET']['__FILE']);
313
            array_unshift($missing, '__OFFSET_LINE=' . ($required['__OFFSET'][$hint] ?? 1));
314
        }
315
 
316
        return $missing;
317
    }
318
 
319
    /**
320
     * Returns the provided data for a method.
321
     *
322
     * @throws Exception
323
     *
324
     * @psalm-param class-string $className
325
     */
326
    public static function getProvidedData(string $className, string $methodName): ?array
327
    {
328
        return Registry::getInstance()->forMethod($className, $methodName)->getProvidedData();
329
    }
330
 
331
    /**
332
     * @psalm-param class-string $className
333
     */
334
    public static function parseTestMethodAnnotations(string $className, ?string $methodName = ''): array
335
    {
336
        $registry = Registry::getInstance();
337
 
338
        if ($methodName !== null) {
339
            try {
340
                return [
341
                    'method' => $registry->forMethod($className, $methodName)->symbolAnnotations(),
342
                    'class'  => $registry->forClassName($className)->symbolAnnotations(),
343
                ];
344
            } catch (Exception $methodNotFound) {
345
                // ignored
346
            }
347
        }
348
 
349
        return [
350
            'method' => null,
351
            'class'  => $registry->forClassName($className)->symbolAnnotations(),
352
        ];
353
    }
354
 
355
    /**
356
     * @psalm-param class-string $className
357
     */
358
    public static function getInlineAnnotations(string $className, string $methodName): array
359
    {
360
        return Registry::getInstance()->forMethod($className, $methodName)->getInlineAnnotations();
361
    }
362
 
363
    /** @psalm-param class-string $className */
364
    public static function getBackupSettings(string $className, string $methodName): array
365
    {
366
        return [
150 lars 367
            'backupGlobals'          => self::getBooleanAnnotationSetting(
148 lars 368
                $className,
369
                $methodName,
370
                'backupGlobals'
371
            ),
372
            'backupStaticAttributes' => self::getBooleanAnnotationSetting(
373
                $className,
374
                $methodName,
375
                'backupStaticAttributes'
376
            ),
377
        ];
378
    }
379
 
380
    /**
381
     * @psalm-param class-string $className
382
     *
383
     * @return ExecutionOrderDependency[]
384
     */
385
    public static function getDependencies(string $className, string $methodName): array
386
    {
387
        $annotations = self::parseTestMethodAnnotations(
388
            $className,
389
            $methodName
390
        );
391
 
392
        $dependsAnnotations = $annotations['class']['depends'] ?? [];
393
 
394
        if (isset($annotations['method']['depends'])) {
395
            $dependsAnnotations = array_merge(
396
                $dependsAnnotations,
397
                $annotations['method']['depends']
398
            );
399
        }
400
 
401
        // Normalize dependency name to className::methodName
402
        $dependencies = [];
403
 
404
        foreach ($dependsAnnotations as $value) {
405
            $dependencies[] = ExecutionOrderDependency::createFromDependsAnnotation($className, $value);
406
        }
407
 
408
        return array_unique($dependencies);
409
    }
410
 
411
    /** @psalm-param class-string $className */
412
    public static function getGroups(string $className, ?string $methodName = ''): array
413
    {
414
        $annotations = self::parseTestMethodAnnotations(
415
            $className,
416
            $methodName
417
        );
418
 
419
        $groups = [];
420
 
421
        if (isset($annotations['method']['author'])) {
422
            $groups[] = $annotations['method']['author'];
423
        } elseif (isset($annotations['class']['author'])) {
424
            $groups[] = $annotations['class']['author'];
425
        }
426
 
427
        if (isset($annotations['class']['group'])) {
428
            $groups[] = $annotations['class']['group'];
429
        }
430
 
431
        if (isset($annotations['method']['group'])) {
432
            $groups[] = $annotations['method']['group'];
433
        }
434
 
435
        if (isset($annotations['class']['ticket'])) {
436
            $groups[] = $annotations['class']['ticket'];
437
        }
438
 
439
        if (isset($annotations['method']['ticket'])) {
440
            $groups[] = $annotations['method']['ticket'];
441
        }
442
 
443
        foreach (['method', 'class'] as $element) {
444
            foreach (['small', 'medium', 'large'] as $size) {
445
                if (isset($annotations[$element][$size])) {
446
                    $groups[] = [$size];
447
 
448
                    break 2;
449
                }
450
            }
451
        }
452
 
453
        foreach (['method', 'class'] as $element) {
454
            if (isset($annotations[$element]['covers'])) {
455
                foreach ($annotations[$element]['covers'] as $coversTarget) {
456
                    $groups[] = ['__phpunit_covers_' . self::canonicalizeName($coversTarget)];
457
                }
458
            }
459
 
460
            if (isset($annotations[$element]['uses'])) {
461
                foreach ($annotations[$element]['uses'] as $usesTarget) {
462
                    $groups[] = ['__phpunit_uses_' . self::canonicalizeName($usesTarget)];
463
                }
464
            }
465
        }
466
 
467
        return array_unique(array_merge([], ...$groups));
468
    }
469
 
470
    /** @psalm-param class-string $className */
471
    public static function getSize(string $className, ?string $methodName): int
472
    {
473
        $groups = array_flip(self::getGroups($className, $methodName));
474
 
475
        if (isset($groups['large'])) {
476
            return self::LARGE;
477
        }
478
 
479
        if (isset($groups['medium'])) {
480
            return self::MEDIUM;
481
        }
482
 
483
        if (isset($groups['small'])) {
484
            return self::SMALL;
485
        }
486
 
487
        return self::UNKNOWN;
488
    }
489
 
490
    /** @psalm-param class-string $className */
491
    public static function getProcessIsolationSettings(string $className, string $methodName): bool
492
    {
493
        $annotations = self::parseTestMethodAnnotations(
494
            $className,
495
            $methodName
496
        );
497
 
498
        return isset($annotations['class']['runTestsInSeparateProcesses']) || isset($annotations['method']['runInSeparateProcess']);
499
    }
500
 
501
    /** @psalm-param class-string $className */
502
    public static function getClassProcessIsolationSettings(string $className, string $methodName): bool
503
    {
504
        $annotations = self::parseTestMethodAnnotations(
505
            $className,
506
            $methodName
507
        );
508
 
509
        return isset($annotations['class']['runClassInSeparateProcess']);
510
    }
511
 
512
    /** @psalm-param class-string $className */
513
    public static function getPreserveGlobalStateSettings(string $className, string $methodName): ?bool
514
    {
515
        return self::getBooleanAnnotationSetting(
516
            $className,
517
            $methodName,
518
            'preserveGlobalState'
519
        );
520
    }
521
 
522
    /** @psalm-param class-string $className */
523
    public static function getHookMethods(string $className): array
524
    {
525
        if (!class_exists($className, false)) {
526
            return self::emptyHookMethodsArray();
527
        }
528
 
529
        if (!isset(self::$hookMethods[$className])) {
530
            self::$hookMethods[$className] = self::emptyHookMethodsArray();
531
 
532
            try {
533
                foreach ((new Reflection)->methodsInTestClass(new ReflectionClass($className)) as $method) {
534
                    $docBlock = Registry::getInstance()->forMethod($className, $method->getName());
535
 
536
                    if ($method->isStatic()) {
537
                        if ($docBlock->isHookToBeExecutedBeforeClass()) {
538
                            array_unshift(
539
                                self::$hookMethods[$className]['beforeClass'],
540
                                $method->getName()
541
                            );
542
                        }
543
 
544
                        if ($docBlock->isHookToBeExecutedAfterClass()) {
545
                            self::$hookMethods[$className]['afterClass'][] = $method->getName();
546
                        }
547
                    }
548
 
549
                    if ($docBlock->isToBeExecutedBeforeTest()) {
550
                        array_unshift(
551
                            self::$hookMethods[$className]['before'],
552
                            $method->getName()
553
                        );
554
                    }
555
 
556
                    if ($docBlock->isToBeExecutedAsPreCondition()) {
557
                        array_unshift(
558
                            self::$hookMethods[$className]['preCondition'],
559
                            $method->getName()
560
                        );
561
                    }
562
 
563
                    if ($docBlock->isToBeExecutedAsPostCondition()) {
564
                        self::$hookMethods[$className]['postCondition'][] = $method->getName();
565
                    }
566
 
567
                    if ($docBlock->isToBeExecutedAfterTest()) {
568
                        self::$hookMethods[$className]['after'][] = $method->getName();
569
                    }
570
                }
571
            } catch (ReflectionException $e) {
572
            }
573
        }
574
 
575
        return self::$hookMethods[$className];
576
    }
577
 
578
    public static function isTestMethod(ReflectionMethod $method): bool
579
    {
580
        if (!$method->isPublic()) {
581
            return false;
582
        }
583
 
584
        if (strpos($method->getName(), 'test') === 0) {
585
            return true;
586
        }
587
 
588
        return array_key_exists(
589
            'test',
590
            Registry::getInstance()->forMethod(
591
                $method->getDeclaringClass()->getName(),
592
                $method->getName()
593
            )
594
            ->symbolAnnotations()
595
        );
596
    }
597
 
598
    /**
599
     * @throws CodeCoverageException
600
     *
601
     * @psalm-param class-string $className
602
     */
603
    private static function getLinesToBeCoveredOrUsed(string $className, string $methodName, string $mode): array
604
    {
605
        $annotations = self::parseTestMethodAnnotations(
606
            $className,
607
            $methodName
608
        );
609
 
610
        $classShortcut = null;
611
 
612
        if (!empty($annotations['class'][$mode . 'DefaultClass'])) {
613
            if (count($annotations['class'][$mode . 'DefaultClass']) > 1) {
614
                throw new CodeCoverageException(
615
                    sprintf(
616
                        'More than one @%sClass annotation in class or interface "%s".',
617
                        $mode,
618
                        $className
619
                    )
620
                );
621
            }
622
 
623
            $classShortcut = $annotations['class'][$mode . 'DefaultClass'][0];
624
        }
625
 
626
        $list = $annotations['class'][$mode] ?? [];
627
 
628
        if (isset($annotations['method'][$mode])) {
629
            $list = array_merge($list, $annotations['method'][$mode]);
630
        }
631
 
632
        $codeUnits = CodeUnitCollection::fromArray([]);
633
        $mapper    = new Mapper;
634
 
635
        foreach (array_unique($list) as $element) {
636
            if ($classShortcut && strncmp($element, '::', 2) === 0) {
637
                $element = $classShortcut . $element;
638
            }
639
 
640
            $element = preg_replace('/[\s()]+$/', '', $element);
641
            $element = explode(' ', $element);
642
            $element = $element[0];
643
 
644
            if ($mode === 'covers' && interface_exists($element)) {
645
                throw new InvalidCoversTargetException(
646
                    sprintf(
647
                        'Trying to @cover interface "%s".',
648
                        $element
649
                    )
650
                );
651
            }
652
 
653
            try {
654
                $codeUnits = $codeUnits->mergeWith($mapper->stringToCodeUnits($element));
655
            } catch (InvalidCodeUnitException $e) {
656
                throw new InvalidCoversTargetException(
657
                    sprintf(
658
                        '"@%s %s" is invalid',
659
                        $mode,
660
                        $element
661
                    ),
662
                    $e->getCode(),
663
                    $e
664
                );
665
            }
666
        }
667
 
668
        return $mapper->codeUnitsToSourceLines($codeUnits);
669
    }
670
 
671
    private static function emptyHookMethodsArray(): array
672
    {
673
        return [
674
            'beforeClass'   => ['setUpBeforeClass'],
675
            'before'        => ['setUp'],
676
            'preCondition'  => ['assertPreConditions'],
677
            'postCondition' => ['assertPostConditions'],
678
            'after'         => ['tearDown'],
679
            'afterClass'    => ['tearDownAfterClass'],
680
        ];
681
    }
682
 
683
    /** @psalm-param class-string $className */
684
    private static function getBooleanAnnotationSetting(string $className, ?string $methodName, string $settingName): ?bool
685
    {
686
        $annotations = self::parseTestMethodAnnotations(
687
            $className,
688
            $methodName
689
        );
690
 
691
        if (isset($annotations['method'][$settingName])) {
692
            if ($annotations['method'][$settingName][0] === 'enabled') {
693
                return true;
694
            }
695
 
696
            if ($annotations['method'][$settingName][0] === 'disabled') {
697
                return false;
698
            }
699
        }
700
 
701
        if (isset($annotations['class'][$settingName])) {
702
            if ($annotations['class'][$settingName][0] === 'enabled') {
703
                return true;
704
            }
705
 
706
            if ($annotations['class'][$settingName][0] === 'disabled') {
707
                return false;
708
            }
709
        }
710
 
711
        return null;
712
    }
713
 
714
    /**
715
     * Trims any extensions from version string that follows after
716
     * the <major>.<minor>[.<patch>] format.
717
     */
718
    private static function sanitizeVersionNumber(string $version)
719
    {
720
        return preg_replace(
721
            '/^(\d+\.\d+(?:.\d+)?).*$/',
722
            '$1',
723
            $version
724
        );
725
    }
726
 
727
    private static function shouldCoversAnnotationBeUsed(array $annotations): bool
728
    {
729
        if (isset($annotations['method']['coversNothing'])) {
730
            return false;
731
        }
732
 
733
        if (isset($annotations['method']['covers'])) {
734
            return true;
735
        }
736
 
737
        if (isset($annotations['class']['coversNothing'])) {
738
            return false;
739
        }
740
 
741
        return true;
742
    }
743
 
744
    /**
745
     * Merge two arrays together.
746
     *
747
     * If an integer key exists in both arrays and preserveNumericKeys is false, the value
748
     * from the second array will be appended to the first array. If both values are arrays, they
749
     * are merged together, else the value of the second array overwrites the one of the first array.
750
     *
751
     * This implementation is copied from https://github.com/zendframework/zend-stdlib/blob/76b653c5e99b40eccf5966e3122c90615134ae46/src/ArrayUtils.php
752
     *
753
     * Zend Framework (http://framework.zend.com/)
754
     *
755
     * @see      http://github.com/zendframework/zf2 for the canonical source repository
756
     *
757
     * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
758
     * @license   http://framework.zend.com/license/new-bsd New BSD License
759
     */
760
    private static function mergeArraysRecursively(array $a, array $b): array
761
    {
762
        foreach ($b as $key => $value) {
763
            if (array_key_exists($key, $a)) {
764
                if (is_int($key)) {
765
                    $a[] = $value;
766
                } elseif (is_array($value) && is_array($a[$key])) {
767
                    $a[$key] = self::mergeArraysRecursively($a[$key], $value);
768
                } else {
769
                    $a[$key] = $value;
770
                }
771
            } else {
772
                $a[$key] = $value;
773
            }
774
        }
775
 
776
        return $a;
777
    }
778
 
779
    private static function canonicalizeName(string $name): string
780
    {
781
        return strtolower(trim($name, '\\'));
782
    }
783
}