Passed
Push — master ( b77f27...1019d9 )
by SignpostMarv
02:12
created

DefinitionAssistant::ValidateClosure()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 55
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 34
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 36
nc 4
nop 3
dl 0
loc 55
ccs 34
cts 34
cp 1
crap 4
rs 9.344
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
* @author SignpostMarv
4
*/
5
declare(strict_types=1);
6
7
namespace SignpostMarv\DaftMagicPropertyAnalysis;
8
9
use Closure;
10
use InvalidArgumentException;
11
use ReflectionFunction;
12
use ReflectionNamedType;
13
use ReflectionType;
14
15
class DefinitionAssistant
16
{
17
    const ARG_INDEX_CLOSURE_GETTER = 2;
18
19
    const ARG_INDEX_CLOSURE_SETTER = 3;
20
21
    const IN_ARRAY_STRICT_MODE = true;
22
23
    const COUNT_EXPECT_AT_LEAST_ONE_PROPERTY = 1;
24
25
    const COUNT_EXPECTED_REQUIRED_PARAMETERS = 1;
26
27
    const PARAM_INDEX_FIRST = 0;
28
29
    const BOOL_IS_PARAM = true;
30
31
    const BOOL_IS_RETURN = false;
32
33
    /**
34
    * @var array<string, array<int, string>>
35
    */
36
    protected static $properties = [];
37
38
    /**
39
    * @var array<string, Closure>
40
    */
41
    protected static $getters = [];
42
43
    /**
44
    * @var array<string, Closure>
45
    */
46
    protected static $setters = [];
47
48 16
    public static function IsTypeUnregistered(string $type) : bool
49
    {
50 16
        if ( ! interface_exists($type) && ! class_exists($type)) {
51 2
            throw new InvalidArgumentException(
52
                'Argument 1 passed to ' .
53
                __METHOD__ .
54 2
                '() must be a class or interface!'
55
            );
56
        }
57
58 14
        return ! isset(static::$properties[$type]);
59
    }
60
61 16
    public static function RegisterType(
62
        string $type,
63
        ? Closure $getter,
64
        ? Closure $setter,
65
        string ...$properties
66
    ) : void {
67 16
        if ( ! self::IsTypeUnregistered($type)) {
68 2
            throw new InvalidArgumentException(
69
                'Argument 1 passed to ' .
70
                __METHOD__ .
71 2
                '() has already been registered!'
72
            );
73 14
        } elseif (is_null($getter) && is_null($setter)) {
74 2
            throw new InvalidArgumentException(
75 2
                'One or both of arguments 2 and 3 must be specified!'
76
            );
77 12
        } elseif (count($properties) < self::COUNT_EXPECT_AT_LEAST_ONE_PROPERTY) {
78 2
            throw new InvalidArgumentException(
79 2
                'Argument 4 must be specified!'
80
            );
81
        }
82
83
        /**
84
        * @var string
85
        */
86 10
        $type = $type;
87
88 10
        static::MaybeRegisterTypeGetter($type, $getter);
89 8
        static::MaybeRegisterTypeSetter($type, $setter);
0 ignored issues
show
Bug introduced by
$type of type object is incompatible with the type string expected by parameter $type of SignpostMarv\DaftMagicPr...ybeRegisterTypeSetter(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

89
        static::MaybeRegisterTypeSetter(/** @scrutinizer ignore-type */ $type, $setter);
Loading history...
90
91 6
        static::$properties[$type] = $properties;
92 6
    }
93
94 4
    public static function GetterMethodName(string $type, string $property) : ? string
95
    {
96
        if (
97 4
            in_array($property, static::$properties[$type] ?? [], self::IN_ARRAY_STRICT_MODE) &&
98 4
            isset(static::$getters[$type])
99
        ) {
100
            /**
101
            * @var string|null
102
            */
103 4
            $out = static::$getters[$type]($property);
104
105 4
            return $out;
106
        }
107
108 2
        foreach (self::$getters as $otherType => $getter) {
109
            if (
110 2
                $otherType !== $type &&
111 2
                isset(self::$properties[$otherType]) &&
112 2
                in_array($property, self::$properties[$otherType], self::IN_ARRAY_STRICT_MODE)
113
            ) {
114
                /**
115
                * @var string|null
116
                */
117
                $out = $getter($property);
118
119 2
                return $out;
120
            }
121
        }
122
123 2
        return null;
124
    }
125
126 4
    public static function SetterMethodName(string $type, string $property) : ? string
127
    {
128
        if (
129 4
            in_array($property, static::$properties[$type] ?? [], self::IN_ARRAY_STRICT_MODE) &&
130 4
            isset(static::$setters[$type])
131
        ) {
132
            /**
133
            * @var string|null
134
            */
135 4
            $out = static::$setters[$type]($property);
136
137 4
            return $out;
138
        }
139
140 2
        return null;
141
    }
142
143
    /**
144
    * @param mixed $maybe
145
    *
146
    * @return array<int, string>
147
    */
148 12
    public static function ObtainExpectedProperties($maybe) : array
149
    {
150 12
        if ( ! is_string($maybe) && ! is_object($maybe)) {
151 8
            throw new InvalidArgumentException(
152
                'Argument 1 passed to ' .
153
                __METHOD__ .
154
                '() must be either a string or an object, ' .
155 8
                gettype($maybe) .
156 8
                ' given!'
157
            );
158
        }
159
160
        /**
161
        * @var array<int, string>
162
        */
163 4
        $out = array_values(array_unique(array_reduce(
164 4
            array_filter(
165 4
                static::$properties,
166
                function (string $type) use ($maybe) : bool {
167 4
                    return is_a($maybe, $type, is_string($maybe));
168 4
                },
169 4
                ARRAY_FILTER_USE_KEY
170
            ),
171 4
            'array_merge',
172 4
            []
173
        )));
174
175 4
        return $out;
176
    }
177
178 22
    protected static function ValidateClosure(
179
        Closure $closure,
180
        int $argument,
181
        string $method
182
    ) : Closure {
183 22
        $ref = new ReflectionFunction($closure);
184
185 22
        if (self::COUNT_EXPECTED_REQUIRED_PARAMETERS !== $ref->getNumberOfRequiredParameters()) {
186 4
            throw new InvalidArgumentException(
187
                'Argument ' .
188 4
                $argument .
189 4
                ' passed to ' .
190 4
                $method .
191 4
                '() must be a closure with 1 required parameter!'
192
            );
193
        }
194
195 18
        $ref_param = $ref->getParameters()[self::PARAM_INDEX_FIRST];
196
197 18
        if ( ! $ref_param->hasType()) {
198 2
            throw new InvalidArgumentException(
199
                'Argument ' .
200 2
                $argument .
201 2
                ' passed to ' .
202 2
                $method .
203 2
                '() must be a closure with a typed first argument!'
204
            );
205
        }
206
207 16
        $closure = static::ValidateTypeExpectNonNullableString(
208 16
            $closure,
209 16
            $ref_param->getType(),
210 16
            $argument,
211 16
            $method,
212 16
            self::BOOL_IS_PARAM
213
        );
214
215 12
        if ( ! $ref->hasReturnType()) {
216 2
            throw new InvalidArgumentException(
217
                'Argument ' .
218 2
                $argument .
219 2
                ' passed to ' .
220 2
                $method .
221 2
                '() must have a return type!'
222
            );
223
        }
224
225 10
        $ref_return = $ref->getReturnType();
226
227 10
        return static::ValidateTypeExpectNonNullableString(
228 10
            $closure,
229 10
            $ref_return,
230 10
            $argument,
231 10
            $method,
232 10
            self::BOOL_IS_RETURN
233
        );
234
    }
235
236 24
    protected static function ValidateTypeExpectNonNullableString(
237
        Closure $closure,
238
        ? ReflectionType $ref,
239
        int $argument,
240
        string $method,
241
        bool $isParam
242
    ) : Closure {
243 24
        $not_named_type = ' named return type';
244
245 24
        if ($isParam) {
246 20
            $not_named_type = ' strongly-typed first argument';
247
        }
248
249 24
        if ( ! ($ref instanceof ReflectionNamedType)) {
250 8
            throw new InvalidArgumentException(
251
                'Argument ' .
252 8
                $argument .
253 8
                ' passed to ' .
254 8
                $method .
255 8
                '() must be a closure with a' .
256 8
                $not_named_type .
257 8
                '!'
258
            );
259
        }
260
261 16
        return static::ValidateTypeExpectNonNullableStringWithNamedType(
262 16
            $closure,
263 16
            $ref,
264 16
            $argument,
265 16
            $method,
266 16
            $isParam
267
        );
268
    }
269
270 16
    protected static function ValidateTypeExpectNonNullableStringWithNamedType(
271
        Closure $closure,
272
        ReflectionNamedType $ref,
273
        int $argument,
274
        string $method,
275
        bool $isParam
276
    ) : Closure {
277 16
        $nullable = '';
278 16
        $return_type = 'return type';
279
280 16
        if ($isParam) {
281 16
            $nullable = 'non-';
282 16
            $return_type = 'first argument';
283
        }
284
285 16
        if ($isParam ? $ref->allowsNull() : ( ! $ref->allowsNull())) {
286 4
            throw new InvalidArgumentException(
287
                'Argument ' .
288 4
                $argument .
289 4
                ' passed to ' .
290 4
                $method .
291 4
                '() must be a closure with a ' .
292 4
                $nullable .
293 4
                'nullable ' .
294 4
                $return_type .
295 4
                '!'
296
            );
297 14
        } elseif ('string' !== $ref->getName()) {
298 4
            throw new InvalidArgumentException(
299
                'Argument ' .
300 4
                $argument .
301 4
                ' passed to ' .
302 4
                $method .
303 4
                '() must be a closure with a string ' .
304 4
                $return_type .
305 4
                ', ' .
306 4
                $ref->getName() .
307 4
                ' given!'
308
            );
309
        }
310
311 12
        return $closure;
312
    }
313
314 10
    private static function MaybeRegisterTypeGetter(string $type, ? Closure $getter) : void
315
    {
316 10
        if ( ! is_null($getter)) {
317 8
            if ( ! method_exists($type, '__get')) {
318 2
                throw new InvalidArgumentException(
319
                    'Argument 1 passed to ' .
320
                    __CLASS__ .
321 2
                    '::RegisterType() must declare __get() !'
322
                );
323
            }
324
325
            /**
326
            * @var string
327
            */
328 6
            $type = $type;
329
330
            /**
331
            * @var Closure
332
            */
333 6
            $getter = static::ValidateClosure(
334 6
                $getter,
335 6
                self::ARG_INDEX_CLOSURE_GETTER,
336 6
                (__CLASS__ . '::RegisterType')
337
            );
338
339 6
            self::$getters[$type] = $getter;
340
        }
341 8
    }
342
343 8
    private static function MaybeRegisterTypeSetter(string $type, ? Closure $setter) : void
344
    {
345 8
        if ( ! is_null($setter)) {
346 8
            if ( ! method_exists($type, '__set')) {
347 2
                throw new InvalidArgumentException(
348
                    'Argument 1 passed to ' .
349
                    __CLASS__ .
350 2
                    '::RegisterType() must declare __set() !'
351
                );
352
            }
353
354
            /**
355
            * @var string
356
            */
357 6
            $type = $type;
358
359
            /**
360
            * @var Closure
361
            */
362 6
            $setter = static::ValidateClosure(
363 6
                $setter,
364 6
                self::ARG_INDEX_CLOSURE_SETTER,
365 6
                (__CLASS__ . '::RegisterType')
366
            );
367
368 6
            self::$setters[$type] = $setter;
369
        }
370 6
    }
371
}
372