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.
Test Failed
Push — master ( 316d38...a6fddc )
by SignpostMarv
02:47
created

TypeUtilities::MaybeThrowIfNotArrayIntKeys()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 1
dl 0
loc 18
rs 10
c 0
b 0
f 0
ccs 8
cts 8
cp 1
crap 2
1
<?php
2
/**
3
* @author SignpostMarv
4
*/
5
declare(strict_types=1);
6
7
namespace SignpostMarv\DaftObject;
8
9
use InvalidArgumentException;
10
use ReflectionException;
11
use ReflectionMethod;
12
13
class TypeUtilities
14
{
15
    const INDEX_FIRST_ARG = 1;
16
17
    const INDEX_SECOND_ARG = 2;
18
19
    const BOOL_EXPECTING_NON_PUBLIC_METHOD = false;
20
21
    const BOOL_EXPECTING_GETTER = false;
22
23
    const BOOL_DEFAULT_THROWIFNOTIMPLEMENTATION = false;
24
25
    const BOOL_DEFAULT_EXPECTING_NON_PUBLIC_METHOD = true;
26
27
    const BOOL_METHOD_IS_PUBLIC = true;
28
29 118
    const BOOL_METHOD_IS_NON_PUBLIC = false;
30
31 118
    const SUPPORTED_INVALID_LEADING_CHARACTERS = [
32
        '@',
33 118
    ];
34
35
    /**
36 2
    * @var array<string, array<string, bool>>
37
    */
38 2
    private static $Getters = [];
39
40 2
    /**
41
    * @var array<string, array<int, string>>
42
    */
43 186
    private static $publicSetters = [];
44
45 186
    public static function DaftObjectPublicGetters(string $class) : array
46
    {
47 186
        static::CachePublicGettersAndSetters($class);
48
49
        return array_keys(array_filter(self::$Getters[$class]));
50 32
    }
51
52
    public static function DaftObjectPublicOrProtectedGetters(string $class) : array
53
    {
54
        static::CachePublicGettersAndSetters($class);
55
56 32
        return array_keys(self::$Getters[$class]);
57
    }
58
59 32
    public static function DaftObjectPublicSetters(string $class) : array
60
    {
61 32
        static::CachePublicGettersAndSetters($class);
62 8
63 8
        return self::$publicSetters[$class];
64
    }
65
66
    private static function HasMethod(
67 262
        string $class,
68
        string $property,
69 262
        bool $SetNotGet,
70 2
        bool $pub = self::BOOL_DEFAULT_EXPECTING_NON_PUBLIC_METHOD
71
    ) : bool {
72
        $method = static::MethodNameFromProperty($property, $SetNotGet);
73 260
74
        try {
75
            $ref = new ReflectionMethod($class, $method);
76
77
            return ($pub ? $ref->isPublic() : $ref->isProtected()) && false === $ref->isStatic();
78
        } catch (ReflectionException $e) {
79
            return false;
80
        }
81
    }
82
83
    public static function MethodNameFromProperty(string $prop, bool $SetNotGet = false) : string
84 528
    {
85
        if (static::MaybeInArray(mb_substr($prop, 0, 1), self::SUPPORTED_INVALID_LEADING_CHARACTERS)) {
86
            return ($SetNotGet ? 'Alter' : 'Obtain') . ucfirst(mb_substr($prop, 1));
87
        }
88 528
89 314
        return ($SetNotGet ? 'Set' : 'Get') . ucfirst($prop);
90 220
    }
91 2
92 2
    /**
93 2
    * Checks if a type correctly defines it's own id.
94
    *
95
    * @throws ClassDoesNotImplementClassException if $class is not an implementation of DefinesOwnIdPropertiesInterface
96 524
    * @throws ClassMethodReturnHasZeroArrayCountException if $class::DaftObjectIdProperties() does not contain at least one property
97
    * @throws ClassMethodReturnIsNotArrayOfStringsException if $class::DaftObjectIdProperties() is not string[]
98
    * @throws UndefinedPropertyException if an id property is not in $class::DaftObjectIdProperties()
99
    */
100
    public static function CheckTypeDefinesOwnIdProperties(
101
        string $class,
102
        bool $throwIfNotImplementation = self::BOOL_DEFAULT_THROWIFNOTIMPLEMENTATION
103 14
    ) : void {
104
        if (self::IsThingStrings($class, DefinesOwnIdPropertiesInterface::class)) {
105
            self::CheckTypeDefinesOwnIdPropertiesIsImplementation($class);
106
        } elseif ($throwIfNotImplementation) {
107
            throw new ClassDoesNotImplementClassException(
108
                $class,
109 14
                DefinesOwnIdPropertiesInterface::class
110 2
            );
111
        }
112
    }
113
114 2
    /**
115 2
    * @param mixed $value
116
    *
117
    * @return array<int, mixed> filtered $value
118
    */
119 12
    public static function MaybeThrowIfValueDoesNotMatchMultiTypedArray(
120 12
        bool $autoTrimStrings,
121 12
        bool $throwIfNotUnique,
122 12
        $value,
123 9
        string ...$types
124
    ) : array {
125
        if ( ! is_array($value)) {
126
            throw new InvalidArgumentException(
127
                'Argument 3 passed to ' .
128
                __METHOD__ .
129
                ' must be an array, ' .
130 12
                (is_object($value) ? get_class($value) : gettype($value)) .
131
                ' given!'
132 12
            );
133
        }
134
135
        return static::MaybeThrowIfValueDoesNotMatchMultiTypedArrayValueArray(
136
            $autoTrimStrings,
137 12
            $throwIfNotUnique,
138
            $value,
139 12
            ...$types
140 2
        );
141
    }
142
143 2
    /**
144
    * @param mixed $needle
145
    * @param mixed $haystack
146
    */
147 10
    public static function MaybeInMaybeArray($needle, $haystack) : bool
148
    {
149
        $haystack = self::EnsureArgumentIsArray($haystack, self::INDEX_SECOND_ARG, __METHOD__);
150
151
        return static::MaybeInArray($needle, $haystack);
152
    }
153
154
    /**
155 10
    * @param mixed $needle
156
    */
157
    public static function MaybeInArray($needle, array $haystack) : bool
158
    {
159 10
        return in_array($needle, $haystack, true);
160
    }
161 10
162 10
    /**
163
    * @param mixed $maybe
164
    */
165
    private static function FilterMaybeArray($maybe, callable $filter) : array
166
    {
167 10
        return array_filter(
168 8
            self::EnsureArgumentIsArray($maybe, self::INDEX_FIRST_ARG, __METHOD__),
169 8
            $filter
170 8
        );
171
    }
172
173
    /**
174 2
    * @param mixed $maybe
175
    */
176
    private static function CountMaybeArray($maybe) : int
177 4
    {
178 10
        return count(self::EnsureArgumentIsArray($maybe, self::INDEX_FIRST_ARG, __METHOD__));
179
    }
180
181 10
    /**
182 2
    * @param mixed $maybe
183
    */
184
    public static function EnsureArgumentIsArray($maybe, int $argument = null, string $method = __METHOD__) : array
185 2
    {
186
        if ( ! is_array($maybe)) {
187
            throw new InvalidArgumentException(
188
                'Argument ' .
189 8
                (is_int($argument) ? $argument : self::INDEX_FIRST_ARG) .
190
                ' passed to ' .
191
                $method .
192
                ' must be an array, ' .
193
                (is_object($maybe) ? get_class($maybe) : gettype($maybe)) .
194
                ' given!'
195
            );
196
        }
197 8
198
        return $maybe;
199
    }
200
201
    public static function ForceArgumentAsArray($maybe) : array
202 8
    {
203 2
        return is_array($maybe) ? $maybe : [$maybe];
204
    }
205
206
    /**
207
    * @param mixed $maybe
208
    */
209
    public static function EnsureArgumentIsString($maybe) : string
210 2
    {
211 2
        if ( ! is_string($maybe)) {
212 2
            throw new InvalidArgumentException(
213
                'Argument 1 passed to ' .
214
                __METHOD__ .
215
                ' must be a string, ' .
216 8
                (is_object($maybe) ? get_class($maybe) : gettype($maybe))
217
            );
218
        }
219
220
        return $maybe;
221
    }
222 12
223
    /**
224
    * @return array<int, mixed> filtered $value
225
    */
226
    private static function MaybeThrowIfNotArrayIntKeys(array $value) : array
227
    {
228 12
        $initialCount = count($value);
229 10
230 8
        /**
231
        * @var array<int, mixed>
232 8
        */
233
        $value = array_filter($value, 'is_int', ARRAY_FILTER_USE_KEY);
234 8
235
        if (count($value) !== $initialCount) {
236 8
            throw new InvalidArgumentException(
237 2
                'Argument 3 passed to ' .
238
                __METHOD__ .
239
                ' must be array<int, mixed>'
240 2
            );
241
        }
242
243
        return $value;
244 6
    }
245
246
    /**
247 236
    * @param array<int, mixed> $value
248
    *
249 236
    * @return array<int, mixed> filtered $value
250 32
    */
251 32
    private static function MaybeThrowIfValueArrayDoesNotMatchTypes(
252
        array $value,
253 32
        string ...$types
254 16
    ) : array {
255
        $initialCount = count($value);
256
257 32
        $value = array_filter(
258
            $value,
259 236
            /**
260
            * @param mixed $maybe
261
            */
262
            function ($maybe) use ($types) : bool {
263
                if (is_object($maybe)) {
264 32
                    foreach ($types as $maybeType) {
265
                        if (is_a($maybe, $maybeType)) {
266
                            return true;
267
                        }
268
                    }
269 32
270
                    return false;
271 32
                }
272 32
273 28
                return static::MaybeInArray(gettype($maybe), $types);
274 6
            }
275 2
        );
276
277
        if (count($value) !== $initialCount) {
278 32
            throw new InvalidArgumentException(
279 32
                'Argument 3 passed to ' .
280
                __METHOD__ .
281
                ' contained values that did not match the provided types!'
282 32
            );
283
        }
284
285
        return $value;
286
    }
287 314
288
    /**
289
    * @param array<int, mixed> $value
290 314
    *
291
    * @return array<int, mixed>
292 314
    */
293 2
    private static function MaybeRemapStringsToTrimmedStrings(
294 2
        array $value,
295 2
        bool $autoTrimStrings,
296
        string ...$types
297 312
    ) : array {
298 2
        if (static::MaybeInArray('string', $types) && $autoTrimStrings) {
299 2
            $value = array_map(
300 2
                /**
301
                * @param mixed $maybe
302
                *
303 310
                * @return mixed
304
                */
305
                function ($maybe) {
306
                    return is_string($maybe) ? trim($maybe) : $maybe;
307
                },
308
                $value
309
            );
310
        }
311
312
        return $value;
313
    }
314
315
    /**
316
    * @return array<int, mixed> filtered $value
317
    */
318
    private static function MaybeThrowIfValueDoesNotMatchMultiTypedArrayValueArray(
319
        bool $autoTrimStrings,
320
        bool $throwIfNotUnique,
321
        array $value,
322
        string ...$types
323
    ) : array {
324
        $value = static::MaybeThrowIfNotArrayIntKeys($value);
325
        $value = static::MaybeThrowIfValueArrayDoesNotMatchTypes($value, ...$types);
326
        $value = static::MaybeRemapStringsToTrimmedStrings($value, $autoTrimStrings, ...$types);
327
328
        $initialCount = count($value);
329
330
        $value = array_unique($value, SORT_REGULAR);
331
332
        if ($throwIfNotUnique && count($value) !== $initialCount) {
333
            throw new InvalidArgumentException(
334
                'Argument 3 passed to ' .
335
                __METHOD__ .
336
                ' contained non-unique values!'
337
            );
338
        }
339
340
        return array_values($value);
341
    }
342
343
    private static function CachePublicGettersAndSetters(string $class) : void
344
    {
345
        if (false === isset(self::$Getters[$class])) {
346
            self::$Getters[$class] = [];
347
            self::$publicSetters[$class] = [];
348
349
            if (self::IsThingStrings($class, DefinesOwnIdPropertiesInterface::class)) {
350
                self::$Getters[$class]['id'] = true;
351
            }
352
353
            self::CachePublicGettersAndSettersProperties($class);
354
        }
355
    }
356
357
    public static function IsThingStrings(string $maybe, string $thing) : bool
358
    {
359
        return is_a($maybe, $thing, true);
360
    }
361
362
    public static function IsSubThingStrings(string $maybe, string $thing) : bool
363
    {
364
        return is_subclass_of($maybe, $thing, true);
365
    }
366
367
    /**
368
    * @psalm-suppress InvalidStringClass
369
    */
370
    private static function CachePublicGettersAndSettersProperties(string $class) : void
371
    {
372
        /**
373
        * @var string[]
374
        */
375
        $props = $class::DaftObjectProperties();
376
377
        foreach ($props as $prop) {
378
            if (static::HasMethod($class, $prop, self::BOOL_EXPECTING_GETTER)) {
379
                self::$Getters[$class][$prop] = self::BOOL_METHOD_IS_PUBLIC;
380
            } elseif (static::HasMethod(
381
                $class,
382
                $prop,
383
                self::BOOL_EXPECTING_GETTER,
384
                self::BOOL_EXPECTING_NON_PUBLIC_METHOD
385
            )) {
386
                self::$Getters[$class][$prop] = self::BOOL_METHOD_IS_NON_PUBLIC;
387
            }
388
389
            if (static::HasMethod($class, $prop, true)) {
390
                self::$publicSetters[$class][] = $prop;
391
            }
392
        }
393
    }
394
395
    /**
396
    * @psalm-suppress InvalidStringClass
397
    */
398
    private static function CheckTypeDefinesOwnIdPropertiesIsImplementation(
399
        string $class
400
    ) : void {
401
        /**
402
        * @var scalar|array|object|null
403
        */
404
        $properties = $class::DaftObjectIdProperties();
405
406
        if (self::CountMaybeArray($properties) < 1) {
407
            throw new ClassMethodReturnHasZeroArrayCountException(
408
                $class,
409
                'DaftObjectIdProperties'
410
            );
411
        } elseif (
412
            self::CountMaybeArray($properties) !==
413
            count(self::FilterMaybeArray($properties, 'is_string'))
414
        ) {
415
            throw new ClassMethodReturnIsNotArrayOfStringsException(
416
                $class,
417
                'DaftObjectIdProperties'
418
            );
419
        }
420
    }
421
}
422