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
Branch master (c9bdeb)
by SignpostMarv
01:40
created

StaticMethodCollector::Collect()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 8
ccs 5
cts 5
cp 1
crap 2
rs 10
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 InvalidArgumentException;
13
use ReflectionClass;
14
use ReflectionMethod;
15
use ReflectionNamedType;
16
use ReflectionType;
17
use Traversable;
18
19
class StaticMethodCollector
20
{
21
    const DEFAULT_INT_ARRAY_FILTER_FLAG = 0;
22
23
    const DEFAULT_BOOL_AUTORESET = true;
24
25
    const INT_FILTER_NON_EMPTY_ARRAY = 0;
26
27
    const EXPECTED_NUMBER_OF_REQUIRED_PARAMETERS = 0;
28
29
    /**
30
    * @var array<int, class-string>
31
    */
32
    protected $processedSources = [];
33
34
    /**
35
    * @var array<int, class-string>
36
    */
37
    protected $alreadyYielded = [];
38
39
    /**
40
    * @var bool
41
    */
42
    protected $autoReset;
43
44
    /**
45
    * @var array<class-string, array<string, array<int, class-string>>>
46
    */
47
    private $staticMethods = [];
48
49
    /**
50
    * @var array<int, class-string>
51
    */
52
    private $interfaces = [];
53
54
    /**
55
    * @param array<class-string, array<string, array<int, class-string>>> $staticMethods
56
    * @param array<int, class-string> $interfaces
57
    */
58 4
    public function __construct(
59
        array $staticMethods,
60
        array $interfaces,
61
        bool $autoReset = self::DEFAULT_BOOL_AUTORESET
62
    ) {
63 4
        $filteredMethods = [];
64
65 4
        foreach ($this->FilterArrayOfInterfaceOffsets($staticMethods) as $interface => $methods) {
66 4
            $filteredMethods[$interface] = $this->FilterMethods($interface, $methods);
67
        }
68
69 4
        $this->staticMethods = $this->FilterNonZeroArray($filteredMethods);
70
71 4
        $this->interfaces = $this->FilterArrayOfInterfacesOrClasses($interfaces);
72
73 4
        $this->autoReset = $autoReset;
74 4
    }
75
76
    /**
77
    * @param class-string ...$implementations
78
    */
79 2
    public function Collect(string ...$implementations) : Generator
80
    {
81 2
        if ($this->autoReset) {
82 1
            $this->processedSources = [];
83 1
            $this->alreadyYielded = [];
84
        }
85
86 2
        yield from $this->CollectInterfaces(...$implementations);
87 2
    }
88
89
    /**
90
    * @param class-string ...$implementations
91
    */
92 4
    protected function CollectInterfaces(string ...$implementations) : Generator
93
    {
94
        foreach (
95 4
            array_filter(
96 4
                $implementations,
97
                /**
98
                * @param class-string $implementation
99
                */
100
                function (string $implementation) : bool {
101
                    return
102 4
                        ! static::IsStringInArray($implementation, $this->processedSources);
103 4
                }
104
            ) as $implementation
105
        ) {
106 4
            $this->processedSources[] = $implementation;
107 4
            yield from $this->CollectInterfacesFromImplementationCheckInterfaces($implementation);
108 4
            yield from $this->CollectInterfacesFromImplementation($implementation);
109
        }
110 4
    }
111
112
    /**
113
    * @template T as object
114
    *
115
    * @param class-string<T> $implementation
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<T> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<T>.
Loading history...
116
    *
117
    * @return Generator<int, class-string<T>, mixed, void>
118
    */
119 4
    final protected function CollectInterfacesFromImplementationCheckInterfaces(
120
        string $implementation
121
    ) : Generator {
122 4
        $checking = array_filter(
123 4
            $this->interfaces,
124
            /**
125
            * @param class-string $interface
126
            */
127
            function (string $interface) use ($implementation) : bool {
128 4
                return static::IsStringA($implementation, $interface);
129 4
            }
130
        );
131
132
        if (
133 4
            count($checking) > self::INT_FILTER_NON_EMPTY_ARRAY &&
134 4
            ! static::IsStringInArray($implementation, $this->alreadyYielded)
135
        ) {
136
            yield $implementation;
137
            $this->alreadyYielded[] = $implementation;
138
        }
139 4
    }
140
141
    /**
142
    * @param class-string $implementation
143
    */
144 4
    final protected function CollectInterfacesFromImplementation(string $implementation) : Generator
145
    {
146 4
        $interfaces = array_keys($this->staticMethods);
147
148 4
        foreach ($this->FilterIsA($implementation, $interfaces) as $interface) {
149 4
            foreach ($this->staticMethods[$interface] as $method => $types) {
150 4
                yield from $this->CollectInterfacesFromImplementationTypes(
151 4
                    $implementation,
152
                    $method,
153
                    $types
154
                );
155
            }
156
        }
157 4
    }
158
159
    /**
160
    * @param class-string $implementation
161
    * @param array<int, class-string> $types
162
    */
163 4
    final protected function CollectInterfacesFromImplementationTypes(
164
        string $implementation,
165
        string $method,
166
        array $types
167
    ) : Generator {
168 4
        if ( ! method_exists($implementation, $method)) {
169
            throw new InvalidArgumentException(
170
                'Argument 2 passed to ' .
171
                __METHOD__ .
172
                ' is not a method on Argument 1!'
173
            );
174
        }
175
176
        /**
177
        * @var iterable<class-string>
178
        */
179 4
        $methodResult = $implementation::$method();
180
181 4
        foreach ($methodResult as $result) {
182 4
            if (static::IsStringInArray($result, $this->alreadyYielded)) {
183 1
                continue;
184
            }
185
186 4
            foreach ($this->FilterIsA($result, $types) as $type) {
187 4
                yield $result;
188 4
                $this->alreadyYielded[] = $result;
189
            }
190
191 4
            yield from $this->CollectInterfaces($result);
192
        }
193 4
    }
194
195
    /**
196
    * @template T
197
    *
198
    * @param T::class $implementation
199
    * @param array<int, class-string> $interfaces
200
    *
201
    * @return array<int, T::class>
202
    */
203 4
    final protected function FilterIsA(string $implementation, array $interfaces) : array
204
    {
205 4
        return array_filter(
206 4
            $interfaces,
207
            /**
208
            * @param class-string $interface
209
            */
210
            function (string $interface) use ($implementation) : bool {
211 4
                return static::IsStringA($implementation, $interface);
212 4
            }
213
        );
214
    }
215
216
    /**
217
    * @param array<int, class-string> $interfaces
218
    *
219
    * @return array<int, class-string>
220
    */
221 4
    final protected function FilterArrayOfInterfacesOrClasses(array $interfaces) : array
222
    {
223
        /**
224
        * @var array<int, class-string>
225
        */
226 4
        $strings = array_filter(
227 4
            $interfaces,
228
            function (string $maybe) : bool {
229 4
                return interface_exists($maybe) || class_exists($maybe);
230 4
            }
231
        );
232
233 4
        return $strings;
234
    }
235
236
    /**
237
    * @param array<class-string, array<string, array<int, class-string>>> $interfaces
238
    *
239
    * @return array<class-string, array<string, array<int, class-string>>>
240
    */
241 4
    final protected function FilterArrayOfInterfaceOffsets(array $interfaces) : array
242
    {
243 4
        return array_filter(
244 4
            $interfaces,
245
            function (string $maybe) : bool {
246 4
                return interface_exists($maybe);
247 4
            },
248 4
            ARRAY_FILTER_USE_KEY
249
        );
250
    }
251
252
    /**
253
    * @param class-string $interface
254
    */
255 4
    final protected function MakeMethodFilter(string $interface) : Closure
256
    {
257
        return function (string $maybe) use ($interface) : bool {
258 4
            $ref = new ReflectionClass($interface);
259
260
            return
261 4
                $ref->hasMethod($maybe) &&
262 4
                $this->FilterReflectionMethod($ref->getMethod($maybe));
263 4
        };
264
    }
265
266 4
    final protected function FilterReflectionMethod(ReflectionMethod $refMethod) : bool
267
    {
268
        return
269 4
            $refMethod->isStatic() &&
270 4
            $refMethod->isPublic() &&
271 4
            self::EXPECTED_NUMBER_OF_REQUIRED_PARAMETERS === $refMethod->getNumberOfRequiredParameters() &&
272 4
            $this->FilterReflectionReturnType($refMethod->getReturnType());
273
    }
274
275 4
    final protected function FilterReflectionReturnType(? ReflectionType $refReturn) : bool
276
    {
277
        /**
278
        * @var string|class-string
279
        */
280 4
        $refReturnName = ($refReturn instanceof ReflectionNamedType) ? $refReturn->getName() : '';
281
282 4
        return 'array' === $refReturnName || static::IsStringA($refReturnName, Traversable::class);
283
    }
284
285
    /**
286
    * @param class-string $interface
287
    * @param array<string, array<int, class-string>> $methods
288
    *
289
    * @return array<class-string, string[]>
290
    */
291 4
    final protected function FilterMethods(string $interface, array $methods) : array
292
    {
293
        /**
294
        * @var array<class-string, string[]>
295
        */
296 4
        $filteredMethods = $this->FilterNonZeroArray(array_map(
297 4
            [$this, 'FilterArrayOfInterfacesOrClasses'],
298 4
            array_filter(
299 4
                array_filter($methods, 'is_string', ARRAY_FILTER_USE_KEY),
300 4
                $this->MakeMethodFilter($interface),
301 4
                ARRAY_FILTER_USE_KEY
302
            )
303
        ));
304
305 4
        return $filteredMethods;
306
    }
307
308
    /**
309
    * @return array<class-string, array<string, array<int, class-string>>>
310
    */
311 4
    final protected function FilterNonZeroArray(array $in) : array
312
    {
313
        /**
314
        * @var array<class-string, array<string, array<int, class-string>>>
315
        */
316 4
        $out = array_filter(
317 4
            $in,
318
            function (array $val) : bool {
319 4
                return count($val) > self::INT_FILTER_NON_EMPTY_ARRAY;
320 4
            }
321
        );
322
323 4
        return $out;
324
    }
325
326
    /**
327
    * @param class-string $maybe
328
    * @param class-string[] $array
329
    */
330 4
    protected static function IsStringInArray(string $maybe, array $array) : bool
331
    {
332 4
        return in_array($maybe, $array, true);
333
    }
334
335
    /**
336
    * @param class-string $thing
337
    */
338 4
    protected static function IsStringA(string $maybe, string $thing) : bool
339
    {
340 4
        return is_a($maybe, $thing, true);
341
    }
342
}
343