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

Classifier   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 130
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 6
Bugs 1 Features 0
Metric Value
eloc 52
dl 0
loc 130
ccs 55
cts 55
cp 1
rs 10
c 6
b 1
f 0
wmc 19

6 Methods

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