Passed
Push — master ( 1019d9...c8136d )
by SignpostMarv
02:20
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
        return self::CheckOtherTypes(self::$getters, $type, $property);
109
    }
110
111 4
    public static function SetterMethodName(string $type, string $property) : ? string
112
    {
113
        if (
114 4
            in_array($property, static::$properties[$type] ?? [], self::IN_ARRAY_STRICT_MODE) &&
115 4
            isset(static::$setters[$type])
116
        ) {
117
            /**
118
            * @var string|null
119
            */
120 4
            $out = static::$setters[$type]($property);
121
122 4
            return $out;
123
        }
124
125 2
        return self::CheckOtherTypes(self::$setters, $type, $property);
126
    }
127
128
    /**
129
    * @param mixed $maybe
130
    *
131
    * @return array<int, string>
132
    */
133 12
    public static function ObtainExpectedProperties($maybe) : array
134
    {
135 12
        if ( ! is_string($maybe) && ! is_object($maybe)) {
136 8
            throw new InvalidArgumentException(
137
                'Argument 1 passed to ' .
138
                __METHOD__ .
139
                '() must be either a string or an object, ' .
140 8
                gettype($maybe) .
141 8
                ' given!'
142
            );
143
        }
144
145
        /**
146
        * @var array<int, string>
147
        */
148 4
        $out = array_values(array_unique(array_reduce(
149 4
            array_filter(
150 4
                static::$properties,
151
                function (string $type) use ($maybe) : bool {
152 4
                    return is_a($maybe, $type, is_string($maybe));
153 4
                },
154 4
                ARRAY_FILTER_USE_KEY
155
            ),
156 4
            'array_merge',
157 4
            []
158
        )));
159
160 4
        return $out;
161
    }
162
163
    /**
164
    * @param array<string, Closure> $otherTypes
165
    */
166 2
    protected static function CheckOtherTypes(
167
        array $otherTypes,
0 ignored issues
show
Unused Code introduced by
The parameter $otherTypes is not used and could be removed. ( Ignorable by Annotation )

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

167
        /** @scrutinizer ignore-unused */ array $otherTypes,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
168
        string $type,
169
        string $property
170
    ) : ? string {
171 2
        foreach (self::$getters as $otherType => $getter) {
172
            if (
173 2
                $otherType !== $type &&
174 2
                isset(self::$properties[$otherType]) &&
175 2
                in_array($property, self::$properties[$otherType], self::IN_ARRAY_STRICT_MODE)
176
            ) {
177
                /**
178
                * @var string|null
179
                */
180
                $out = $getter($property);
181
182 2
                return $out;
183
            }
184
        }
185
186 2
        return null;
187
    }
188
189 22
    protected static function ValidateClosure(
190
        Closure $closure,
191
        int $argument,
192
        string $method
193
    ) : Closure {
194 22
        $ref = new ReflectionFunction($closure);
195
196 22
        if (self::COUNT_EXPECTED_REQUIRED_PARAMETERS !== $ref->getNumberOfRequiredParameters()) {
197 4
            throw new InvalidArgumentException(
198
                'Argument ' .
199 4
                $argument .
200 4
                ' passed to ' .
201 4
                $method .
202 4
                '() must be a closure with 1 required parameter!'
203
            );
204
        }
205
206 18
        $ref_param = $ref->getParameters()[self::PARAM_INDEX_FIRST];
207
208 18
        if ( ! $ref_param->hasType()) {
209 2
            throw new InvalidArgumentException(
210
                'Argument ' .
211 2
                $argument .
212 2
                ' passed to ' .
213 2
                $method .
214 2
                '() must be a closure with a typed first argument!'
215
            );
216
        }
217
218 16
        $closure = static::ValidateTypeExpectNonNullableString(
219 16
            $closure,
220 16
            $ref_param->getType(),
221 16
            $argument,
222 16
            $method,
223 16
            self::BOOL_IS_PARAM
224
        );
225
226 12
        if ( ! $ref->hasReturnType()) {
227 2
            throw new InvalidArgumentException(
228
                'Argument ' .
229 2
                $argument .
230 2
                ' passed to ' .
231 2
                $method .
232 2
                '() must have a return type!'
233
            );
234
        }
235
236 10
        $ref_return = $ref->getReturnType();
237
238 10
        return static::ValidateTypeExpectNonNullableString(
239 10
            $closure,
240 10
            $ref_return,
241 10
            $argument,
242 10
            $method,
243 10
            self::BOOL_IS_RETURN
244
        );
245
    }
