Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
65.57% covered (warning)
65.57%
40 / 61
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
TypeUtils
65.57% covered (warning)
65.57%
40 / 61
0.00% covered (danger)
0.00%
0 / 3
113.97
0.00% covered (danger)
0.00%
0 / 1
 __construct
n/a
0 / 0
n/a
0 / 0
1
 allowEmptyString
91.67% covered (success)
91.67%
22 / 24
0.00% covered (danger)
0.00%
0 / 1
13.10
 couldBeAStream
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
42
 matchesType
64.29% covered (warning)
64.29%
18 / 28
0.00% covered (danger)
0.00%
0 / 1
44.05
1<?php
2namespace Apie\Core;
3
4use Apie\Core\Utils\ConverterUtils;
5use Apie\Core\ValueObjects\Interfaces\ValueObjectInterface;
6use Psr\Http\Message\UploadedFileInterface;
7use ReflectionClass;
8use ReflectionIntersectionType;
9use ReflectionNamedType;
10use ReflectionType;
11use ReflectionUnionType;
12use Throwable;
13
14final class TypeUtils
15{
16    /**
17     * @codeCoverageIgnore
18     */
19    private function __construct()
20    {
21    }
22
23    public static function allowEmptyString(
24        ?ReflectionType $type
25    ): bool {
26        if ($type === null) {
27            return true;
28        }
29        if ($type instanceof ReflectionNamedType) {
30            if ($type->getName() === 'string' || $type->getName() === 'mixed') {
31                return true;
32            }
33            $class = ConverterUtils::toReflectionClass($type);
34            if (!$class) {
35                return false;
36            }
37            if (in_array(ValueObjectInterface::class, $class->getInterfaceNames())) {
38                try {
39                    $class->getMethod('fromNative')->invoke(null, '');
40                    return true;
41                } catch (Throwable) {
42                    return false;
43                }
44            }
45            return false;
46        }
47        if ($type instanceof ReflectionIntersectionType) {
48            foreach ($type->getTypes() as $type) {
49                if (!self::allowEmptyString($type)) {
50                    return false;
51                }
52            }
53            return true;
54        }
55        assert($type instanceof ReflectionUnionType);
56        foreach ($type->getTypes() as $type) {
57            if (self::allowEmptyString($type)) {
58                return true;
59            }
60        }
61        return false;
62    }
63
64    public static function couldBeAStream(
65        ?ReflectionType $type
66    ): bool {
67        if ($type === null) {
68            return true;
69        }
70        if ($type instanceof ReflectionNamedType) {
71            return in_array($type->getName(), ['mixed', 'resource', UploadedFileInterface::class]);
72        }
73        assert($type instanceof ReflectionIntersectionType || $type instanceof ReflectionUnionType);
74    
75        foreach ($type->getTypes() as $type) {
76            if (self::couldBeAStream($type)) {
77                return true;
78            }
79        }
80        return false;
81    }
82
83    public static function matchesType(
84        ?ReflectionType $type,
85        mixed $input
86    ): bool {
87        if ($type === null) {
88            return true;
89        }
90        if ($input === null && $type->allowsNull()) {
91            return true;
92        }
93        if ($type instanceof ReflectionNamedType) {
94            return match ($type->getName()) {
95                'mixed' => true,
96                'bool' => is_bool($input),
97                'int' => is_int($input),
98                'string' => is_string($input),
99                'null' => $input === null,
100                'true' => $input === true,
101                'false' => $input === false,
102                default => get_debug_type($input) === $type->getName()
103                    || ((interface_exists($type->getName()) || class_exists($type->getName()))
104                        && is_object($input)
105                        && (new ReflectionClass($type->getName()))->isInstance($input))
106            };
107        }
108        if ($type instanceof ReflectionUnionType) {
109            foreach ($type->getTypes() as $type) {
110                if (self::matchesType($type, $input)) {
111                    return true;
112                }
113            }
114            return false;
115        }
116        assert($type instanceof ReflectionIntersectionType);
117        foreach ($type->getTypes() as $type) {
118            if (!self::matchesType($type, $input)) {
119                return false;
120            }
121        }
122        return true;
123    }
124}