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

tempestphp / tempest-framework / 12021710761

25 Nov 2024 06:54PM UTC coverage: 79.441% (-2.6%) from 81.993%
12021710761

push

github

web-flow
ci: close stale issues and pull requests

7879 of 9918 relevant lines covered (79.44%)

61.32 hits per line

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

84.38
/src/Tempest/Reflection/src/TypeReflector.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Tempest\Reflection;
6

7
use Exception;
8
use Generator;
9
use ReflectionClass as PHPReflectionClass;
10
use ReflectionIntersectionType as PHPReflectionIntersectionType;
11
use ReflectionNamedType as PHPReflectionNamedType;
12
use ReflectionParameter as PHPReflectionParameter;
13
use ReflectionProperty as PHPReflectionProperty;
14
use ReflectionType as PHPReflectionType;
15
use ReflectionUnionType as PHPReflectionUnionType;
16
use Reflector as PHPReflector;
17

18
final readonly class TypeReflector implements Reflector
19
{
20
    private const array BUILTIN_VALIDATION = [
21
        'array' => 'is_array',
22
        'bool' => 'is_bool',
23
        'callable' => 'is_callable',
24
        'float' => 'is_float',
25
        'int' => 'is_int',
26
        'null' => 'is_null',
27
        'object' => 'is_object',
28
        'resource' => 'is_resource',
29
        'string' => 'is_string',
30
        // these are handled explicitly
31
        'false' => null,
32
        'mixed' => null,
33
        'never' => null,
34
        'true' => null,
35
        'void' => null,
36
    ];
37

38
    private string $definition;
39

40
    private string $cleanDefinition;
41

42
    private bool $isNullable;
43

44
    public function __construct(
443✔
45
        private PHPReflector|PHPReflectionType|string $reflector,
46
    ) {
47
        $this->definition = $this->resolveDefinition($this->reflector);
443✔
48
        $this->isNullable = $this->resolveIsNullable($this->reflector);
443✔
49
        $this->cleanDefinition = str_replace('?', '', $this->definition);
443✔
50
    }
51

52
    public function asClass(): ClassReflector
96✔
53
    {
54
        return new ClassReflector($this->cleanDefinition);
96✔
55
    }
56

57
    public function equals(string|TypeReflector $type): bool
13✔
58
    {
59
        if (is_string($type)) {
13✔
60
            $type = new TypeReflector($type);
×
61
        }
62

63
        return $this->definition === $type->definition;
13✔
64
    }
65

66
    public function accepts(mixed $input): bool
403✔
67
    {
68
        if ($this->isNullable && $input === null) {
403✔
69
            return true;
1✔
70
        }
71

72
        if ($this->isBuiltIn()) {
402✔
73
            return match ($this->cleanDefinition) {
5✔
74
                'false' => $input === false,
×
75
                'mixed' => true,
×
76
                'never' => false,
×
77
                'true' => $input === true,
×
78
                'void' => false,
×
79
                default => self::BUILTIN_VALIDATION[$this->cleanDefinition]($input),
5✔
80
            };
5✔
81
        }
82

83
        if ($this->isClass()) {
398✔
84
            if (is_string($input)) {
397✔
85
                return $this->matches($input);
3✔
86
            }
87

88
            $cleanDefinition = $this->cleanDefinition;
397✔
89

90
            return $input instanceof $cleanDefinition;
397✔
91
        }
92

93
        if ($this->isIterable()) {
383✔
94
            return is_iterable($input);
×
95
        }
96

97
        if (str_contains($this->definition, '|')) {
383✔
98
            foreach ($this->split() as $type) {
1✔
99
                if ($type->accepts($input)) {
1✔
100
                    return true;
1✔
101
                }
102
            }
103

104
            return false;
×
105
        }
106

107
        if (str_contains($this->definition, '&')) {
382✔
108
            foreach ($this->split() as $type) {
×
109
                if (! $type->accepts($input)) {
×
110
                    return false;
×
111
                }
112
            }
113

114
            return true;
×
115
        }
116

117
        return false;
382✔
118
    }
119

120
    public function matches(string $className): bool
416✔
121
    {
122
        return is_a($this->cleanDefinition, $className, true);
416✔
123
    }
124

125
    public function getName(): string
425✔
126
    {
127
        return $this->definition;
425✔
128
    }
129

130
    public function getShortName(): string
389✔
131
    {
132
        $parts = explode('\\', $this->definition);
389✔
133

134
        return $parts[array_key_last($parts)];
389✔
135
    }
136

137
    public function isBuiltIn(): bool
410✔
138
    {
139
        return isset(self::BUILTIN_VALIDATION[$this->cleanDefinition]);
410✔
140
    }
141

142
    public function isClass(): bool
398✔
143
    {
144
        return class_exists($this->cleanDefinition);
398✔
145
    }
146

147
    public function isInterface(): bool
×
148
    {
149
        return interface_exists($this->cleanDefinition);
×
150
    }
151

152
    public function isIterable(): bool
392✔
153
    {
154
        return in_array($this->cleanDefinition, [
392✔
155
            'array',
392✔
156
            'iterable',
392✔
157
            Generator::class,
392✔
158
        ]);
392✔
159
    }
160

161
    public function isNullable(): bool
2✔
162
    {
163
        return $this->isNullable;
2✔
164
    }
165

166
    /** @return self[] */
167
    public function split(): array
406✔
168
    {
169
        return array_map(
406✔
170
            fn (string $part) => new self($part),
406✔
171
            preg_split('/[&|]/', $this->definition),
406✔
172
        );
406✔
173
    }
174

175
    private function resolveDefinition(PHPReflector|PHPReflectionType|string $reflector): string
443✔
176
    {
177
        if (is_string($reflector)) {
443✔
178
            return $reflector;
413✔
179
        }
180

181
        if (
182
            $reflector instanceof PHPReflectionParameter
435✔
183
            || $reflector instanceof PHPReflectionProperty
435✔
184
        ) {
185
            return $this->resolveDefinition($reflector->getType());
421✔
186
        }
187

188
        if ($reflector instanceof PHPReflectionClass) {
435✔
189
            return $reflector->getName();
415✔
190
        }
191

192
        if ($reflector instanceof PHPReflectionNamedType) {
429✔
193
            return $reflector->getName();
429✔
194
        }
195

196
        if ($reflector instanceof PHPReflectionUnionType) {
385✔
197
            return implode('|', array_map(
384✔
198
                fn (PHPReflectionType $reflectionType) => $this->resolveDefinition($reflectionType),
384✔
199
                $reflector->getTypes(),
384✔
200
            ));
384✔
201
        }
202

203
        if ($reflector instanceof PHPReflectionIntersectionType) {
1✔
204
            return implode('&', array_map(
1✔
205
                fn (PHPReflectionType $reflectionType) => $this->resolveDefinition($reflectionType),
1✔
206
                $reflector->getTypes(),
1✔
207
            ));
1✔
208
        }
209

210
        throw new Exception('Could not resolve type');
×
211
    }
212

213
    private function resolveIsNullable(PHPReflectionType|PHPReflector|string $reflector): bool
443✔
214
    {
215
        if (is_string($reflector)) {
443✔
216
            return str_contains($this->definition, '?') || str_contains($this->definition, 'null');
413✔
217
        }
218

219
        if (
220
            $reflector instanceof PHPReflectionParameter
435✔
221
            || $reflector instanceof PHPReflectionProperty
435✔
222
        ) {
223
            return $reflector->getType()->allowsNull();
421✔
224
        }
225

226
        if ($reflector instanceof PHPReflectionType) {
415✔
227
            return $reflector->allowsNull();
392✔
228
        }
229

230
        return false;
415✔
231
    }
232
}
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