• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

voku / Arrayy / 4403873933

pending completion
4403873933

push

github

GitHub
Merge pull request #128 from voku/renovate/actions-cache-3.x

2479 of 2778 relevant lines covered (89.24%)

33.39 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.96
/src/Mapper/Json.php
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 object|iterable $json
52
     *                                                      <p>JSON object structure from json_decode()</p>
53
     * @param object|string $object
54
     *                                                      <p>Object to map $json data into</p>
55
     *
56
     * @return mixed
57
     *               <p>mapped object is returned</p>
58
     *
59
     * @see mapArray()
60
     *
61
     * @template TObject
62
     * @phpstan-param TObject|class-string<TObject> $object
63
     *                                                      <p>Object to map $json data into.</p>
64
     * @phpstan-return TObject
65
     *
66
     */
67
    public function map($json, $object)
68
    {
69
        if (\is_string($object) && \class_exists($object)) {
8✔
70
            $object = self::createInstance($object);
5✔
71
        }
72

73
        if (!\is_object($object)) {
8✔
74
            throw new \InvalidArgumentException(
×
75
                'JsonMapper::map() requires second argument to be an object, ' . \gettype($object) . ' given.'
×
76
            );
×
77
        }
78

79
        $strClassName = \get_class($object);
8✔
80
        $rc = new \ReflectionClass($object);
8✔
81
        $strNs = $rc->getNamespaceName();
8✔
82
        foreach ($json as $key => $jsonValue) {
8✔
83
            $key = $this->getSafeName($key);
8✔
84

85
            // Store the property inspection results, so we don't have to do it
86
            // again for subsequent objects of the same type.
87
            if (!isset($this->arInspectedClasses[$strClassName][$key])) {
8✔
88
                $this->arInspectedClasses[$strClassName][$key] = $this->inspectProperty($rc, $key);
8✔
89
            }
90

91
            list(
8✔
92
                $hasProperty,
8✔
93
                $accessor,
8✔
94
                $type
8✔
95
            ) = $this->arInspectedClasses[$strClassName][$key];
8✔
96

97
            if (!$hasProperty) {
8✔
98
                if (\is_callable($this->undefinedPropertyHandler)) {
1✔
99
                    \call_user_func(
1✔
100
                        $this->undefinedPropertyHandler,
1✔
101
                        $object,
1✔
102
                        $key,
1✔
103
                        $jsonValue
1✔
104
                    );
1✔
105
                }
106

107
                continue;
×
108
            }
109

110
            if ($accessor === null) {
8✔
111
                continue;
×
112
            }
113

114
            if ($this->isNullable($type)) {
8✔
115
                if ($jsonValue === null) {
5✔
116
                    $this->setProperty($object, $accessor, null);
1✔
117

118
                    continue;
1✔
119
                }
120

121
                $type = $this->removeNullable($type);
4✔
122
            } elseif ($jsonValue === null) {
8✔
123
                throw new \InvalidArgumentException(
×
124
                    'JSON property "' . $key . '" in class "' . $strClassName . '" must not be NULL'
×
125
                );
×
126
            }
127

128
            $type = $this->getFullNamespace($type, $strNs);
8✔
129
            $type = $this->getMappedType($type, $jsonValue);
8✔
130

131
            if (
132
                $type === null
8✔
133
                ||
134
                $type === 'mixed'
8✔
135
            ) {
136
                // no given type - simply set the json data
137
                $this->setProperty($object, $accessor, $jsonValue);
1✔
138

139
                continue;
1✔
140
            }
141

142
            if ($this->isObjectOfSameType($type, $jsonValue)) {
7✔
143
                $this->setProperty($object, $accessor, $jsonValue);
×
144

145
                continue;
×
146
            }
147

148
            if ($this->isSimpleType($type)) {
7✔
149
                if ($type === 'string' && \is_object($jsonValue)) {
7✔
150
                    throw new \InvalidArgumentException(
×
151
                        'JSON property "' . $key . '" in class "' . $strClassName . '" is an object and cannot be converted to a string'
×
152
                    );
×
153
                }
154

155
                if (\strpos($type, '|') !== false) {
7✔
156
                    foreach (\explode('|', $type) as $tmpType) {
4✔
157
                        if (\gettype($jsonValue) === $tmpType) {
4✔
158
                            \settype($jsonValue, $tmpType);
4✔
159
                        }
160
                    }
161
                } else {
162
                    \settype($jsonValue, $type);
7✔
163
                }
164

165
                $this->setProperty($object, $accessor, $jsonValue);
7✔
166

167
                continue;
7✔
168
            }
169

170
            if ($type === '') {
6✔
171
                throw new \InvalidArgumentException(
×
172
                    'Empty type at property "' . $strClassName . '::$' . $key . '"'
×
173
                );
×
174
            }
175

176
            $array = null;
6✔
177
            $subtype = null;
6✔
178
            if ($this->isArrayOfType($type)) {
6✔
179
                $array = [];
1✔
180
                $subtype = \substr($type, 0, -2);
1✔
181
            } elseif (\substr($type, -1) == ']') {
5✔
182
                list($proptype, $subtype) = \explode('[', \substr($type, 0, -1));
×
183
                if ($proptype == 'array') {
×
184
                    $array = [];
×
185
                } else {
186
                    /** @noinspection PhpSillyAssignmentInspection - phpstan helper */
187
                    /** @phpstan-var class-string $proptype */
188
                    $proptype = $proptype;
×
189
                    $array = self::createInstance($proptype, false, $jsonValue);
×
190
                }
191
            } elseif (\is_a($type, \ArrayObject::class, true)) {
5✔
192
                /** @noinspection PhpSillyAssignmentInspection - phpstan helper */
193
                /** @phpstan-var \ArrayObject<array-key, mixed> $type */
194
                $type = $type;
5✔
195
                $array = self::createInstance($type, false, $jsonValue);
5✔
196
            }
197

198
            if ($array !== null) {
6✔
199
                /** @noinspection NotOptimalIfConditionsInspection */
200
                if (
201
                    !\is_array($jsonValue)
6✔
202
                    &&
203
                    $this->isScalarType(\gettype($jsonValue))
6✔
204
                ) {
205
                    throw new \InvalidArgumentException(
×
206
                        'JSON property "' . $key . '" must be an array, ' . \gettype($jsonValue) . ' given'
×
207
                    );
×
208
                }
209

210
                $cleanSubtype = $this->removeNullable($subtype);
6✔
211
                $subtype = $this->getFullNamespace($cleanSubtype, $strNs);
6✔
212
                $child = $this->mapArray($jsonValue, $array, $subtype, $key);
6✔
213
            } elseif ($this->isScalarType(\gettype($jsonValue))) {
×
214
                // use constructor parameter if we have a class, but only a flat type (i.e. string, int)
215
                /** @noinspection PhpSillyAssignmentInspection - phpstan helper */
216
                /** @phpstan-var object $type */
217
                $type = $type;
×
218
                $child = self::createInstance($type, true, $jsonValue);
×
219
            } else {
220
                /** @noinspection PhpSillyAssignmentInspection - phpstan helper */
221
                /** @phpstan-var object $type */
222
                $type = $type;
×
223
                $child = self::createInstance($type, false, $jsonValue);
×
224
                $this->map($jsonValue, $child);
×
225
            }
226

227
            $this->setProperty($object, $accessor, $child);
6✔
228
        }
229

230
        /** @noinspection PhpSillyAssignmentInspection */
231
        /** @phpstan-var TObject $object */
232
        $object = $object;
7✔
233

234
        return $object;
7✔
235
    }
