GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( ddb6e6...937d02 )
by SignpostMarv
03:09
created

StaticMethodCollector::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 23
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 3
dl 0
loc 23
ccs 0
cts 11
cp 0
crap 6
rs 9.0856
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
/**
5
* @author SignpostMarv
6
*/
7
8
namespace SignpostMarv\DaftInterfaceCollector;
9
10
use Closure;
11
use Generator;
12
use ReflectionClass;
13
use ReflectionMethod;
14
use ReflectionNamedType;
15
use ReflectionType;
16
use Traversable;
17
18
class StaticMethodCollector
19
{
20
    /**
21
    * @var array<string, array<string, string[]>>
22
    */
23
    private $staticMethods = [];
24
25
    /**
26
    * @var string[]
27
    */
28
    private $interfaces = [];
29
30
    /**
31
    * @var array<int, string>
32
    */
33
    private $processedSources = [];
34
35
    /**
36
    * @var string[]
37
    */
38
    private $alreadyYielded = [];
39
40
    /**
41
    * @var bool
42
    */
43
    private $autoReset;
44
45
    public function __construct(array $staticMethods, array $interfaces, bool $autoReset = true)
46
    {
47
        $filteredMethods = [];
48
49
        foreach ($this->FilterArrayOfInterfaceOffsets($staticMethods) as $interface => $methods) {
50
            $filteredMethods[$interface] = $this->FilterMethods($interface, $methods);
51
        }
52
53
        /**
54
        * @var array<string, array<string, string[]>> $filteredMethods
55
        */
56
        $filteredMethods = $this->FilterNonZeroArray($filteredMethods);
57
58
        $this->staticMethods = $filteredMethods;
59
60
        /**
61
        * @var string[] $filteredInterfaces
62
        */
63
        $filteredInterfaces = $this->FilterArrayOfInterfacesOrClasses($interfaces);
64
65
        $this->interfaces = $filteredInterfaces;
66
67
        $this->autoReset = $autoReset;
68
    }
69
70
    public function Collect(string ...$implementations) : Generator
71
    {
72
        if ($this->autoReset) {
73
            $this->processedSources = [];
74
            $this->alreadyYielded = [];
75
        }
76
77
        yield from $this->CollectInterfaces(...$implementations);
78
    }
79
80
    protected function CollectInterfaces(string ...$implementations) : Generator
81
    {
82
        foreach (
83
            array_filter(
84
                $implementations,
85
                function (string $implementation) : bool {
86
                    return
87
                        class_exists($implementation) &&
88
                        ! in_array($implementation, $this->processedSources, true);
89
                }
90
            ) as $implementation
91
        ) {
92
            $this->processedSources[] = $implementation;
93
94
            foreach ($this->interfaces as $interface) {
95
                if (
96
                    ! in_array($implementation, $this->alreadyYielded, true) &&
97
                    is_a($implementation, $interface, true)
98
                ) {
99
                    yield $implementation;
100
                    $this->alreadyYielded[] = $implementation;
101
                    break;
102
                }
103
            }
104
105
            yield from $this->CollectInterfacesFromImplementation($implementation);
106
        }
107
    }
108
109
    private function CollectInterfacesFromImplementation(string $implementation) : Generator
110
    {
111
        $interfaces = array_keys($this->staticMethods);
112
        /**
113
        * @var string $interface
114
        */
115
        foreach ($this->FilterIsA($implementation, $interfaces) as $interface) {
116
            foreach ($this->staticMethods[$interface] as $method => $types) {
117
                /**
118
                * @var iterable<string> $methodResult
119
                */
120
                $methodResult = $implementation::$method();
121
122
                foreach ($methodResult as $result) {
123
                    if (in_array($result, $this->alreadyYielded, true)) {
124
                        continue;
125
                    }
126
                    /**
127
                    * @var string $type
128
                    */
129
                    foreach ($this->FilterIsA($result, $types) as $type) {
130
                        yield $result;
131
                        $this->alreadyYielded[] = $result;
132
                    }
133
134
                    yield from $this->CollectInterfaces($result);
135
                }
136
            }
137
        }
138
    }
139
140
    private function FilterIsA(string $implementation, array $interfaces) : array
141
    {
142
        return array_filter($interfaces, function (string $interface) use ($implementation) : bool {
143
            return is_a($implementation, $interface, true);
144
        });
145
    }
146
147
    /**
148
    * @return string[]|array<string, mixed>
149
    */
150
    private function FilterArrayOfInterfaces(array $interfaces, int $flag = 0) : array
151
    {
152
        $strings = array_filter($interfaces, 'is_string', $flag);
153
154
        return array_filter($strings, 'interface_exists', $flag);
155
    }
156
157
    /**
158
    * @return string[]
159
    */
160
    private function FilterArrayOfInterfacesOrClasses(array $interfaces) : array
161
    {
162
        /**
163
        * @var string[] $strings
164
        */
165
        $strings = array_filter($interfaces, 'is_string');
166
167
        return array_filter($strings, function (string $maybe) : bool {
168
            return interface_exists($maybe) || class_exists($maybe);
169
        });
170
    }
171
172
    /**
173
    * @return array<string, array>
174
    */
175
    private function FilterArrayOfInterfaceOffsets(array $interfaces) : array
176
    {
177
        /**
178
        * @var array<string, array> $strings
179
        */
180
        $strings = $this->FilterArrayOfInterfaces($interfaces, ARRAY_FILTER_USE_KEY);
181
182
        return array_filter($strings, 'is_array');
183
    }
184
185
    private function MakeMethodFilter(string $interface) : Closure
186
    {
187
        return function (string $maybe) use ($interface) : bool {
188
            $ref = new ReflectionClass($interface);
189
190
            return
191
                $ref->hasMethod($maybe) &&
192
                $this->FilterReflectionMethod($ref->getMethod($maybe));
193
        };
194
    }
195
196
    private function FilterReflectionMethod(ReflectionMethod $refMethod) : bool
197
    {
198
        return
199
            $refMethod->isStatic() &&
200
            $refMethod->isPublic() &&
201
            0 === $refMethod->getNumberOfRequiredParameters() &&
202
            $this->FilterReflectionReturnType($refMethod->getReturnType());
203
    }
204
205
    private function FilterReflectionReturnType(? ReflectionType $refReturn) : bool
206
    {
207
        $refReturnName = ($refReturn instanceof ReflectionNamedType) ? $refReturn->getName() : '';
208
209
        return 'array' === $refReturnName || is_a($refReturnName, Traversable::class, true);
210
    }
211
212
    /**
213
    * @return array<string, string[]>
214
    */
215
    private function FilterMethods(string $interface, array $methods) : array
216
    {
217
        /**
218
        * @var array<string, string[]>
219
        */
220
        $filteredMethods = $this->FilterNonZeroArray(array_map(
221
            [$this, 'FilterArrayOfInterfacesOrClasses'],
222
            array_filter(
223
                array_filter($methods, 'is_string', ARRAY_FILTER_USE_KEY),
224
                $this->MakeMethodFilter($interface),
225
                ARRAY_FILTER_USE_KEY
226
            )
227
        ));
228
229
        return $filteredMethods;
230
    }
231
232
    /**
233
    * @var array[]
234
    */
235
    private function FilterNonZeroArray(array $in) : array
236
    {
237
        return array_filter(
238
            $in,
239
            function (array $val) : bool {
240
                return count($val) > 0;
241
            }
242
        );
243
    }
244
}
245