Passed
Pull Request — master (#38)
by Rustam
12:17
created

AbstractClassifier::withAttribute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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