236

237
    /**
238
     * Map an array
239
     *
240
     * @param array       $json       JSON array structure from json_decode()
241
     * @param mixed       $array      Array or ArrayObject that gets filled with
242
     *                                data from $json
243
     * @param string|null $class      Class name for children objects.
244
     *                                All children will get mapped onto this type.
245
     *                                Supports class names and simple types
246
     *                                like "string" and nullability "string|null".
247
     *                                Pass "null" to not convert any values
248
     * @param string      $parent_key defines the key this array belongs to
249
     *                                in order to aid debugging
250
     *
251
     * @pslam-param null|class-string $class
252
     *
253
     * @return mixed Mapped $array is returned
254
     */
255
    public function mapArray($json, $array, $class = null, $parent_key = '')
256
    {
257
        $originalClass = $class;
6✔
258
        foreach ($json as $key => $jsonValue) {
6✔
259
            $class = $this->getMappedType($originalClass, $jsonValue);
6✔
260
            if ($class === null) {
6✔
261
                $foundArrayy = false;
5✔
262

263
                if ($array instanceof \Arrayy\Arrayy && $jsonValue instanceof \stdClass) {
5✔
264
                    foreach ($array->getPhpDocPropertiesFromClass() as $typesKey => $typesTmp) {
1✔
265
                        if (
266
                            (
267
                                $typesKey === $key
1✔
268
                                ||
1✔
269
                                $typesKey === \Arrayy\Arrayy::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES
1✔
270
                            )
271
                            &&
272
                            \count($typesTmp->getTypes()) === 1
1✔
273
                            &&
274
                            \is_subclass_of($typesTmp->getTypes()[0], \Arrayy\Arrayy::class)
1✔
275
                        ) {
276
                            $array[$key] = $typesTmp->getTypes()[0]::createFromObjectVars($jsonValue);
×
277
                            $foundArrayy = true;
×
278

279
                            break;
×
280
                        }
281
                    }
282
                }
283
                if ($foundArrayy === false) {
5✔
284
                    if ($array instanceof \Arrayy\Arrayy && $jsonValue instanceof \stdClass) {
5✔
285
                        foreach ($array->getPhpDocPropertiesFromClass() as $typesKey => $typesTmp) {
1✔
286
                            if (
287
                                (
288
                                    $typesKey === $key
1✔
289
                                    ||
1✔
290
                                    $typesKey === \Arrayy\Arrayy::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES
1✔
291
                                )
292
                                &&
293
                                \count($typesTmp->getTypes()) === 1
1✔
294
                            ) {
295
                                $array[$key] = $this->map($jsonValue, $typesTmp->getTypes()[0]);
1✔
296
                                $foundArrayy = true;
1✔
297

298
                                break;
1✔
299
                            }
300
                        }
301
                    }
302
                    if ($foundArrayy === false) {
5✔
303
                        $array[$key] = $jsonValue;
5✔
304
                    }
305
                }
306
            } elseif ($this->isArrayOfType($class)) {
1✔
307
                $array[$key] = $this->mapArray(
×
308
                    $jsonValue,
×
309
                    [],
×
310
                    \substr($class, 0, -2)
×
311
                );
×
312
            } elseif ($this->isScalarType(\gettype($jsonValue))) {
1✔
313
                // Use constructor parameter if we have a class, but only a flat type (i.e. string, int).
314
                if ($jsonValue === null) {
1✔
315
                    $array[$key] = null;
×
316
                } elseif ($this->isSimpleType($class)) {
1✔
317
                    \settype($jsonValue, $class);
1✔
318
                    $array[$key] = $jsonValue;
1✔
319
                } else {
320
                    /** @noinspection PhpSillyAssignmentInspection - phpstan helper */
321
                    /** @phpstan-var class-string $class */
322
                    $class = $class;
×
323
                    $array[$key] = self::createInstance(
1✔
324
                        $class,
1✔
325
                        true,
1✔
326
                        $jsonValue
1✔
327
                    );
1✔
328
                }
329
            } elseif ($this->isScalarType($class)) {
×
330
                throw new \InvalidArgumentException(
×
331
                    'JSON property "' . ($parent_key ?: '?') . '" is an array of type "' . $class . '" but contained a value of type "' . \gettype($jsonValue) . '"'
×
332
                );
×
333
            } elseif (\is_a($class, \ArrayObject::class, true)) {
×
334
                /** @noinspection PhpSillyAssignmentInspection - phpstan helper */
335
                /** @phpstan-var \ArrayObject<array-key, mixed> $class */
336
                $class = $class;
×
337
                $array[$key] = $this->mapArray(
×
338
                    $jsonValue,
×
339
                    self::createInstance($class)
×
340
                );
×
341
            } else {
342
                /** @noinspection PhpSillyAssignmentInspection - phpstan helper */
343
                /** @phpstan-var class-string $class */
344
                $class = $class;
×
345
                $array[$key] = $this->map(
×
346
                    $jsonValue,
×
347
                    self::createInstance($class, false, $jsonValue)
×
348
                );
×
349
            }
350
        }
351

352
        return $array;
6✔
353
    }
