Passed
Pull Request — master (#34)
by Rustam
02:12
created

Classifier::withParentClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Classifier;
6
7
use ReflectionAttribute;
8
use ReflectionClass;
9
use Symfony\Component\Finder\Finder;
10
11
final class Classifier
12
{
13
    /**
14
     * @var string[]
15
     */
16
    private array $interfaces = [];
17
    /**
18
     * @var string[]
19
     */
20
    private array $attributes = [];
21
    /**
22
     * @var class-string
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
23
     */
24
    private ?string $parentClass = null;
25
    /**
26
     * @var string[]
27
     */
28
    private array $directories;
29
30 12
    public function __construct(string ...$directory)
31
    {
32 12
        $this->directories = $directory;
33
    }
34
35
    /**
36
     * @psalm-param class-string ...$interfaces
37
     */
38 9
    public function withInterface(string ...$interfaces): self
39
    {
40 9
        $new = clone $this;
41 9
        array_push($new->interfaces, ...array_values($interfaces));
42
43 9
        return $new;
44
    }
45
46
    /**
47
     * @psalm-param class-string $parentClass
48
     */
49 1
    public function withParentClass(string $parentClass): self
50
    {
51 1
        $new = clone $this;
52 1
        $new->parentClass = $parentClass;
53 1
        return $new;
54
    }
55
56
    /**
57
     * @psalm-param class-string ...$attributes
58
     */
59 4
    public function withAttribute(string ...$attributes): self
60
    {
61 4
        $new = clone $this;
62 4
        array_push($new->attributes, ...array_values($attributes));
63
64 4
        return $new;
65
    }
66
67
    /**
68
     * @return iterable<class-string>
69
     */
70 12
    public function find(): iterable
71
    {
72 12
        $countInterfaces = count($this->interfaces);
73 12
        $countAttributes = count($this->attributes);
74
75 12
        if ($countInterfaces === 0 && $countAttributes === 0 && $this->parentClass === null) {
76 3
            return [];
77
        }
78
79 9
        $this->scanFiles();
80
81 9
        $classesToFind = get_declared_classes();
82 9
        $isWindows = DIRECTORY_SEPARATOR === '\\';
83 9
        $directories = $this->directories;
84
85 9
        if ($isWindows) {
86
            $directories = array_map(
87
                static fn($directory) => str_replace('/', '\\', $directory),
88
                $this->directories
89
            );
90
        }
91
92 9
        foreach ($classesToFind as $className) {
93 9
            $reflection = new ReflectionClass($className);
94
95 9
            if (!$reflection->isUserDefined()) {
96 9
                continue;
97
            }
98
99 9
            $matchedDirs = array_filter(
100 9
                $directories,
101 9
                static fn($directory) => str_starts_with($reflection->getFileName(), $directory)
102 9
            );
103
104 9
            if (count($matchedDirs) === 0) {
105 9
                continue;
106
            }
107
108 9
            if ($countInterfaces > 0) {
109 7
                $interfaces = $reflection->getInterfaces();
110 7
                $interfaces = array_map(static fn(ReflectionClass $class) => $class->getName(), $interfaces);
111
112 7
                if (count(array_intersect($this->interfaces, $interfaces)) !== $countInterfaces) {
113 5
                    continue;
114
                }
115
            }
116
117 9
            if ($countAttributes > 0) {
118 2
                $attributes = $reflection->getAttributes();
119 2
                $attributes = array_map(
120 2
                    static fn(ReflectionAttribute $attribute) => $attribute->getName(),
121 2
                    $attributes
122 2
                );
123
124 2
                if (count(array_intersect($this->attributes, $attributes)) !== $countAttributes) {
125 2
                    continue;
126
                }
127
            }
128
129 9
            if (($this->parentClass !== null) && !is_subclass_of($className, $this->parentClass)) {
130 1
                continue;
131
            }
132
133 9
            yield $className;
134
        }
135
    }
136
137
    /**
138
     * @psalm-suppress UnresolvableInclude
139
     */
140 9
    private function scanFiles(): void
141
    {
142 9
        $files = (new Finder())
143 9
            ->in($this->directories)
144 9
            ->name('*.php')
145 9
            ->sortByName()
146 9
            ->files();
147
148 9
        foreach ($files as $file) {
149 9
            require_once $file;
150
        }
151
    }
152
}
153