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

Classifier   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 134
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 7
Bugs 1 Features 0
Metric Value
eloc 55
c 7
b 1
f 0
dl 0
loc 134
ccs 59
cts 59
cp 1
rs 10
wmc 20

6 Methods

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