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

voku / Arrayy / 25170332470

30 Apr 2026 02:13PM UTC coverage: 92.38%. Remained the same
25170332470

push

github

web-flow
Merge pull request #167 from voku/copilot/update-readme-custom-phpstan-extension

Document PHPStan `meta()` return-type extension usage

2764 of 2992 relevant lines covered (92.38%)

249.42 hits per line

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

91.64
/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)) {
154✔
70
            $object = self::createInstance($object);
35✔
71
        }
72

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

79
        $strClassName = \get_class($object);
147✔
80
        $rc = new \ReflectionClass($object);
147✔
81
        $strNs = $rc->getNamespaceName();
147✔
82
        foreach ($json as $key => $jsonValue) {
147✔
83
            $key = $this->getSafeName($key);
147✔
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])) {
147✔
88
                $this->arInspectedClasses[$strClassName][$key] = $this->inspectProperty($rc, $key);
147✔
89
            }
90

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

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

107
                continue;
7✔
108
            }
109

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

114
            if ($this->isNullable($type)) {
133✔
115
                if ($jsonValue === null) {
70✔
116
                    $this->setProperty($object, $accessor, null);
28✔
117

118
                    continue;
28✔
119
                }
120

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

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

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

139
                continue;
14✔
140
            }
141

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

145
                continue;
7✔
146
            }
147

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

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

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

167
                continue;
91✔
168
            }
169

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

176
            $array = null;
77✔
177
            $subtype = null;
77✔
178
            if ($this->isArrayOfType($type)) {
77✔
179
                $array = [];
21✔
180
                $subtype = \substr($type, 0, -2);
21✔
181
            } elseif (\substr($type, -1) == ']') {
56✔
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)) {
56✔
192
                /** @noinspection PhpSillyAssignmentInspection - phpstan helper */
193
                /** @phpstan-var \ArrayObject<array-key, mixed> $type */
194
                $type = $type;
49✔
195
                $array = self::createInstance($type, false, $jsonValue);
49✔
196
            }
197

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

210
                $cleanSubtype = $this->removeNullable($subtype);
70✔
211
                $subtype = $this->getFullNamespace($cleanSubtype, $strNs);
70✔
212
                if (
213
                    $object instanceof \Arrayy\Arrayy
70✔
214
                    &&
215
                    $subtype !== null
70✔
216
                    &&
217
                    $this->isSimpleType($subtype)
70✔
218
                ) {
219
                    $child = $jsonValue;
21✔
220
                } else {
221
                    $child = $this->mapArray($jsonValue, $array, $subtype, $key);
58✔
222
                }
223
            } elseif ($this->isScalarType(\gettype($jsonValue))) {
7✔
224
                // use constructor parameter if we have a class, but only a flat type (i.e. string, int)
225
                /** @noinspection PhpSillyAssignmentInspection - phpstan helper */
226
                /** @phpstan-var object $type */
227
                $type = $type;
7✔
228
                $child = self::createInstance($type, true, $jsonValue);
7✔
229
            } else {
230
                /** @noinspection PhpSillyAssignmentInspection - phpstan helper */
231
                /** @phpstan-var object $type */
232
                $type = $type;
7✔
233
                $child = self::createInstance($type, false, $jsonValue);
7✔
234
                $this->map($jsonValue, $child);
7✔
235
            }
236

237
            $this->setProperty($object, $accessor, $child);
70✔
238
        }
239

240
        /** @noinspection PhpSillyAssignmentInspection */
241
        /** @phpstan-var TObject $object */
242
        $object = $object;
105✔
243

244
        return $object;
105✔
245
    }
246

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

273
                if ($array instanceof \Arrayy\Arrayy && $jsonValue instanceof \stdClass) {
63✔
274
                    foreach ($array->getPhpDocPropertiesFromClass() as $typesKey => $typesTmp) {
14✔
275
                        if (
276
                            (
277
                                $typesKey === $key
14✔
278
                                ||
14✔
279
                                $typesKey === \Arrayy\Arrayy::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES
14✔
280
                            )
281
                            &&
282
                            \count($typesTmp->getTypes()) === 1
14✔
283
                            &&
284
                            \is_subclass_of($typesTmp->getTypes()[0], \Arrayy\Arrayy::class)
14✔
285
                        ) {
286
                            $array[$key] = $typesTmp->getTypes()[0]::createFromObjectVars($jsonValue);
7✔
287
                            $foundArrayy = true;
7✔
288

289
                            break;
7✔
290
                        }
291
                    }
292
                }
