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 ReflectionType; |
15
|
|
|
use Traversable; |
16
|
|
|
|
17
|
|
|
class StaticMethodCollector |
18
|
|
|
{ |
19
|
|
|
const DEFAULT_INT_ARRAY_FILTER_FLAG = 0; |
20
|
|
|
|
21
|
|
|
const DEFAULT_BOOL_AUTORESET = true; |
22
|
|
|
|
23
|
|
|
const INT_FILTER_NON_EMPTY_ARRAY = 0; |
24
|
|
|
|
25
|
|
|
const EXPECTED_NUMBER_OF_REQUIRED_PARAMETERS = 0; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* @var array<int, string> |
29
|
|
|
*/ |
30
|
|
|
protected $processedSources = []; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @var string[] |
34
|
|
|
*/ |
35
|
|
|
protected $alreadyYielded = []; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* @var bool |
39
|
|
|
*/ |
40
|
|
|
protected $autoReset; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @var array<string, array<string, array<int, string>>> |
44
|
|
|
*/ |
45
|
|
|
private $staticMethods = []; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* @var string[] |
49
|
|
|
*/ |
50
|
|
|
private $interfaces = []; |
51
|
|
|
|
52
|
4 |
|
public function __construct( |
53
|
|
|
array $staticMethods, |
54
|
|
|
array $interfaces, |
55
|
|
|
bool $autoReset = self::DEFAULT_BOOL_AUTORESET |
56
|
|
|
) { |
57
|
4 |
|
$filteredMethods = []; |
58
|
|
|
|
59
|
4 |
|
foreach ($this->FilterArrayOfInterfaceOffsets($staticMethods) as $interface => $methods) { |
60
|
4 |
|
$filteredMethods[$interface] = $this->FilterMethods($interface, $methods); |
61
|
|
|
} |
62
|
|
|
|
63
|
4 |
|
$this->staticMethods = $this->FilterNonZeroArray($filteredMethods); |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* @var string[] |
67
|
|
|
*/ |
68
|
4 |
|
$filteredInterfaces = $this->FilterArrayOfInterfacesOrClasses($interfaces); |
69
|
|
|
|
70
|
4 |
|
$this->interfaces = $filteredInterfaces; |
71
|
|
|
|
72
|
4 |
|
$this->autoReset = $autoReset; |
73
|
4 |
|
} |
74
|
|
|
|
75
|
2 |
|
public function Collect(string ...$implementations) : Generator |
76
|
|
|
{ |
77
|
2 |
|
if ($this->autoReset) { |
78
|
1 |
|
$this->processedSources = []; |
79
|
1 |
|
$this->alreadyYielded = []; |
80
|
|
|
} |
81
|
|
|
|
82
|
2 |
|
yield from $this->CollectInterfaces(...$implementations); |
83
|
2 |
|
} |
84
|
|
|
|
85
|
4 |
|
protected function CollectInterfaces(string ...$implementations) : Generator |
86
|
|
|
{ |
87
|
|
|
foreach ( |
88
|
4 |
|
array_filter( |
89
|
4 |
|
$implementations, |
90
|
4 |
|
function (string $implementation) : bool { |
91
|
|
|
return |
92
|
4 |
|
! static::IsStringInArray($implementation, $this->processedSources); |
93
|
4 |
|
} |
94
|
|
|
) as $implementation |
95
|
|
|
) { |
96
|
4 |
|
$this->processedSources[] = $implementation; |
97
|
4 |
|
yield from $this->CollectInterfacesFromImplementationCheckInterfaces($implementation); |
98
|
4 |
|
yield from $this->CollectInterfacesFromImplementation($implementation); |
99
|
|
|
} |
100
|
4 |
|
} |
101
|
|
|
|
102
|
4 |
|
final protected function CollectInterfacesFromImplementationCheckInterfaces( |
103
|
|
|
string $implementation |
104
|
|
|
) : Generator { |
105
|
4 |
|
$checking = array_filter( |
106
|
4 |
|
$this->interfaces, |
107
|
4 |
|
function (string $interface) use ($implementation) : bool { |
108
|
4 |
|
return static::IsStringA($implementation, $interface); |
109
|
4 |
|
} |
110
|
|
|
); |
111
|
|
|
|
112
|
|
|
if ( |
113
|
4 |
|
count($checking) > self::INT_FILTER_NON_EMPTY_ARRAY && |
114
|
4 |
|
! static::IsStringInArray($implementation, $this->alreadyYielded) |
115
|
|
|
) { |
116
|
|
|
yield $implementation; |
117
|
|
|
$this->alreadyYielded[] = $implementation; |
118
|
|
|
} |
119
|
4 |
|
} |
120
|
|
|
|
121
|
4 |
|
final protected function CollectInterfacesFromImplementation(string $implementation) : Generator |
122
|
|
|
{ |
123
|
4 |
|
$interfaces = array_keys($this->staticMethods); |
124
|
|
|
|
125
|
4 |
|
foreach ($this->FilterIsA($implementation, $interfaces) as $interface) { |
126
|
4 |
|
foreach ($this->staticMethods[$interface] as $method => $types) { |
127
|
4 |
|
yield from $this->CollectInterfacesFromImplementationTypes( |
128
|
4 |
|
$implementation, |
129
|
4 |
|
$method, |
130
|
4 |
|
$types |
131
|
|
|
); |
132
|
|
|
} |
133
|
|
|
} |
134
|
4 |
|
} |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* @param array<int, string> $types |
138
|
|
|
*/ |
139
|
4 |
|
final protected function CollectInterfacesFromImplementationTypes( |
140
|
|
|
string $implementation, |
141
|
|
|
string $method, |
142
|
|
|
array $types |
143
|
|
|
) : Generator { |
144
|
|
|
/** |
145
|
|
|
* @var iterable<string> |
146
|
|
|
*/ |
147
|
4 |
|
$methodResult = $implementation::$method(); |
148
|
|
|
|
149
|
4 |
|
foreach ($methodResult as $result) { |
150
|
4 |
|
if (static::IsStringInArray($result, $this->alreadyYielded)) { |
151
|
1 |
|
continue; |
152
|
|
|
} |
153
|
|
|
|
154
|
4 |
|
foreach ($this->FilterIsA($result, $types) as $type) { |
155
|
4 |
|
yield $result; |
156
|
4 |
|
$this->alreadyYielded[] = $result; |
157
|
|
|
} |
158
|
|
|
|
159
|
4 |
|
yield from $this->CollectInterfaces($result); |
160
|
|
|
} |
161
|
4 |
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* @param array<int, string> $interfaces |
165
|
|
|
* |
166
|
|
|
* @return array<int, string> |
167
|
|
|
*/ |
168
|
|
|
final protected function FilterIsA(string $implementation, array $interfaces) : array |
169
|
|
|
{ |
170
|
|
|
/** |
171
|
|
|
* @var array<int, string> |
172
|
|
|
*/ |
173
|
4 |
|
$out = array_filter($interfaces, function (string $interface) use ($implementation) : bool { |
174
|
4 |
|
return static::IsStringA($implementation, $interface); |
175
|
4 |
|
}); |
176
|
|
|
|
177
|
4 |
|
return $out; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* @return string[]|array<string, mixed> |
182
|
|
|
*/ |
183
|
4 |
|
final protected function FilterArrayOfInterfaces( |
184
|
|
|
array $interfaces, |
185
|
|
|
int $flag = self::DEFAULT_INT_ARRAY_FILTER_FLAG |
186
|
|
|
) : array { |
187
|
4 |
|
$strings = array_filter($interfaces, 'is_string', $flag); |
188
|
|
|
|
189
|
4 |
|
return array_filter($strings, 'interface_exists', $flag); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* @return string[] |
194
|
|
|
*/ |
195
|
4 |
|
final protected function FilterArrayOfInterfacesOrClasses(array $interfaces) : array |
196
|
|
|
{ |
197
|
|
|
/** |
198
|
|
|
* @var string[] |
199
|
|
|
*/ |
200
|
4 |
|
$strings = array_filter($interfaces, 'is_string'); |
201
|
|
|
|
202
|
4 |
|
return array_filter($strings, function (string $maybe) : bool { |
203
|
4 |
|
return interface_exists($maybe) || class_exists($maybe); |
204
|
4 |
|
}); |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* @return array<string, array> |
209
|
|
|
*/ |
210
|
4 |
|
final protected function FilterArrayOfInterfaceOffsets(array $interfaces) : array |
211
|
|
|
{ |
212
|
|
|
/** |
213
|
|
|
* @var array<string, array> |
214
|
|
|
*/ |
215
|
4 |
|
$strings = $this->FilterArrayOfInterfaces($interfaces, ARRAY_FILTER_USE_KEY); |
216
|
|
|
|
217
|
4 |
|
return array_filter($strings, 'is_array'); |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
final protected function MakeMethodFilter(string $interface) : Closure |
221
|
|
|
{ |
222
|
4 |
|
return function (string $maybe) use ($interface) : bool { |
223
|
4 |
|
$ref = new ReflectionClass($interface); |
224
|
|
|
|
225
|
|
|
return |
226
|
4 |
|
$ref->hasMethod($maybe) && |
227
|
4 |
|
$this->FilterReflectionMethod($ref->getMethod($maybe)); |
228
|
4 |
|
}; |
229
|
|
|
} |
230
|
|
|
|
231
|
4 |
|
final protected function FilterReflectionMethod(ReflectionMethod $refMethod) : bool |
232
|
|
|
{ |
233
|
|
|
return |
234
|
4 |
|
$refMethod->isStatic() && |
235
|
4 |
|
$refMethod->isPublic() && |
236
|
4 |
|
self::EXPECTED_NUMBER_OF_REQUIRED_PARAMETERS === $refMethod->getNumberOfRequiredParameters() && |
237
|
4 |
|
$this->FilterReflectionReturnType($refMethod->getReturnType()); |
238
|
|
|
} |
239
|
|
|
|
240
|
4 |
|
final protected function FilterReflectionReturnType(ReflectionType $refReturn = null) : bool |
241
|
|
|
{ |
242
|
4 |
|
$refReturnName = ($refReturn instanceof ReflectionType) ? $refReturn->__toString() : ''; |
|
|
|
|
243
|
|
|
|
244
|
4 |
|
return 'array' === $refReturnName || static::IsStringA($refReturnName, Traversable::class); |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* @return array<string, string[]> |
249
|
|
|
*/ |
250
|
4 |
|
final protected function FilterMethods(string $interface, array $methods) : array |
251
|
|
|
{ |
252
|
|
|
/** |
253
|
|
|
* @var array<string, string[]> |
254
|
|
|
*/ |
255
|
4 |
|
$filteredMethods = $this->FilterNonZeroArray(array_map( |
256
|
4 |
|
[$this, 'FilterArrayOfInterfacesOrClasses'], |
257
|
4 |
|
array_filter( |
258
|
4 |
|
array_filter($methods, 'is_string', ARRAY_FILTER_USE_KEY), |
259
|
4 |
|
$this->MakeMethodFilter($interface), |
260
|
4 |
|
ARRAY_FILTER_USE_KEY |
261
|
|
|
) |
262
|
|
|
)); |
263
|
|
|
|
264
|
4 |
|
return $filteredMethods; |
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
/** |
268
|
|
|
* @return array<string, array<string, array<int, string>>> |
269
|
|
|
*/ |
270
|
4 |
|
final protected function FilterNonZeroArray(array $in) : array |
271
|
|
|
{ |
272
|
|
|
/** |
273
|
|
|
* @var array<string, array<string, array<int, string>>> |
274
|
|
|
*/ |
275
|
4 |
|
$out = array_filter( |
276
|
4 |
|
$in, |
277
|
4 |
|
function (array $val) : bool { |
278
|
4 |
|
return count($val) > self::INT_FILTER_NON_EMPTY_ARRAY; |
279
|
4 |
|
} |
280
|
|
|
); |
281
|
|
|
|
282
|
4 |
|
return $out; |
283
|
|
|
} |
284
|
|
|
|
285
|
4 |
|
protected static function IsStringInArray(string $maybe, array $array) : bool |
286
|
|
|
{ |
287
|
4 |
|
return in_array($maybe, $array, true); |
288
|
|
|
} |
289
|
|
|
|
290
|
4 |
|
protected static function IsStringA(string $maybe, string $thing) : bool |
291
|
|
|
{ |
292
|
4 |
|
return is_a($maybe, $thing, true); |
293
|
|
|
} |
294
|
|
|
} |
295
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.