354

355
    /**
356
     * Convert a type name to a fully namespaced type name.
357
     *
358
     * @param string|null $type  Type name (simple type or class name)
359
     * @param string      $strNs Base namespace that gets prepended to the type name
360
     *
361
     * @return string|null Fully-qualified type name with namespace
362
     */
363
    private function getFullNamespace($type, $strNs)
364
    {
365
        if (
366
            $type === null
8✔
367
            ||
368
            $type === ''
8✔
369
            ||
370
            $type[0] == '\\'
8✔
371
            ||
372
            $strNs == ''
8✔
373
        ) {
374
            return $type;
5✔
375
        }
376

377
        list($first) = \explode('[', $type, 2);
8✔
378
        if (
379
            $first === 'mixed'
8✔
380
            ||
381
            $this->isSimpleType($first)
8✔
382
        ) {
383
            return $type;
8✔
384
        }
385

386
        //create a full qualified namespace
387
        return '\\' . $strNs . '\\' . $type;
×
388
    }
389

390
    /**
391
     * Try to find out if a property exists in a given class.
392
     * Checks property first, falls back to setter method.
393
     *
394
     * @param \ReflectionClass<object> $rc   Reflection class to check
395
     * @param string                   $name Property name
396
     *
397
     * @return array First value: if the property exists
398
     *               Second value: the accessor to use (
399
     *               Array-Key-String or ReflectionMethod or ReflectionProperty, or null)
400
     *               Third value: type of the property
401
     */
