Test Failed
Pull Request — master (#38)
by Sergei
08:01 queued 05:42
created

AbstractClassifier::withInterface()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

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
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
    public function __construct(string $directory, string ...$directories)
36
    {
37
        $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
    public function withInterface(string ...$interfaces): self
44
    {
45
        $new = clone $this;
46
        array_push($new->interfaces, ...array_values($interfaces));
47
48
        return $new;
49
    }
50
51
    /**
52
     * @psalm-param class-string $parentClass
53
     */
54
    public function withParentClass(string $parentClass): self
55
    {
56
        $new = clone $this;
57
        $new->parentClass = $parentClass;
58
        return $new;
59
    }
60
61
    /**
62
     * @psalm-param class-string ...$attributes
63
     */
64
    public function withAttribute(string ...$attributes): self
65
    {
66
        $new = clone $this;
67
        array_push($new->attributes, ...array_values($attributes));
68
69
        return $new;
70
    }
71
72
    /**
73
     * @psalm-return iterable<class-string>
74
     */
75
    public function find(): iterable
76
    {
77
        if (count($this->interfaces) === 0 && count($this->attributes) === 0 && $this->parentClass === null) {
78
            return [];
79
        }
80
81
        yield from $this->getAvailableClasses();
82
    }
83
84
    protected function getFiles(): Finder
85
    {
86
        return (new Finder())
87
            ->in($this->directories)
88
            ->name('*.php')
89
            ->sortByName()
90
            ->files();
91
    }
92
93
    /**
94
     * @psalm-param class-string $className
95
     */
96
    protected function skipClass(string $className): bool
97
    {
98
        $reflectionClass = self::$reflectionsCache[$className] ??= new ReflectionClass($className);
99
100
        if ($reflectionClass->isInternal()) {
101
            return true;
102
        }
103
        $countInterfaces = count($this->interfaces);
104
        $countAttributes = count($this->attributes);
105
        $directories = $this->directories;
106
107
        $matchedDirs = array_filter(
108
            $directories,
109
            static fn($directory) => str_starts_with($reflectionClass->getFileName(), $directory)
110
        );
111
112
        if (count($matchedDirs) === 0) {
113
            return true;
114
        }
115
116
        if ($countInterfaces > 0) {
117
            $interfaces = $reflectionClass->getInterfaces();
118
            $interfaces = array_map(static fn(ReflectionClass $class) => $class->getName(), $interfaces);
119
120
            if (count(array_intersect($this->interfaces, $interfaces)) !== $countInterfaces) {
121
                return true;
122
            }
123
        }
124
125
        if ($countAttributes > 0) {
126
            $attributes = $reflectionClass->getAttributes();
127
            $attributes = array_map(
128
                static fn(ReflectionAttribute $attribute) => $attribute->getName(),
129
                $attributes
130
            );
131
132
            if (count(array_intersect($this->attributes, $attributes)) !== $countAttributes) {
133
                return true;
134
            }
135
        }
136
137
        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