Passed
Pull Request — master (#38)
by Rustam
02:33
created

NativeClassifier   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 84
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 36
dl 0
loc 84
ccs 36
cts 36
cp 1
rs 10
c 0
b 0
f 0
wmc 15

2 Methods

Rating   Name   Duplication   Size   Complexity  
A getAvailableClasses() 0 18 5
B skipClass() 0 50 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Classifier;
6
7
use ReflectionAttribute;
8
use ReflectionClass;
9
10
/**
11
 * `NativeClassifier` is a classifier that finds classes using PHP's native function {@see get_declared_classes()}.
12
 */
13
final class NativeClassifier extends AbstractClassifier
14
{
15
    /**
16
     * @psalm-var array<class-string, ReflectionClass>
17
     */
18
    private static array $reflectionsCache = [];
19
20
    /**
21
     * @psalm-suppress UnresolvableInclude
22
     */
23 10
    protected function getAvailableClasses(): iterable
24
    {
25 10
        $files = $this->getFiles();
26
27 10
        foreach ($files as $file) {
28
            try {
29 10
                require_once $file;
30 1
            } catch (\Throwable) {
31
                // Ignore syntax errors
32
            }
33
        }
34
35 10
        foreach (get_declared_classes() as $className) {
36 10
            if ($this->skipClass($className)) {
37 10
                continue;
38
            }
39
40 10
            yield $className;
41
        }
42
    }
43
44
    /**
45
     * @psalm-param class-string $className
46
     */
47 10
    private function skipClass(string $className): bool
48
    {
49 10
        $reflectionClass = self::$reflectionsCache[$className] ??= new ReflectionClass($className);
50
51 10
        if ($reflectionClass->isInternal() || $reflectionClass->isAnonymous()) {
52 10
            return true;
53
        }
54 10
        $directories = $this->directories;
55 10
        $isWindows = DIRECTORY_SEPARATOR === '\\';
56
57 10
        if ($isWindows) {
58
            /**
59
             * @psalm-var string[] $directories
60
             */
61
            // @codeCoverageIgnoreStart
62
            $directories = str_replace('/', '\\', $directories);
63
            // @codeCoverageIgnoreEnd
64
        }
65
66 10
        $matchedDirs = array_filter(
67 10
            $directories,
68 10
            static fn($directory) => str_starts_with($reflectionClass->getFileName(), $directory)
69 10
        );
70
71 10
        if (count($matchedDirs) === 0) {
72 10
            return true;
73
        }
74
75 10
        if (!empty($this->interfaces)) {
76 8
            $interfaces = $reflectionClass->getInterfaces();
77 8
            $interfaces = array_map(static fn(ReflectionClass $class) => $class->getName(), $interfaces);
78
79 8
            if (count(array_intersect($this->interfaces, $interfaces)) !== count($this->interfaces)) {
80 5
                return true;
81
            }
82
        }
83
84 10
        if (!empty($this->attributes)) {
85 2
            $attributes = $reflectionClass->getAttributes();
86 2
            $attributes = array_map(
87 2
                static fn(ReflectionAttribute $attribute) => $attribute->getName(),
88 2
                $attributes
89 2
            );
90
91 2
            if (count(array_intersect($this->attributes, $attributes)) !== count($this->attributes)) {
92 2
                return true;
93
            }
94
        }
95
96 10
        return ($this->parentClass !== null) && !is_subclass_of($reflectionClass->getName(), $this->parentClass);
97
    }
98
}
99