293
                if ($foundArrayy === false) {
63✔
294
                    if ($array instanceof \Arrayy\Arrayy && $jsonValue instanceof \stdClass) {
56✔
295
                        foreach ($array->getPhpDocPropertiesFromClass() as $typesKey => $typesTmp) {
7✔
296
                            if (
297
                                (
298
                                    $typesKey === $key
7✔
299
                                    ||
7✔
300
                                    $typesKey === \Arrayy\Arrayy::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES
7✔
301
                                )
302
                                &&
303
                                \count($typesTmp->getTypes()) === 1
7✔
304
                            ) {
305
                                $array[$key] = $this->map($jsonValue, $typesTmp->getTypes()[0]);
7✔
306
                                $foundArrayy = true;
7✔
307

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

362
        return $array;
63✔
363
    }
364

365
    /**
366
     * Convert a type name to a fully namespaced type name.
367
     *
368
     * @param string|null $type  Type name (simple type or class name)
369
     * @param string      $strNs Base namespace that gets prepended to the type name
370
     *
371
     * @return string|null Fully-qualified type name with namespace
372
     */
373
    private function getFullNamespace($type, $strNs)
374
    {
375
        if (
376
            $type === null
119✔
377
            ||
378
            $type === ''
112✔
379
            ||
380
            $type[0] == '\\'
112✔
381
            ||
382
            $strNs == ''
119✔
383
        ) {
384
            return $type;
70✔
385
        }
386

387
        list($first) = \explode('[', $type, 2);
105✔
388
        if (
389
            $first === 'mixed'
105✔
390
            ||
391
            $this->isSimpleType($first)
105✔
392
        ) {
393
            return $type;
105✔
394
        }
395

396
        //create a full qualified namespace
397
        return '\\' . $strNs . '\\' . $type;
×
398
    }
399

400
    /**
401
     * Try to find out if a property exists in a given class.
402
     * Checks property first, falls back to setter method.
403
     *
404
     * @param \ReflectionClass<object> $rc   Reflection class to check
405
     * @param string                   $name Property name
406
     *
407
     * @return array First value: if the property exists
408
     *               Second value: the accessor to use (
409
     *               Array-Key-String or ReflectionMethod or ReflectionProperty, or null)
410
     *               Third value: type of the property
411
     */
412
    private function inspectProperty(\ReflectionClass $rc, $name): array
413
    {
414
        // now try to set the property directly, we have to look it up in the class hierarchy
415
        $class = $rc;
147✔
416
        $accessor = null;
147✔
417

418
        /** @var \Arrayy\Arrayy[] $ARRAYY_CACHE */
419
        /** @phpstan-var array<string, \Arrayy\Arrayy<array-key, mixed, array<array-key, mixed>>> $ARRAYY_CACHE */
420
        static $ARRAYY_CACHE = [];
147✔
421

422
        if (\is_subclass_of($class->name, \Arrayy\Arrayy::class)) {
147✔
423
            if (!isset($ARRAYY_CACHE[$class->name])) {
84✔
424
                $ARRAYY_CACHE[$class->name] = new $class->name();
42✔
425
            }
426

427
            $tmpProps = $ARRAYY_CACHE[$class->name]->getPhpDocPropertiesFromClass();
84✔
428
            if ($tmpProps === []) {
84✔
429
                return [true, $name, 'mixed'];
7✔
430
            }
431

432
            foreach ($tmpProps as $tmpName => $tmpProp) {
77✔
433
                if ($tmpName === $name) {
77✔
434
                    return [true, $name, \implode('|', $tmpProp->getTypes())];
77✔
435
                }
436
            }
437
        }
438

439
        do {
440
            if ($class->hasProperty($name)) {
77✔
441
                $accessor = $class->getProperty($name);
63✔
442
            }
443
        } while ($accessor === null && $class = $class->getParentClass());
77✔
444

445
        if ($accessor === null) {
77✔
446
            // case-insensitive property matching
447
            foreach ($rc->getProperties() as $p) {
14✔
448
                if ((\strcasecmp($p->name, $name) === 0)) {
7✔
449
                    $accessor = $p;
×
450

451
                    break;
×
452
                }
453
            }
454
        }
455

456
        if ($accessor !== null) {
77✔
457
            if ($accessor->isPublic()) {
63✔
458
                $docblock = $accessor->getDocComment();
56✔
459
                if ($docblock === false) {
56✔
460
                    return [true, null, null];
×
461
                }
462

463
                $annotations = self::parseAnnotations($docblock);
56✔
464

465
                if (!isset($annotations['var'][0])) {
56✔
466
                    return [true, $accessor, null];
7✔
467
                }
468

469
                // support "@var type description"
470
                list($type) = \explode(' ', $annotations['var'][0]);
49✔
471

472
                return [true, $accessor, $type];
49✔
473
            }
474

475
            // no private property
476
            return [true, null, null];
7✔
477
        }
478

479
        // no setter, no property
480
        return [false, null, null];
14✔
481
    }
482

483
    /**
484
     * Copied from PHPUnit 3.7.29, Util/Test.php
485
     *
486
     * @param string $docblock Full method docblock
487
     *
488
     * @return array
489
     */
490
    private static function parseAnnotations($docblock): array
491
    {
492
        // init
493
        $annotations = [];
56✔
494

495
        // Strip away the docblock header and footer
496
        // to ease parsing of one line annotations
497
        $docblock = \substr($docblock, 3, -2);
56✔
498

499
        $re = '/@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?$/m';
56✔
500
        if (\preg_match_all($re, $docblock, $matches)) {
56✔
501
            $numMatches = \count($matches[0]);
49✔
502

503
            for ($i = 0; $i < $numMatches; ++$i) {
49✔
504
                $annotations[$matches['name'][$i]][] = $matches['value'][$i];
49✔
505
            }
506
        }
507

508
        return $annotations;
56✔
509
    }
510

511
    /**
512
     * Removes - and _ and makes the next letter uppercase
513
     *
514
     * @param string $name Property name
515
     *
516
     * @return string CamelCasedVariableName
517
     */
518
    private function getCamelCaseName($name): string
519
    {
520
        return \str_replace(
7✔
521
            ' ',
7✔
522
            '',
7✔
523
            \ucwords(\str_replace(['_', '-'], ' ', $name))
7✔
524
        );
7✔
525
    }
526

527
    /**
528
     * Since hyphens cannot be used in variables we have to uppercase them.
529
     *
530
     * Technically you may use them, but they are awkward to access.
531
     *
532
     * @param string $name Property name
533
     *
534
     * @return string Name without hyphen
535
     */
536
    private function getSafeName($name): string
537
    {
538
        $convertHyphens = \strpos($name, '-') !== false;
147✔
539
        $convertSnake = \strpos($name, '_') !== false;
147✔
540

541
        if ($convertHyphens || $convertSnake) {
147✔
542
            $name = $this->getCamelCaseName($name);
7✔
543
        }
544

545
        return $name;
147✔
546
    }
547

548
    /**
549
     * Set a property on a given object to a given value.
550
     *
551
     * Checks if the setter or the property are public are made before
552
     * calling this method.
553
     *
554
     * @param \Arrayy\Arrayy|object                        $object   Object to set property on
555
     * @param \ReflectionMethod|\ReflectionProperty|string $accessor Array-Key-String or ReflectionMethod or ReflectionProperty
556
     * @param mixed                                        $value    Value of property
557
     *
558
     * @return void
559
     */
560
    private function setProperty(
561
        $object,
562
        $accessor,
563
        $value
564
    ) {
565
        if (\is_string($accessor) && $object instanceof \Arrayy\Arrayy) {
119✔
566
            $object[$accessor] = $value;
84✔
567
        } elseif ($accessor instanceof \ReflectionProperty) {
42✔
568
            $accessor->setValue($object, $value);
42✔
569
        } elseif ($accessor instanceof \ReflectionMethod) {
×
570
            // setter method
571
            $accessor->invoke($object, $value);
×
572
        }
573
    }
574

575
    /**
576
     * Get the mapped class/type name for this class.
577
     * Returns the incoming classname if not mapped.
578
     *
579
     * @param string|null $type      Type name to map
580
     * @param mixed       $jsonValue Constructor parameter (the json value)
581
     *
582
     * @return string|null The mapped type/class name
583
     *
584
     * @phpstan-return class-string|string|null
585
     */
586
    private function getMappedType($type, $jsonValue = null)
587
    {
588
        if (isset($this->classMap[$type])) {
140✔
589
            $target = $this->classMap[$type];
×
590
        } elseif (
591
            \is_string($type)
140✔
592
            &&
593
            $type !== ''
140✔
594
            &&
595
            $type[0] == '\\'
140✔
596
            &&
597
            isset($this->classMap[\substr($type, 1)])
140✔
598
        ) {
599
            $target = $this->classMap[\substr($type, 1)];
×
600
        } else {
601
            $target = null;
140✔
602
        }
603

604
        if ($target) {
140✔
605
            if (\is_callable($target)) {
×
606
                $type = $target($type, $jsonValue);
×
607
            } else {
608
                $type = $target;
×
609
            }
610
        }
611

612
        return $type;
140✔
613
    }
614

615
    /**
616
     * Checks if the given type is a "simple type"
617
     *
618
     * @param string $type type name from gettype()
619
     *
620
     * @return bool True if it is a simple PHP type
621
     *
622
     * @see isScalarType()
623
     */
624
    private function isSimpleType($type): bool
625
    {
626
        if (\strpos($type, '|') !== false) {
98✔
627
            foreach (\explode('|', $type) as $tmpType) {
42✔
628
                if ($this->isSimpleType($tmpType)) {
42✔
629
                    return true;
42✔
630
                }
631
            }
632
        }
633

634
        /** @noinspection InArrayCanBeUsedInspection */
635
        return $type == 'string'
98✔
636
               || $type == 'boolean' || $type == 'bool'
98✔
637
               || $type == 'integer' || $type == 'int' || $type == 'int'
98✔
638
               || $type == 'double' || $type == 'float'
98✔
639
               || $type == 'array' || $type == 'object';
98✔
640
    }
641

642
    /**
643
     * Checks if the object is of this type or has this type as one of its parents
644
     *
645
     * @param string $type  class name of type being required
646
     * @param mixed  $value Some PHP value to be tested
647
     *
648
     * @return bool True if $object has type of $type
649
     */
650
    private function isObjectOfSameType($type, $value): bool
651
    {
652
        if (\is_object($value) === false) {
105✔
653
            return false;
91✔
654
        }
655

656
        return \is_a($value, $type);
63✔
657
    }
658

659
    /**
660
     * Checks if the given type is a type that is not nested
661
     * (simple type except array and object)
662
     *
663
     * @param string $type type name from gettype()
664
     *
665
     * @return bool True if it is a non-nested PHP type
666
     *
667
     * @see isSimpleType()
668
     */
669
    private function isScalarType($type): bool
670
    {
671
        /** @noinspection InArrayCanBeUsedInspection */
672
        return $type == 'NULL'
70✔
673
               || $type == 'string'
70✔
674
               || $type == 'boolean' || $type == 'bool'
70✔
675
               || $type == 'integer' || $type == 'int'
70✔
676
               || $type == 'double' || $type == 'float';
70✔
677
    }
678

679
    /**
680
     * Returns true if type is an array of elements
681
     * (bracket notation)
682
     *
683
     * @param string $strType type to be matched
684
     *
685
     * @return bool
686
     */
687
    private function isArrayOfType($strType): bool
688
    {
689
        return \substr($strType, -2) === '[]';
98✔
690
    }
691

692
    /**
693
     * Checks if the given type is nullable
694
     *
695
     * @param string $type type name from the phpdoc param
696
     *
697
     * @return bool True if it is nullable
698
     */
699
    private function isNullable($type): bool
700
    {
701
        return \stripos('|' . $type . '|', '|null|') !== false;
133✔
702
    }
703

704
    /**
705
     * Remove the 'null' section of a type
706
     *
707
     * @param false|string|null $type type name from the phpdoc param
708
     *
709
     * @return string|null The new type value
710
     */
711
    private function removeNullable($type)
712
    {
713
        if ($type === null || $type === false) {
70✔
714
            return null;
49✔
715
        }
716

717
        return \substr(
63✔
718
            \str_ireplace('|null|', '|', '|' . $type . '|'),
63✔
719
            1,
63✔
720
            -1
63✔
721
        );
63✔
722
    }
723

724
    /**
725
     * Create a new object of the given type.
726
     *
727
     * This method exists to be overwritten in child classes,
728
     * so you can do dependency injection or so.
729
     *
730
     * @param object|string $class
731
     *                            <p>Class name to instantiate</p>
732
     * @param bool $useParameter
733
     *                            <p>Pass $parameter to the constructor or not</p>
734
     * @param mixed $jsonValue
735
     *                            <p>Constructor parameter (the json value)</p>
736
     *
737
     * @return object
738
     *               <p>Freshly created object</p>
739
     *
740
     * @internal
741
     *
742
     * @template TClass
743
     * @phpstan-param TClass|class-string<TClass> $class
744
     * @phpstan-return TClass
745
     */
746
    private static function createInstance(
747
        $class,
748
        bool $useParameter = false,
749
        $jsonValue = null
750
    ) {
751
        if ($useParameter) {
77✔
752
            /** @phpstan-var TClass $return */
753
            $return = new $class($jsonValue);
7✔
754

755
            return $return;
7✔
756
        }
757

758
        $reflectClass = new \ReflectionClass($class);
77✔
759
        $constructor = $reflectClass->getConstructor();
77✔
760
        if (
761
            $constructor === null
77✔
762
            ||
763
            $constructor->getNumberOfRequiredParameters() > 0
77✔
764
        ) {
765
            /** @phpstan-var TClass $return */
766
            $return =  $reflectClass->newInstanceWithoutConstructor();
21✔
767
        } else {
768
            /** @phpstan-var TClass $return */
769
            $return = $reflectClass->newInstance();
63✔
770
        }
771

772
        return $return;
77✔
773
    }
774
}
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

© 2026 Coveralls, Inc