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.

StaticMethodCollector::CollectInterfaces()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

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