402
    private function inspectProperty(\ReflectionClass $rc, $name): array
403
    {
404
        // now try to set the property directly, we have to look it up in the class hierarchy
405
        $class = $rc;
8✔
406
        $accessor = null;
8✔
407

408
        /** @var \Arrayy\Arrayy[] $ARRAYY_CACHE */
409
        /** @phpstan-var array<string, \Arrayy\Arrayy<array-key, mixed>> $ARRAYY_CACHE */
410
        static $ARRAYY_CACHE = [];
8✔
411

412
        if (\is_subclass_of($class->name, \Arrayy\Arrayy::class)) {
8✔
413
            if (!isset($ARRAYY_CACHE[$class->name])) {
8✔
414
                $ARRAYY_CACHE[$class->name] = new $class->name();
4✔
415
            }
416

417
            $tmpProps = $ARRAYY_CACHE[$class->name]->getPhpDocPropertiesFromClass();
8✔
418
            if ($tmpProps === []) {
8✔
419
                return [true, $name, 'mixed'];
1✔
420
            }
421

422
            foreach ($tmpProps as $tmpName => $tmpProp) {
7✔
423
                if ($tmpName === $name) {
7✔
424
                    return [true, $name, \implode('|', $tmpProp->getTypes())];
7✔
425
                }
426
            }
427
        }
428

429
        do {
430
            if ($class->hasProperty($name)) {
2✔
431
                $accessor = $class->getProperty($name);
1✔
432
            }
433
        } while ($accessor === null && $class = $class->getParentClass());
2✔
434

435
        if ($accessor === null) {
2✔
436
            // case-insensitive property matching
437
            foreach ($rc->getProperties() as $p) {
1✔
438
                if ((\strcasecmp($p->name, $name) === 0)) {
1✔
439
                    $accessor = $p;
×
440

441
                    break;
×
442
                }
443
            }
444
        }
445

446
        if ($accessor !== null) {
2✔
447
            if ($accessor->isPublic()) {
1✔
448
                $docblock = $accessor->getDocComment();
1✔
449
                if ($docblock === false) {
1✔
450
                    return [true, null, null];
×
451
                }
452

453
                $annotations = self::parseAnnotations($docblock);
1✔
454

455
                if (!isset($annotations['var'][0])) {
1✔
456
                    return [true, $accessor, null];
×
457
                }
458

459
                // support "@var type description"
460
                list($type) = \explode(' ', $annotations['var'][0]);
1✔
461

462
                return [true, $accessor, $type];
1✔
463
            }
464

465
            // no private property
466
            return [true, null, null];
×
467
        }
468

469
        // no setter, no property
470
        return [false, null, null];
1✔
471
    }
472

473
    /**
474
     * Copied from PHPUnit 3.7.29, Util/Test.php
475
     *
476
     * @param string $docblock Full method docblock
477
     *
478
     * @return array
479
     */
480
    private static function parseAnnotations($docblock): array
