Completed
Push — master ( f8fd3e...f85948 )
by Lars
01:47 queued 11s
created

Json::getFullNamespace()   B

Complexity

Conditions 7
Paths 3

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 7.0368

Importance

Changes 0
Metric Value
cc 7
nc 3
nop 2
dl 0
loc 26
ccs 10
cts 11
cp 0.9091
crap 7.0368
rs 8.5706
c 0
b 0
f 0
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
                $array[$key] = $jsonValue;
251 1
            } elseif ($this->isArrayOfType($class)) {
252
                $array[$key] = $this->mapArray(
253
                    $jsonValue,
254
                    [],
255
                    \substr($class, 0, -2)
256
                );
257 1
            } elseif ($this->isScalarType(\gettype($jsonValue))) {
258
                // Use constructor parameter if we have a class, but only a flat type (i.e. string, int).
259 1
                if ($jsonValue === null) {
260
                    $array[$key] = null;
261 1
                } elseif ($this->isSimpleType($class)) {
262 1
                    \settype($jsonValue, $class);
263 1
                    $array[$key] = $jsonValue;
264
                } else {
265
                    /** @noinspection PhpSillyAssignmentInspection - phpstan helper */
266
                    /** @phpstan-var class-string $class */
267
                    $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...
268
                    $array[$key] = self::createInstance(
269
                        $class,
270
                        true,
271 1
                        $jsonValue
272
                    );
273
                }
274
            } elseif ($this->isScalarType($class)) {
275
                throw new \InvalidArgumentException(
276
                    'JSON property "' . ($parent_key ?: '?') . '" is an array of type "' . $class . '" but contained a value of type "' . \gettype($jsonValue) . '"'
277
                );
278 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...
279
                /** @noinspection PhpSillyAssignmentInspection - phpstan helper */
280
                /** @phpstan-var \ArrayObject<mixed, mixed> $class */
281
                $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...
282
                $array[$key] = $this->mapArray(
283
                    $jsonValue,
284
                    self::createInstance($class)
285
                );
286
            } else {
287
                /** @noinspection PhpSillyAssignmentInspection - phpstan helper */
288
                /** @phpstan-var class-string $class */
289
                $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...
290
                $array[$key] = $this->map(
291
                    $jsonValue,
292 4
                    self::createInstance($class, false, $jsonValue)
293
                );
294
            }
295
        }
296
297 4
        return $array;
298
    }
299
300
    /**
301
     * Convert a type name to a fully namespaced type name.
302
     *
303
     * @param string|null $type  Type name (simple type or class name)
304
     * @param string      $strNs Base namespace that gets prepended to the type name
305
     *
306
     * @return string|null Fully-qualified type name with namespace
307
     */
308 6
    private function getFullNamespace($type, $strNs)
309
    {
310
        if (
311 6
            $type === null
312
            ||
313 6
            $type === ''
314
            ||
315 6
            $type[0] == '\\'
316
            ||
317 6
            $strNs == ''
318
        ) {
319 3
            return $type;
320
        }
321
322 6
        list($first) = \explode('[', $type, 2);
323
        if (
324 6
            $first === 'mixed'
325
            ||
326 6
            $this->isSimpleType($first)
327
        ) {
328 6
            return $type;
329
        }
330
331
        //create a full qualified namespace
332
        return '\\' . $strNs . '\\' . $type;
333
    }
334
335
    /**
336
     * Try to find out if a property exists in a given class.
337
     * Checks property first, falls back to setter method.
338
     *
339
     * @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...
340
     * @param string                   $name Property name
341
     *
342
     * @return array First value: if the property exists
343
     *               Second value: the accessor to use (
344
     *               Array-Key-String or ReflectionMethod or ReflectionProperty, or null)
345
     *               Third value: type of the property
346
     */
347 6
    private function inspectProperty(\ReflectionClass $rc, $name): array
348
    {
349
        // now try to set the property directly, we have to look it up in the class hierarchy
350 6
        $class = $rc;
351 6
        $accessor = null;
352
353
        /** @var \Arrayy\Arrayy[] $ARRAYY_CACHE */
354
        /** @phpstan-var array<string, \Arrayy\Arrayy<mixed, mixed>> $ARRAYY_CACHE */
355 6
        static $ARRAYY_CACHE = [];
356
357 6
        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...
358 6
            if (!isset($ARRAYY_CACHE[$class->name])) {
359 3
                $ARRAYY_CACHE[$class->name] = new $class->name();
360
            }
361
362 6
            $tmpProps = $ARRAYY_CACHE[$class->name]->getPhpDocPropertiesFromClass();
363 6
            if ($tmpProps === []) {
364 1
                return [true, $name, 'mixed'];
365
            }
366
367 5
            foreach ($tmpProps as $tmpName => $tmpProp) {
368 5
                if ($tmpName === $name) {
369 5
                    return [true, $name, \implode('|', $tmpProp->getTypes())];
370
                }
371
            }
372
        }