246
247 24
    protected static function ValidateTypeExpectNonNullableString(
248
        Closure $closure,
249
        ? ReflectionType $ref,
250
        int $argument,
251
        string $method,
252
        bool $isParam
253
    ) : Closure {
254 24
        $not_named_type = ' named return type';
255
256 24
        if ($isParam) {
257 20
            $not_named_type = ' strongly-typed first argument';
258
        }
259
260 24
        if ( ! ($ref instanceof ReflectionNamedType)) {
261 8
            throw new InvalidArgumentException(
262
                'Argument ' .
263 8
                $argument .
264 8
                ' passed to ' .
265 8
                $method .
266 8
                '() must be a closure with a' .
267 8
                $not_named_type .
268 8
                '!'
269
            );
270
        }
271
272 16
        return static::ValidateTypeExpectNonNullableStringWithNamedType(
273 16
            $closure,
274 16
            $ref,
275 16
            $argument,
276 16
            $method,
277 16
            $isParam
278
        );
279
    }
280
281 16
    protected static function ValidateTypeExpectNonNullableStringWithNamedType(
282
        Closure $closure,
283
        ReflectionNamedType $ref,
284
        int $argument,
285
        string $method,
286
        bool $isParam
287
    ) : Closure {
288 16
        $nullable = '';
289 16
        $return_type = 'return type';
290
291 16
        if ($isParam) {
292 16
            $nullable = 'non-';
293 16
            $return_type = 'first argument';
294
        }
295
296 16
        if ($isParam ? $ref->allowsNull() : ( ! $ref->allowsNull())) {
297 4
            throw new InvalidArgumentException(
298
                'Argument ' .
299 4
                $argument .
300 4
                ' passed to ' .
301 4
                $method .
302 4
                '() must be a closure with a ' .
303 4
                $nullable .
304 4
                'nullable ' .
305 4
                $return_type .
306 4
                '!'
307
            );
308 14
        } elseif ('string' !== $ref->getName()) {
309 4
            throw new InvalidArgumentException(
310
                'Argument ' .
311 4
                $argument .
312 4
                ' passed to ' .
313 4
                $method .
314 4
                '() must be a closure with a string ' .
315 4
                $return_type .
316 4
                ', ' .
317 4
                $ref->getName() .
318 4
                ' given!'
319
            );
320
        }
321
322 12
        return $closure;
323
    }
324
325 10
    private static function MaybeRegisterTypeGetter(string $type, ? Closure $getter) : void
326
    {
327 10
        if ( ! is_null($getter)) {
328 8
            if ( ! method_exists($type, '__get')) {
329 2
                throw new InvalidArgumentException(
330
                    'Argument 1 passed to ' .
331
                    __CLASS__ .
332 2
                    '::RegisterType() must declare __get() !'
333
                );
334
            }
335
336
            /**
337
            * @var string
338
            */
339 6
            $type = $type;
340
341
            /**
342
            * @var Closure
343
            */
344 6
            $getter = static::ValidateClosure(
345 6
                $getter,
346 6
                self::ARG_INDEX_CLOSURE_GETTER,
347 6
                (__CLASS__ . '::RegisterType')
348
            );
349
350 6
            self::$getters[$type] = $getter;
351
        }
352 8
    }
353
354 8
    private static function MaybeRegisterTypeSetter(string $type, ? Closure $setter) : void
355
    {
356 8
        if ( ! is_null($setter)) {
357 8
            if ( ! method_exists($type, '__set')) {
358 2
                throw new InvalidArgumentException(
359
                    'Argument 1 passed to ' .
360
                    __CLASS__ .
361 2
                    '::RegisterType() must declare __set() !'
362
                );
363
            }
364
365
            /**
366
            * @var string
367
            */
368 6
            $type = $type;
369
370
            /**
371
            * @var Closure
372
            */
373 6
            $setter = static::ValidateClosure(
374 6
                $setter,
375 6
                self::ARG_INDEX_CLOSURE_SETTER,
376 6
                (__CLASS__ . '::RegisterType')
377
            );
378
379 6
            self::$setters[$type] = $setter;
380
        }
381 6
    }
382
}
383