481
    {
482
        // init
483
        $annotations = [];
1✔
484

485
        // Strip away the docblock header and footer
486
        // to ease parsing of one line annotations
487
        $docblock = \substr($docblock, 3, -2);
1✔
488

489
        $re = '/@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?$/m';
1✔
490
        if (\preg_match_all($re, $docblock, $matches)) {
1✔
491
            $numMatches = \count($matches[0]);
1✔
492

493
            for ($i = 0; $i < $numMatches; ++$i) {
1✔
494
                $annotations[$matches['name'][$i]][] = $matches['value'][$i];
1✔
495
            }
496
        }
497

498
        return $annotations;
1✔
499
    }
500

501
    /**
502
     * Removes - and _ and makes the next letter uppercase
503
     *
504
     * @param string $name Property name
505
     *
506
     * @return string CamelCasedVariableName
507
     */
508
    private function getCamelCaseName($name): string
509
    {
510
        return \str_replace(
×
511
            ' ',
×
512
            '',
×
513
            \ucwords(\str_replace(['_', '-'], ' ', $name))
×
514
        );
×
515
    }
516

517
    /**
518
     * Since hyphens cannot be used in variables we have to uppercase them.
519
     *
520
     * Technically you may use them, but they are awkward to access.
521
     *
522
     * @param string $name Property name
523
     *
524
     * @return string Name without hyphen
525
     */
526
    private function getSafeName($name): string
527
    {
528
        $convertHyphens = \strpos($name, '-') !== false;
8✔
529
        $convertSnake = \strpos($name, '_') !== false;
8✔
530

531
        if ($convertHyphens || $convertSnake) {
8✔
532
            $name = $this->getCamelCaseName($name);
×
533
        }
534

535
        return $name;
8✔
536
    }
537

538
    /**
539
     * Set a property on a given object to a given value.
540
     *
541
     * Checks if the setter or the property are public are made before
542
     * calling this method.
543
     *
544
     * @param \Arrayy\Arrayy|object                        $object   Object to set property on
545
     * @param \ReflectionMethod|\ReflectionProperty|string $accessor Array-Key-String or ReflectionMethod or ReflectionProperty
546
     * @param mixed                                        $value    Value of property
547
     *
548
     * @return void
549
     */
550
    private function setProperty(
551
        $object,
552
        $accessor,
553
        $value
554
    ) {
555
        if (\is_string($accessor) && $object instanceof \Arrayy\Arrayy) {
8✔
556
            $object[$accessor] = $value;
8✔
557
        } elseif ($accessor instanceof \ReflectionProperty) {
1✔
558
            $accessor->setValue($object, $value);
1✔
559
        } elseif ($accessor instanceof \ReflectionMethod) {
×
560
            // setter method
561
            $accessor->invoke($object, $value);
×
562
        }
563
    }
564

565
    /**
566
     * Get the mapped class/type name for this class.
567
     * Returns the incoming classname if not mapped.
568
     *
569
     * @param string|null $type      Type name to map
570
     * @param mixed       $jsonValue Constructor parameter (the json value)
571
     *
572
     * @return string|null The mapped type/class name
573
     *
574
     * @phpstan-return class-string|string|null
575
     */
576
    private function getMappedType($type, $jsonValue = null)
577
    {
578
        if (isset($this->classMap[$type])) {
8✔
579
            $target = $this->classMap[$type];
×
580
        } elseif (
581
            \is_string($type)
8✔
582
            &&
583
            $type !== ''
8✔
584
            &&
585
            $type[0] == '\\'
8✔
586
            &&
587
            isset($this->classMap[\substr($type, 1)])
8✔
588
        ) {
589
            $target = $this->classMap[\substr($type, 1)];
×
590
        } else {
591
            $target = null;
8✔
592
        }
593

594
        if ($target) {
8✔
595
            if (\is_callable($target)) {
×
596
                $type = $target($type, $jsonValue);
×
597
            } else {
598
                $type = $target;
×
599
            }
600
        }
601

602
        return $type;
8✔
603
    }
604

605
    /**
606
     * Checks if the given type is a "simple type"
607
     *
608
     * @param string $type type name from gettype()
609
     *
610
     * @return bool True if it is a simple PHP type
611
     *
612
     * @see isScalarType()
613
     */
614
    private function isSimpleType($type): bool
615
    {
616
        if (\strpos($type, '|') !== false) {
7✔
617
            foreach (\explode('|', $type) as $tmpType) {
4✔
618
                if ($this->isSimpleType($tmpType)) {
4✔
619
                    return true;
4✔
620
                }
621
            }
622
        }
623

624
        /** @noinspection InArrayCanBeUsedInspection */
625
        return $type == 'string'
7✔
626
               || $type == 'boolean' || $type == 'bool'
7✔
627
               || $type == 'integer' || $type == 'int' || $type == 'int'
7✔
628
               || $type == 'double' || $type == 'float'
7✔
629
               || $type == 'array' || $type == 'object';
7✔
630
    }