373
374
        do {
375 1
            if ($class->hasProperty($name)) {
376
                $accessor = $class->getProperty($name);
377
            }
378 1
        } while ($accessor === null && $class = $class->getParentClass());
379
380 1
        if ($accessor === null) {
381
            // case-insensitive property matching
382 1
            foreach ($rc->getProperties() as $p) {
383 1
                if ((\strcasecmp($p->name, $name) === 0)) {
384
                    $accessor = $p;
385
386 1
                    break;
387
                }
388
            }
389
        }
390
391 1
        if ($accessor !== null) {
392
            if ($accessor->isPublic()) {
393
                $docblock = $accessor->getDocComment();
394
                if ($docblock === false) {
395
                    return [true, null, null];
396
                }
397
398
                $annotations = self::parseAnnotations($docblock);
399
400
                if (!isset($annotations['var'][0])) {
401
                    return [true, $accessor, null];
402
                }
403
404
                // support "@var type description"
405
                list($type) = \explode(' ', $annotations['var'][0]);
406
407
                return [true, $accessor, $type];
408
            }
409
410
            // no private property
411
            return [true, null, null];
412
        }
413
414
        // no setter, no property
415 1
        return [false, null, null];
416
    }
417
418
    /**
419
     * Copied from PHPUnit 3.7.29, Util/Test.php
420
     *
421
     * @param string $docblock Full method docblock
422
     *
423
     * @return array
424
     */
425
    private static function parseAnnotations($docblock): array
426
    {
427
        // init
428
        $annotations = [];
429
430
        // Strip away the docblock header and footer
431
        // to ease parsing of one line annotations
432
        $docblock = \substr($docblock, 3, -2);
433
434
        $re = '/@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?$/m';
435
        if (\preg_match_all($re, $docblock, $matches)) {
436
            $numMatches = \count($matches[0]);
437
438
            for ($i = 0; $i < $numMatches; ++$i) {
439
                $annotations[$matches['name'][$i]][] = $matches['value'][$i];
440
            }
441
        }
442
443
        return $annotations;
444
    }
445
446
    /**
447
     * Removes - and _ and makes the next letter uppercase
448
     *
449
     * @param string $name Property name
450
     *
451
     * @return string CamelCasedVariableName
452
     */
453
    private function getCamelCaseName($name): string
454
    {
455
        return \str_replace(
456
            ' ',
457
            '',
458
            \ucwords(\str_replace(['_', '-'], ' ', $name))
459
        );
460
    }
461
462
    /**
463
     * Since hyphens cannot be used in variables we have to uppercase them.
464
     *
465
     * Technically you may use them, but they are awkward to access.
466
     *
467
     * @param string $name Property name
468
     *
469
     * @return string Name without hyphen
470
     */
471 6
    private function getSafeName($name): string
472
    {
473 6
        $convertHyphens = \strpos($name, '-') !== false;
474 6
        $convertSnake = \strpos($name, '_') !== false;
475
476 6
        if ($convertHyphens || $convertSnake) {
477
            $name = $this->getCamelCaseName($name);
478
        }
479
480 6
        return $name;
481
    }
482
483
    /**
484
     * Set a property on a given object to a given value.
485
     *
486
     * Checks if the setter or the property are public are made before
487
     * calling this method.
488
     *
489
     * @param \Arrayy\Arrayy|object                        $object   Object to set property on
490
     * @param \ReflectionMethod|\ReflectionProperty|string $accessor Array-Key-String or ReflectionMethod or ReflectionProperty
491
     * @param mixed                                        $value    Value of property
492
     *
493
     * @return void
494
     */
495 6
    private function setProperty(
496
        $object,
497
        $accessor,
498
        $value
499
    ) {
500 6
        if (\is_string($accessor) && $object instanceof \Arrayy\Arrayy) {
501 6
            $object[$accessor] = $value;
502
        } elseif ($accessor instanceof \ReflectionProperty) {
503
            $accessor->setValue($object, $value);
504
        } elseif ($accessor instanceof \ReflectionMethod) {
505
            // setter method
506
            $accessor->invoke($object, $value);
507
        }
508 6
    }
509
510
    /**
511
     * Get the mapped class/type name for this class.
512
     * Returns the incoming classname if not mapped.
513
     *
514
     * @param string|null $type      Type name to map
515
     * @param mixed       $jsonValue Constructor parameter (the json value)
516
     *
517
     * @return string|null The mapped type/class name
518
     *
519
     * @phpstan-return class-string|string|null
520
     */
521 6
    private function getMappedType($type, $jsonValue = null)
522
    {
523 6
        if (isset($this->classMap[$type])) {
524
            $target = $this->classMap[$type];
525
        } elseif (
526 6
            \is_string($type)
527
            &&
528 6
            $type !== ''
529
            &&
530 6
            $type[0] == '\\'
531
            &&
532 6
            isset($this->classMap[\substr($type, 1)])
533
        ) {
534
            $target = $this->classMap[\substr($type, 1)];
535
        } else {
536 6
            $target = null;
537
        }
538
539 6
        if ($target) {
540
            if (\is_callable($target)) {
541
                $type = $target($type, $jsonValue);
542
            } else {
543
                $type = $target;
544
            }
545
        }
546
547 6
        return $type;
548
    }
549
550
    /**
551
     * Checks if the given type is a "simple type"
552
     *
553
     * @param string $type type name from gettype()
554
     *
555
     * @return bool True if it is a simple PHP type
556
     *
557
     * @see isScalarType()
558
     */
559 5
    private function isSimpleType($type): bool
560
    {
561 5
        if (\strpos($type, '|') !== false) {
562 3
            foreach (\explode('|', $type) as $tmpType) {
563 3
                if ($this->isSimpleType($tmpType)) {
564 3
                    return true;
565
                }
566
            }
567
        }
568
569
        /** @noinspection InArrayCanBeUsedInspection */
570 5
        return $type == 'string'
571 5
               || $type == 'boolean' || $type == 'bool'
572 5
               || $type == 'integer' || $type == 'int' || $type == 'int'
573 4
               || $type == 'double' || $type == 'float'
574 5
               || $type == 'array' || $type == 'object';
575
    }
576
577
    /**
578
     * Checks if the object is of this type or has this type as one of its parents
579
     *
580
     * @param string $type  class name of type being required
581
     * @param mixed  $value Some PHP value to be tested
582
     *
583
     * @return bool True if $object has type of $type
584
     */
585 5
    private function isObjectOfSameType($type, $value): bool
586
    {
587 5
        if (\is_object($value) === false) {
588 5
            return false;
589
        }
590
591 3
        return \is_a($value, $type);
592
    }
593
594
    /**
595
     * Checks if the given type is a type that is not nested
596
     * (simple type except array and object)
597
     *
598
     * @param string $type type name from gettype()
599
     *
600
     * @return bool True if it is a non-nested PHP type
601
     *
602
     * @see isSimpleType()
603
     */
604 4
    private function isScalarType($type): bool
605
    {
606
        /** @noinspection InArrayCanBeUsedInspection */
607 4
        return $type == 'NULL'
608 4
               || $type == 'string'
609 3
               || $type == 'boolean' || $type == 'bool'
610 3
               || $type == 'integer' || $type == 'int'
611 4
               || $type == 'double' || $type == 'float';
612
    }
613
614
    /**
615
     * Returns true if type is an array of elements
616
     * (bracket notation)
617
     *
618
     * @param string $strType type to be matched
619
     *
620
     * @return bool
621
     */
622 4
    private function isArrayOfType($strType): bool
623
    {
624 4
        return \substr($strType, -2) === '[]';
625
    }
626
627
    /**
628
     * Checks if the given type is nullable
629
     *
630
     * @param string $type type name from the phpdoc param
631
     *
632
     * @return bool True if it is nullable
633
     */
634 6
    private function isNullable($type): bool
635
    {
636 6
        return \stripos('|' . $type . '|', '|null|') !== false;
637
    }
638
639
    /**
640
     * Remove the 'null' section of a type
641
     *
642
     * @param string|null $type type name from the phpdoc param
643
     *
644
     * @return string|null The new type value
645
     */
646 4
    private function removeNullable($type)
647
    {
648 4
        if ($type === null) {
649 3
            return null;
650
        }
651
652 4
        return \substr(
653 4
            \str_ireplace('|null|', '|', '|' . $type . '|'),
654 4
            1,
655 4
            -1
656
        );
657
    }
658
659
    /**
660
     * Create a new object of the given type.
661
     *
662
     * This method exists to be overwritten in child classes,
663
     * so you can do dependency injection or so.
664
     *
665
     * @param object|string $class        Class name to instantiate
666
     * @param bool          $useParameter Pass $parameter to the constructor or not
667
     * @param mixed         $jsonValue    Constructor parameter (the json value)
668
     *
669
     * @phpstan-param object|class-string $class
670
     *
671
     * @return object Freshly created object
672
     *
673
     * @internal
674
     */
675 4
    private static function createInstance(
676
        $class,
677
        $useParameter = false,
678
        $jsonValue = null
679
    ) {
680 4
        if ($useParameter) {
681
            return new $class($jsonValue);
682
        }
683
684 4
        $reflectClass = new \ReflectionClass($class);
685 4
        $constructor = $reflectClass->getConstructor();
686
        if (
687 4
            $constructor === null
688
            ||
689 4
            $constructor->getNumberOfRequiredParameters() > 0
690
        ) {
691
            return $reflectClass->newInstanceWithoutConstructor();
692
        }
693
694 4
        return $reflectClass->newInstance();
695
    }
696
}
697