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

AbstractClassifier::find()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 3
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 7
ccs 4
cts 4
cp 1
crap 4
rs 10
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
abstract class AbstractClassifier implements ClassifierInterface
12
{
13
    /**
14
     * @psalm-var array<class-string, ReflectionClass>
15
     */
16
    private static array $reflectionsCache = [];
17
18
    /**
19
     * @var string[]
20
     */
21
    private array $interfaces = [];
22
    /**
23
     * @var string[]
24
     */
25
    private array $attributes = [];
26
    /**
27
     * @psalm-var class-string
28
     */
29
    private ?string $parentClass = null;
30
    /**
31
     * @var string[]
32
     */
33
    private array $directories;
34
35 26
    public function __construct(string $directory, string ...$directories)
36
    {
37 26
        $this->directories = [$directory, ...array_values($directories)];
0 ignored issues
show
Documentation Bug introduced by
array($directory, array_values($directories)) is of type array<integer,array|string>, but the property $directories was declared to be of type string[]. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
38 26
        $isWindows = DIRECTORY_SEPARATOR === '\\';
39
40 26
        if ($isWindows) {
41
            $this->directories = str_replace('/', '\\', $this->directories);
42
        }
43
    }
44
45
    /**
46
     * @psalm-param class-string ...$interfaces
47
     */
48 20
    public function withInterface(string ...$interfaces): self
49
    {
50 20
        $new = clone $this;
51 20
        array_push($new->interfaces, ...array_values($interfaces));
52
53 20
        return $new;
54
    }
55
56
    /**
57
     * @psalm-param class-string $parentClass
58
     */
59 2
    public function withParentClass(string $parentClass): self
60
    {
61 2
        $new = clone $this;
62 2
        $new->parentClass = $parentClass;
63 2
        return $new;
64
    }
65
66
    /**
67
     * @psalm-param class-string ...$attributes
68
     */
69 8
    public function withAttribute(string ...$attributes): self
70
    {
71 8
        $new = clone $this;
72 8
        array_push($new->attributes, ...array_values($attributes));
73
74 8
        return $new;
75
    }
76
77
    /**
78
     * @psalm-return iterable<class-string>
79
     */
80 26
    public function find(): iterable
81
    {
82 26
        if (count($this->interfaces) === 0 && count($this->attributes) === 0 && $this->parentClass === null) {
83 6
            return [];
84
        }
85
86 20
        yield from $this->getAvailableClasses();
87
    }
88
89 20
    protected function getFiles(): Finder
90
    {
91 20
        return (new Finder())
92 20
            ->in($this->directories)
93 20
            ->name('*.php')
94 20
            ->sortByName()
95 20
            ->files();
96
    }
97
98
    /**
99
     * @psalm-param class-string $className
100
     */
101 20
    protected function skipClass(string $className): bool
102
    {
103 20
        $reflectionClass = self::$reflectionsCache[$className] ??= new ReflectionClass($className);
104
105 20
        if ($reflectionClass->isInternal()) {
106 10
            return true;
107
        }
108 20
        $countInterfaces = count($this->interfaces);
109 20
        $countAttributes = count($this->attributes);
110 20
        $directories = $this->directories;
111
112 20
        $matchedDirs = array_filter(
113 20
            $directories,
114 20
            static fn($directory) => str_starts_with($reflectionClass->getFileName(), $directory)
115 20
        );
116
117 20
        if (count($matchedDirs) === 0) {
118 10
            return true;
119
        }
120
121 20
        if ($countInterfaces > 0) {
122 16
            $interfaces = $reflectionClass->getInterfaces();
123 16
            $interfaces = array_map(static fn(ReflectionClass $class) => $class->getName(), $interfaces);
124
125 16
            if (count(array_intersect($this->interfaces, $interfaces)) !== $countInterfaces) {
126 10
                return true;
127
            }
128
        }
129
130 20
        if ($countAttributes > 0) {
131 4
            $attributes = $reflectionClass->getAttributes();
132 4
            $attributes = array_map(
133 4
                static fn(ReflectionAttribute $attribute) => $attribute->getName(),
134 4
                $attributes
135 4
            );
136
137 4
            if (count(array_intersect($this->attributes, $attributes)) !== $countAttributes) {
138 4
                return true;
139
            }
140
        }
141
142 20
        return ($this->parentClass !== null) && !is_subclass_of($reflectionClass->getName(), $this->parentClass);
143
    }
144
145
    /**
146
     * @return iterable<class-string>
147
     */
148
    abstract protected function getAvailableClasses(): iterable;
149
}
150