631

632
    /**
633
     * Checks if the object is of this type or has this type as one of its parents
634
     *
635
     * @param string $type  class name of type being required
636
     * @param mixed  $value Some PHP value to be tested
637
     *
638
     * @return bool True if $object has type of $type
639
     */
640
    private function isObjectOfSameType($type, $value): bool
641
    {
642
        if (\is_object($value) === false) {
7✔
643
            return false;
7✔
644
        }
645

646
        return \is_a($value, $type);
4✔
647
    }
648

649
    /**
650
     * Checks if the given type is a type that is not nested
651
     * (simple type except array and object)
652
     *
653
     * @param string $type type name from gettype()
654
     *
655
     * @return bool True if it is a non-nested PHP type
656
     *
657
     * @see isSimpleType()
658
     */
659
    private function isScalarType($type): bool
660
    {
661
        /** @noinspection InArrayCanBeUsedInspection */
662
        return $type == 'NULL'
5✔
663
               || $type == 'string'
5✔
664
               || $type == 'boolean' || $type == 'bool'
5✔
665
               || $type == 'integer' || $type == 'int'
5✔
666
               || $type == 'double' || $type == 'float';
5✔
667
    }
668

669
    /**
670
     * Returns true if type is an array of elements
671
     * (bracket notation)
672
     *
673
     * @param string $strType type to be matched
674
     *
675
     * @return bool
676
     */
677
    private function isArrayOfType($strType): bool
678
    {
679
        return \substr($strType, -2) === '[]';
6✔
680
    }
681

682
    /**
683
     * Checks if the given type is nullable
684
     *
685
     * @param string $type type name from the phpdoc param
686
     *
687
     * @return bool True if it is nullable
688
     */
689
    private function isNullable($type): bool
690
    {
691
        return \stripos('|' . $type . '|', '|null|') !== false;
8✔
692
    }
693

694
    /**
695
     * Remove the 'null' section of a type
696
     *
697
     * @param false|string|null $type type name from the phpdoc param
698
     *
699
     * @return string|null The new type value
700
     */
701
    private function removeNullable($type)
702
    {
703
        if ($type === null || $type === false) {
6✔
704
            return null;
5✔
705
        }
706

707
        return \substr(
5✔
708
            \str_ireplace('|null|', '|', '|' . $type . '|'),
5✔
709
            1,
5✔
710
            -1
5✔
711
        );
5✔
712
    }
713

714
    /**
715
     * Create a new object of the given type.
716
     *
717
     * This method exists to be overwritten in child classes,
718
     * so you can do dependency injection or so.
719
     *
720
     * @param object|string $class
721
     *                            <p>Class name to instantiate</p>
722
     * @param bool $useParameter
723
     *                            <p>Pass $parameter to the constructor or not</p>
724
     * @param mixed $jsonValue
725
     *                            <p>Constructor parameter (the json value)</p>
726
     *
727
     * @return object
728
     *               <p>Freshly created object</p>
729
     *
730
     * @internal
731
     *
732
     * @template TClass
733
     * @phpstan-param TClass|class-string<TClass> $class
734
     * @phpstan-return TClass
735
     */
736
    private static function createInstance(
737
        $class,
738
        bool $useParameter = false,
739
        $jsonValue = null
740
    ) {
741
        if ($useParameter) {
6✔
742
            /** @phpstan-var TClass $return */
743
            $return = new $class($jsonValue);
×
744

745
            return $return;
×
746
        }
747

748
        $reflectClass = new \ReflectionClass($class);
6✔
749
        $constructor = $reflectClass->getConstructor();
6✔
750
        if (
751
            $constructor === null
6✔
752
            ||
753
            $constructor->getNumberOfRequiredParameters() > 0
6✔
754
        ) {
755
            /** @phpstan-var TClass $return */
756
            $return =  $reflectClass->newInstanceWithoutConstructor();
1✔
757
        } else {
758
            /** @phpstan-var TClass $return */
759
            $return = $reflectClass->newInstance();
6✔
760
        }
761

762
        return $return;
6✔
763
    }
764
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc