Completed
Push — master ( baf3d3...388f64 )
by Lars
14:31
created

Json::mapArray()   C

Complexity

Conditions 17
Paths 16

Size

Total Lines 74

Duplication

Lines 9
Ratio 12.16 %

Code Coverage

Tests 20
CRAP Score 53.125

Importance

Changes 0
Metric Value
cc 17
nc 16
nop 4
dl 9
loc 74
ccs 20
cts 40
cp 0.5
crap 53.125
rs 5.2166
c 0
b 0
f 0

How to fix   Long Method    Complexity   

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
namespace Arrayy\Mapper;
4
5
/**
6
 * @category Netresearch
7
 *
8
 * @license  OSL-3.0 http://opensource.org/licenses/osl-3.0
9
 *
10
 * @see     http://cweiske.de/
11
 *
12
 * INFO: this json-mapper is mostly a copy of https://github.com/cweiske/jsonmapper/
13
 *
14
 * @internal
15
 */
16
final class Json
17
{
18
    /**
19
     * Override class names that JsonMapper uses to create objects.
20
     * Useful when your setter methods accept abstract classes or interfaces.
21
     *
22
     * @var array
23
     */
24
    public $classMap = [];
25
26
    /**
27
     * Callback used when an undefined property is found.
28
     *
29
     * Works only when $bExceptionOnUndefinedProperty is disabled.
30
     *
31
     * Parameters to this function are:
32
     * 1. Object that is being filled
33
     * 2. Name of the unknown JSON property
34
     * 3. JSON value of the property
35
     *
36
     * @var callable
37
     */
38
    public $undefinedPropertyHandler;
39
40
    /**
41
     * Runtime cache for inspected classes. This is particularly effective if
42
     * mapArray() is called with a large number of objects
43
     *
44
     * @var array property inspection result cache
45
     */
46
    private $arInspectedClasses = [];
47
48
    /**
49
     * Map data all data in $json into the given $object instance.
50
     *
51
     * @param iterable      $json   JSON object structure from json_decode()
52
     * @param object|string $object Object to map $json data into
53
     *
54
     * @phpstan-param object|class-string $object Object to map $json data into
55
     *
56
     * @return mixed mapped object is returned
57
     *
58
     * @see    mapArray()
59
     */
60 6
    public function map($json, $object)
61
    {
62 6
        if (\is_string($object) && \class_exists($object)) {
63 3
            $object = self::createInstance($object);
64
        }
65
66 6
        if (!\is_object($object)) {
67
            throw new \InvalidArgumentException(
68
                'JsonMapper::map() requires second argument to be an object, ' . \gettype($object) . ' given.'
69
            );
70
        }
71
72 6
        $strClassName = \get_class($object);
73 6
        $rc = new \ReflectionClass($object);
74 6
        $strNs = $rc->getNamespaceName();
75 6
        foreach ($json as $key => $jsonValue) {
76 6
            $key = $this->getSafeName($key);
77
78
            // Store the property inspection results, so we don't have to do it
79
            // again for subsequent objects of the same type.
80 6
            if (!isset($this->arInspectedClasses[$strClassName][$key])) {
81 6
                $this->arInspectedClasses[$strClassName][$key] = $this->inspectProperty($rc, $key);
82
            }
83
84
            list(
85
                $hasProperty,
86
                $accessor,
87
                $type
88 6
            ) = $this->arInspectedClasses[$strClassName][$key];
89
90 6
            if (!$hasProperty) {
91 1
                if (\is_callable($this->undefinedPropertyHandler)) {
92
                    \call_user_func(
93 1
                        $this->undefinedPropertyHandler,
94
                        $object,
95
                        $key,
96
                        $jsonValue
97
                    );
98
                }
99
100
                continue;
101
            }
102
103 6
            if ($accessor === null) {
104
                continue;
105
            }
106
107 6
            if ($this->isNullable($type)) {
108 4
                if ($jsonValue === null) {
109 1
                    $this->setProperty($object, $accessor, null);
110
111 1
                    continue;
112
                }
113
114 3
                $type = $this->removeNullable($type);
115 6 View Code Duplication
            } elseif ($jsonValue === null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
116
                throw new \InvalidArgumentException(
117
                    'JSON property "' . $key . '" in class "' . $strClassName . '" must not be NULL'
118
                );
119
            }
120
121 6
            $type = $this->getFullNamespace($type, $strNs);
122 6
            $type = $this->getMappedType($type, $jsonValue);
123
124
            if (
125 6
                $type === null
126
                ||
127 6
                $type === 'mixed'
128
            ) {
129
                // no given type - simply set the json data
130 1
                $this->setProperty($object, $accessor, $jsonValue);
131
132 1
                continue;
133
            }
134
135 5
            if ($this->isObjectOfSameType($type, $jsonValue)) {
136
                $this->setProperty($object, $accessor, $jsonValue);
137
138
                continue;
139
            }
140
141 5
            if ($this->isSimpleType($type)) {
142 5
                if ($type === 'string' && \is_object($jsonValue)) {
143
                    throw new \InvalidArgumentException(
144
                        'JSON property "' . $key . '" in class "' . $strClassName . '" is an object and cannot be converted to a string'
145
                    );
146
                }
147
148 5
                if (\strpos($type, '|') !== false) {
149 3
                    foreach (\explode('|', $type) as $tmpType) {
150 3
                        if (\gettype($jsonValue) === $tmpType) {
151 3
                            \settype($jsonValue, $tmpType);
152
                        }
153
                    }
154
                } else {
155 5
                    \settype($jsonValue, $type);
156
                }
157
158 5
                $this->setProperty($object, $accessor, $jsonValue);
159
160 5
                continue;
161
            }
162
163 4 View Code Duplication
            if ($type === '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
164
                throw new \InvalidArgumentException(
165
                    'Empty type at property "' . $strClassName . '::$' . $key . '"'
166
                );
167
            }
168
169 4
            $array = null;
170 4
            $subtype = null;
171 4
            if ($this->isArrayOfType($type)) {
172 1
                $array = [];
173 1
                $subtype = \substr($type, 0, -2);
174 3
            } elseif (\substr($type, -1) == ']') {
175
                list($proptype, $subtype) = \explode('[', \substr($type, 0, -1));
176
                if ($proptype == 'array') {
177
                    $array = [];
178
                } else {
179
                    /** @noinspection PhpSillyAssignmentInspection - phpstan helper */
180
                    /** @phpstan-var class-string $proptype */
181
                    $proptype = $proptype;
0 ignored issues
show
Bug introduced by
Why assign $proptype to itself?

This checks looks for cases where a variable has been assigned to itself.

This assignement can be removed without consequences.

Loading history...
182
                    $array = self::createInstance($proptype, false, $jsonValue);
183
                }
184 3 View Code Duplication
            } elseif (\is_a($type, \ArrayObject::class, true)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
185
                /** @noinspection PhpSillyAssignmentInspection - phpstan helper */
186
                /** @phpstan-var \ArrayObject<mixed, mixed> $type */
187 3
                $type = $type;
0 ignored issues
show
Bug introduced by
Why assign $type to itself?

This checks looks for cases where a variable has been assigned to itself.

This assignement can be removed without consequences.

Loading history...
188 3
                $array = self::createInstance($type, false, $jsonValue);
189
            }
190
191 4
            if ($array !== null) {
192
                /** @noinspection NotOptimalIfConditionsInspection */
193
                if (
194 4
                    !\is_array($jsonValue)
195
                    &&
196 4
                    $this->isScalarType(\gettype($jsonValue))
197
                ) {
198
                    throw new \InvalidArgumentException(
199
                        'JSON property "' . $key . '" must be an array, ' . \gettype($jsonValue) . ' given'
200
                    );
201
                }
202
203 4
                $cleanSubtype = $this->removeNullable($subtype);
204 4
                $subtype = $this->getFullNamespace($cleanSubtype, $strNs);
205 4
                $child = $this->mapArray($jsonValue, $array, $subtype, $key);
206
            } elseif ($this->isScalarType(\gettype($jsonValue))) {
207
                // use constructor parameter if we have a class, but only a flat type (i.e. string, int)
208
                /** @noinspection PhpSillyAssignmentInspection - phpstan helper */
209
                /** @phpstan-var object $type */
210
                $type = $type;
0 ignored issues
show
Bug introduced by
Why assign $type to itself?

This checks looks for cases where a variable has been assigned to itself.

This assignement can be removed without consequences.

Loading history...
211
                $child = self::createInstance($type, true, $jsonValue);
212
            } else {
213
                /** @noinspection PhpSillyAssignmentInspection - phpstan helper */
214
                /** @phpstan-var object $type */
215
                $type = $type;
0 ignored issues
show
Bug introduced by
Why assign $type to itself?

This checks looks for cases where a variable has been assigned to itself.

This assignement can be removed without consequences.

Loading history...
216
                $child = self::createInstance($type, false, $jsonValue);
217
                $this->map($jsonValue, $child);
218
            }
219
220 4
            $this->setProperty($object, $accessor, $child);
221
        }
222
223 5
        return $object;
224
    }
225
226
    /**
227
     * Map an array
228
     *
229
     * @param array       $json       JSON array structure from json_decode()
230
     * @param mixed       $array      Array or ArrayObject that gets filled with
231
     *                                data from $json
232
     * @param string|null $class      Class name for children objects.
233
     *                                All children will get mapped onto this type.
234
     *                                Supports class names and simple types
235
     *                                like "string" and nullability "string|null".
236
     *                                Pass "null" to not convert any values
237
     * @param string      $parent_key defines the key this array belongs to
238
     *                                in order to aid debugging
239
     *
240
     * @pslam-param null|class-string $class
241
     *
242
     * @return mixed Mapped $array is returned
243
     */
244 4
    public function mapArray($json, $array, $class = null, $parent_key = '')
245
    {
246 4
        $originalClass = $class;
247 4
        foreach ($json as $key => $jsonValue) {
248 4
            $class = $this->getMappedType($originalClass, $jsonValue);
249 4
            if ($class === null) {
250 3
                $foundArrayy = false;
251 1
                if ($array instanceof \Arrayy\Arrayy && $jsonValue instanceof \stdClass) {
252
                    foreach ($array->getPhpDocPropertiesFromClass() as $typesKey => $typesTmp) {
253
                        if (
254
                            $typesKey === $key
255
                            &&
256
                            \count($typesTmp->getTypes()) === 1
257 1
                            &&
258
                            \is_subclass_of($typesTmp->getTypes()[0], \Arrayy\Arrayy::class)
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \Arrayy\Arrayy::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
259 1
                        ) {
260
                            $array[$key] = $typesTmp->getTypes()[0]::createFromObjectVars($jsonValue);
261 1
                            $foundArrayy = true;
262 1
263 1
                            break;
264
                        }
265
                    }
266
                }
267
                if ($foundArrayy === false) {
268
                    $array[$key] = $jsonValue;
269
                }
270
            } elseif ($this->isArrayOfType($class)) {
271 1
                $array[$key] = $this->mapArray(
272
                    $jsonValue,
273
                    [],
274
                    \substr($class, 0, -2)
275
                );
276
            } elseif ($this->isScalarType(\gettype($jsonValue))) {
277
                // Use constructor parameter if we have a class, but only a flat type (i.e. string, int).
278
                if ($jsonValue === null) {
279
                    $array[$key] = null;
280
                } elseif ($this->isSimpleType($class)) {
281
                    \settype($jsonValue, $class);
282
                    $array[$key] = $jsonValue;
283
                } else {
284
                    /** @noinspection PhpSillyAssignmentInspection - phpstan helper */
285
                    /** @phpstan-var class-string $class */
286
                    $class = $class;
0 ignored issues
show
Bug introduced by
Why assign $class to itself?

This checks looks for cases where a variable has been assigned to itself.

This assignement can be removed without consequences.

Loading history...
287
                    $array[$key] = self::createInstance(
288
                        $class,
289
                        true,
290
                        $jsonValue
291
                    );
292 4
                }
293
            } elseif ($this->isScalarType($class)) {
294
                throw new \InvalidArgumentException(
295
                    'JSON property "' . ($parent_key ?: '?') . '" is an array of type "' . $class . '" but contained a value of type "' . \gettype($jsonValue) . '"'
296
                );
297 4 View Code Duplication
            } elseif (\is_a($class, \ArrayObject::class, true)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
298
                /** @noinspection PhpSillyAssignmentInspection - phpstan helper */
299
                /** @phpstan-var \ArrayObject<mixed, mixed> $class */
300
                $class = $class;
0 ignored issues
show
Bug introduced by
Why assign $class to itself?

This checks looks for cases where a variable has been assigned to itself.

This assignement can be removed without consequences.

Loading history...
301
                $array[$key] = $this->mapArray(
302
                    $jsonValue,
303
                    self::createInstance($class)
304
                );
305
            } else {
306
                /** @noinspection PhpSillyAssignmentInspection - phpstan helper */
307
                /** @phpstan-var class-string $class */
308 6
                $class = $class;
0 ignored issues
show
Bug introduced by
Why assign $class to itself?

This checks looks for cases where a variable has been assigned to itself.

This assignement can be removed without consequences.

Loading history...
309
                $array[$key] = $this->map(
310
                    $jsonValue,
311 6
                    self::createInstance($class, false, $jsonValue)
312
                );
313 6
            }
314
        }
315 6
316
        return $array;
317 6
    }
318
319 3
    /**
320
     * Convert a type name to a fully namespaced type name.
321
     *
322 6
     * @param string|null $type  Type name (simple type or class name)
323
     * @param string      $strNs Base namespace that gets prepended to the type name
324 6
     *
325
     * @return string|null Fully-qualified type name with namespace
326 6
     */
327
    private function getFullNamespace($type, $strNs)
328 6
    {
329
        if (
330
            $type === null
331
            ||
332
            $type === ''
333
            ||
334
            $type[0] == '\\'
335
            ||
336
            $strNs == ''
337
        ) {
338
            return $type;
339
        }
340
341
        list($first) = \explode('[', $type, 2);
342
        if (
343
            $first === 'mixed'
344
            ||
345
            $this->isSimpleType($first)
346
        ) {
347 6
            return $type;
348
        }
349
350 6
        //create a full qualified namespace
351 6
        return '\\' . $strNs . '\\' . $type;
352
    }
353
354
    /**
355 6
     * Try to find out if a property exists in a given class.
356
     * Checks property first, falls back to setter method.
357 6
     *
358 6
     * @param \ReflectionClass<object> $rc   Reflection class to check
0 ignored issues
show
Documentation introduced by
The doc-type \ReflectionClass<object> could not be parsed: Expected "|" or "end of type", but got "<" at position 16. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
359 3
     * @param string                   $name Property name
360
     *
361
     * @return array First value: if the property exists
362 6
     *               Second value: the accessor to use (
363 6
     *               Array-Key-String or ReflectionMethod or ReflectionProperty, or null)
364 1
     *               Third value: type of the property
365
     */
366
    private function inspectProperty(\ReflectionClass $rc, $name): array
367 5
    {
368 5
        // now try to set the property directly, we have to look it up in the class hierarchy
369 5
        $class = $rc;
370
        $accessor = null;
371
372
        /** @var \Arrayy\Arrayy[] $ARRAYY_CACHE */
373
        /** @phpstan-var array<string, \Arrayy\Arrayy<mixed, mixed>> $ARRAYY_CACHE */
374
        static $ARRAYY_CACHE = [];
375 1
376
        if (\is_subclass_of($class->name, \Arrayy\Arrayy::class)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \Arrayy\Arrayy::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
377
            if (!isset($ARRAYY_CACHE[$class->name])) {
378 1
                $ARRAYY_CACHE[$class->name] = new $class->name();
379
            }
380 1
381
            $tmpProps = $ARRAYY_CACHE[$class->name]->getPhpDocPropertiesFromClass();
382 1
            if ($tmpProps === []) {
383 1
                return [true, $name, 'mixed'];
384
            }
385
386 1
            foreach ($tmpProps as $tmpName => $tmpProp) {
387
                if ($tmpName === $name) {
388
                    return [true, $name, \implode('|', $tmpProp->getTypes())];
389
                }
390
            }
391 1
        }
392
393
        do {
394
            if ($class->hasProperty($name)) {
395
                $accessor = $class->getProperty($name);
396
            }
397
        } while ($accessor === null && $class = $class->getParentClass());
398
399
        if ($accessor === null) {
400
            // case-insensitive property matching
401
            foreach ($rc->getProperties() as $p) {
402
                if ((\strcasecmp($p->name, $name) === 0)) {
403
                    $accessor = $p;
404
405
                    break;
406
                }
407
            }
408
        }
409
410
        if ($accessor !== null) {
411
            if ($accessor->isPublic()) {
412
                $docblock = $accessor->getDocComment();
413
                if ($docblock === false) {
414
                    return [true, null, null];
415 1
                }
416
417
                $annotations = self::parseAnnotations($docblock);
418
419
                if (!isset($annotations['var'][0])) {
420
                    return [true, $accessor, null];
421
                }
422
423
                // support "@var type description"
424
                list($type) = \explode(' ', $annotations['var'][0]);
425
426
                return [true, $accessor, $type];
427
            }
428
429
            // no private property
430
            return [true, null, null];
431
        }
432
433
        // no setter, no property
434
        return [false, null, null];
435
    }
436
437
    /**
438
     * Copied from PHPUnit 3.7.29, Util/Test.php
439
     *
440
     * @param string $docblock Full method docblock
441
     *
442
     * @return array
443
     */
444
    private static function parseAnnotations($docblock): array
445
    {
446
        // init
447
        $annotations = [];
448
449
        // Strip away the docblock header and footer
450
        // to ease parsing of one line annotations
451
        $docblock = \substr($docblock, 3, -2);
452
453
        $re = '/@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?$/m';
454
        if (\preg_match_all($re, $docblock, $matches)) {
455
            $numMatches = \count($matches[0]);
456
457
            for ($i = 0; $i < $numMatches; ++$i) {
458
                $annotations[$matches['name'][$i]][] = $matches['value'][$i];
459
            }
460
        }
461
462
        return $annotations;
463
    }
464
465
    /**
466
     * Removes - and _ and makes the next letter uppercase
467
     *
468
     * @param string $name Property name
469
     *
470
     * @return string CamelCasedVariableName
471 6
     */
472
    private function getCamelCaseName($name): string
473 6
    {
474 6
        return \str_replace(
475
            ' ',
476 6
            '',
477
            \ucwords(\str_replace(['_', '-'], ' ', $name))
478
        );
479
    }
480 6
481
    /**
482
     * Since hyphens cannot be used in variables we have to uppercase them.
483
     *
484
     * Technically you may use them, but they are awkward to access.
485
     *
486
     * @param string $name Property name
487
     *
488
     * @return string Name without hyphen
489
     */
490
    private function getSafeName($name): string
491
    {
492
        $convertHyphens = \strpos($name, '-') !== false;
493
        $convertSnake = \strpos($name, '_') !== false;
494
495 6
        if ($convertHyphens || $convertSnake) {
496
            $name = $this->getCamelCaseName($name);
497
        }
498
499
        return $name;
500 6
    }
501 6
502
    /**
503
     * Set a property on a given object to a given value.
504
     *
505
     * Checks if the setter or the property are public are made before
506
     * calling this method.
507
     *
508 6
     * @param \Arrayy\Arrayy|object                        $object   Object to set property on
509
     * @param \ReflectionMethod|\ReflectionProperty|string $accessor Array-Key-String or ReflectionMethod or ReflectionProperty
510
     * @param mixed                                        $value    Value of property
511
     *
512
     * @return void
513
     */
514
    private function setProperty(
515
        $object,
516
        $accessor,
517
        $value
518
    ) {
519
        if (\is_string($accessor) && $object instanceof \Arrayy\Arrayy) {
520
            $object[$accessor] = $value;
521 6
        } elseif ($accessor instanceof \ReflectionProperty) {
522
            $accessor->setValue($object, $value);
523 6
        } elseif ($accessor instanceof \ReflectionMethod) {
524
            // setter method
525
            $accessor->invoke($object, $value);
526 6
        }
527
    }
528 6
529
    /**
530 6
     * Get the mapped class/type name for this class.
531
     * Returns the incoming classname if not mapped.
532 6
     *
533
     * @param string|null $type      Type name to map
534
     * @param mixed       $jsonValue Constructor parameter (the json value)
535
     *
536 6
     * @return string|null The mapped type/class name
537
     *
538
     * @phpstan-return class-string|string|null
539 6
     */
540
    private function getMappedType($type, $jsonValue = null)
541
    {
542
        if (isset($this->classMap[$type])) {
543
            $target = $this->classMap[$type];
544
        } elseif (
545
            \is_string($type)
546
            &&
547 6
            $type !== ''
548
            &&
549
            $type[0] == '\\'
550
            &&
551
            isset($this->classMap[\substr($type, 1)])
552
        ) {
553
            $target = $this->classMap[\substr($type, 1)];
554
        } else {
555
            $target = null;
556
        }
557
558
        if ($target) {
559 5
            if (\is_callable($target)) {
560
                $type = $target($type, $jsonValue);
561 5
            } else {
562 3
                $type = $target;
563 3
            }
564 3
        }
565
566
        return $type;
567
    }
568
569
    /**
570 5
     * Checks if the given type is a "simple type"
571 5
     *
572 5
     * @param string $type type name from gettype()
573 4
     *
574 5
     * @return bool True if it is a simple PHP type
575
     *
576
     * @see isScalarType()
577
     */
578
    private function isSimpleType($type): bool
579
    {
580
        if (\strpos($type, '|') !== false) {
581
            foreach (\explode('|', $type) as $tmpType) {
582
                if ($this->isSimpleType($tmpType)) {
583
                    return true;
584
                }
585 5
            }
586
        }
587 5
588 5
        /** @noinspection InArrayCanBeUsedInspection */
589
        return $type == 'string'
590
               || $type == 'boolean' || $type == 'bool'
591 3
               || $type == 'integer' || $type == 'int' || $type == 'int'
592
               || $type == 'double' || $type == 'float'
593
               || $type == 'array' || $type == 'object';
594
    }
595
596
    /**
597
     * Checks if the object is of this type or has this type as one of its parents
598
     *
599
     * @param string $type  class name of type being required
600
     * @param mixed  $value Some PHP value to be tested
601
     *
602
     * @return bool True if $object has type of $type
603
     */
604 4
    private function isObjectOfSameType($type, $value): bool
605
    {
606
        if (\is_object($value) === false) {
607 4
            return false;
608 4
        }
609 3
610 3
        return \is_a($value, $type);
611 4
    }
612
613
    /**
614
     * Checks if the given type is a type that is not nested
615
     * (simple type except array and object)
616
     *
617
     * @param string $type type name from gettype()
618
     *
619
     * @return bool True if it is a non-nested PHP type
620
     *
621
     * @see isSimpleType()
622 4
     */
623
    private function isScalarType($type): bool
624 4
    {
625
        /** @noinspection InArrayCanBeUsedInspection */
626
        return $type == 'NULL'
627
               || $type == 'string'
628
               || $type == 'boolean' || $type == 'bool'
629
               || $type == 'integer' || $type == 'int'
630
               || $type == 'double' || $type == 'float';
631
    }
632
633
    /**
634 6
     * Returns true if type is an array of elements
635
     * (bracket notation)
636 6
     *
637
     * @param string $strType type to be matched
638
     *
639
     * @return bool
640
     */
641
    private function isArrayOfType($strType): bool
642
    {
643
        return \substr($strType, -2) === '[]';
644
    }
645
646 4
    /**
647
     * Checks if the given type is nullable
648 4
     *
649 3
     * @param string $type type name from the phpdoc param
650
     *
651
     * @return bool True if it is nullable
652 4
     */
653 4
    private function isNullable($type): bool
654 4
    {
655 4
        return \stripos('|' . $type . '|', '|null|') !== false;
656
    }
657
658
    /**
659
     * Remove the 'null' section of a type
660
     *
661
     * @param string|null $type type name from the phpdoc param
662
     *
663
     * @return string|null The new type value
664
     */
665
    private function removeNullable($type)
666
    {
667
        if ($type === null) {
668
            return null;
669
        }
670
671
        return \substr(
672
            \str_ireplace('|null|', '|', '|' . $type . '|'),
673
            1,
674
            -1
675 4
        );
676
    }
677
678
    /**
679
     * Create a new object of the given type.
680 4
     *
681
     * This method exists to be overwritten in child classes,
682
     * so you can do dependency injection or so.
683
     *
684 4
     * @param object|string $class        Class name to instantiate
685 4
     * @param bool          $useParameter Pass $parameter to the constructor or not
686
     * @param mixed         $jsonValue    Constructor parameter (the json value)
687 4
     *
688
     * @phpstan-param object|class-string $class
689 4
     *
690
     * @return object Freshly created object
691
     *
692
     * @internal
693
     */
694 4
    private static function createInstance(
695
        $class,
696
        $useParameter = false,
697
        $jsonValue = null
698
    ) {
699
        if ($useParameter) {
700
            return new $class($jsonValue);
701
        }
702
703
        $reflectClass = new \ReflectionClass($class);
704
        $constructor = $reflectClass->getConstructor();
705
        if (
706
            $constructor === null
707
            ||
708
            $constructor->getNumberOfRequiredParameters() > 0
709
        ) {
710
            return $reflectClass->newInstanceWithoutConstructor();
711
        }
712
713
        return $reflectClass->newInstance();
714
    }
715
}
716