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

Classifier::withParent()   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 = str_replace('/', '\\', $directories);
87
        }
88
89 9
        foreach ($classesToFind as $className) {
90 9
            $reflection = new ReflectionClass($className);
91
92 9
            if (!$reflection->isUserDefined()) {
93 9
                continue;
94
            }
95
96 9
            $matchedDirs = array_filter(
97 9
                $directories,
98 9
                static fn($directory) => str_starts_with($reflection->getFileName(), $directory)
99 9
            );
100
101 9
            if (count($matchedDirs) === 0) {
102 9
                continue;
103
            }
104
105 9
            if ($countInterfaces > 0) {
106 7
                $interfaces = $reflection->getInterfaces();
107 7
                $interfaces = array_map(static fn(ReflectionClass $class) => $class->getName(), $interfaces);
108
109 7
                if (count(array_intersect($this->interfaces, $interfaces)) !== $countInterfaces) {
110 5
                    continue;
111
                }
112
            }
113
114 9
            if ($countAttributes > 0) {
115 2
                $attributes = $reflection->getAttributes();
116 2
                $attributes = array_map(
117 2
                    static fn(ReflectionAttribute $attribute) => $attribute->getName(),
118 2
                    $attributes
119 2
                );
120
121 2
                if (count(array_intersect($this->attributes, $attributes)) !== $countAttributes) {
122 2
                    continue;
123
                }
124
            }
125
126 9
            if (($this->parentClass !== null) && !is_subclass_of($className, $this->parentClass)) {
127 1
                continue;
128
            }
129
130 9
            yield $className;
131
        }
132
    }
133
134
    /**
135
     * @psalm-suppress UnresolvableInclude
136
     */
137 9
    private function scanFiles(): void
138
    {
139 9
        $files = (new Finder())
140 9
            ->in($this->directories)
141 9
            ->name('*.php')
142 9
            ->sortByName()
143 9
            ->files();
144
145 9
        foreach ($files as $file) {
146 9
            require_once $file;
147
        }
148
    }
149
}
150