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

tempestphp / tempest-framework / 14140550176

28 Mar 2025 10:29PM UTC coverage: 80.716% (+1.4%) from 79.334%
14140550176

push

github

web-flow
feat(support): support `$default` on array `first` and `last` methods (#1096)

11 of 12 new or added lines in 2 files covered. (91.67%)

135 existing lines in 16 files now uncovered.

10941 of 13555 relevant lines covered (80.72%)

100.32 hits per line

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

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

3
declare(strict_types=1);
4

5
namespace Tempest\Reflection;
6

7
use BackedEnum;
8
use DateTimeInterface;
9
use Exception;
10
use Generator;
11
use Iterator;
12
use ReflectionClass as PHPReflectionClass;
13
use ReflectionIntersectionType as PHPReflectionIntersectionType;
14
use ReflectionNamedType as PHPReflectionNamedType;
15
use ReflectionParameter as PHPReflectionParameter;
16
use ReflectionProperty as PHPReflectionProperty;
17
use ReflectionType as PHPReflectionType;
18
use ReflectionUnionType as PHPReflectionUnionType;
19
use Reflector as PHPReflector;
20
use Stringable;
21
use UnitEnum;
22

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

43
    private const array SCALAR_TYPES = [
44
        'bool',
45
        'string',
46
        'int',
47
        'float',
48
    ];
49

50
    private string $definition;
51

52
    private string $cleanDefinition;
53

54
    private bool $isNullable;
55

56
    public function __construct(
781✔
57
        private PHPReflector|PHPReflectionType|string $reflector,
58
    ) {
59
        $this->definition = $this->resolveDefinition($this->reflector);
781✔
60
        $this->isNullable = $this->resolveIsNullable($this->reflector);
781✔
61
        $this->cleanDefinition = str_replace('?', '', $this->definition);
781✔
62
    }
63

64
    public function asClass(): ClassReflector
138✔
65
    {
66
        return new ClassReflector($this->cleanDefinition);
138✔
67
    }
68

69
    public function equals(string|TypeReflector $type): bool
81✔
70
    {
71
        if (is_string($type)) {
81✔
72
            $type = new TypeReflector($type);
70✔
73
        }
74

75
        return $this->definition === $type->definition;
81✔
76
    }
77

78
    public function accepts(mixed $input): bool
728✔
79
    {
80
        if ($this->isNullable && $input === null) {
728✔
81
            return true;
1✔
82
        }
83

84
        if ($this->isBuiltIn()) {
727✔
85
            return match ($this->cleanDefinition) {
245✔
UNCOV
86
                'false' => $input === false,
×
UNCOV
87
                'mixed' => true,
×
UNCOV
88
                'never' => false,
×
UNCOV
89
                'true' => $input === true,
×
UNCOV
90
                'void' => false,
×
91
                default => self::BUILTIN_VALIDATION[$this->cleanDefinition]($input),
245✔
92
            };
245✔
93
        }
94

95
        if ($this->isClass()) {
723✔
96
            if (is_string($input)) {
722✔
97
                return $this->matches($input);
4✔
98
            }
99

100
            $cleanDefinition = $this->cleanDefinition;
722✔
101

102
            return $input instanceof $cleanDefinition;
722✔
103
        }
104

105
        if ($this->isIterable()) {
708✔
UNCOV
106
            return is_iterable($input);
×
107
        }
108

109
        if (str_contains($this->definition, '|')) {
708✔
110
            return array_any($this->split(), static fn ($type) => $type->accepts($input));
1✔
111
        }
112

113
        if (str_contains($this->definition, '&')) {
707✔
UNCOV
114
            return array_all($this->split(), static fn ($type) => $type->accepts($input));
×
115
        }
116

117
        return false;
707✔
118
    }
119

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

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

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

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

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

142
    public function isScalar(): bool
19✔
143
    {
144
        return in_array($this->cleanDefinition, self::SCALAR_TYPES, strict: true);
19✔
145
    }
146

147
    public function isClass(): bool
732✔
148
    {
149
        return class_exists($this->cleanDefinition);
732✔
150
    }
151

152
    public function isEnum(): bool
70✔
153
    {
154
        if ($this->matches(UnitEnum::class)) {
70✔
155
            return true;
17✔
156
        }
157

158
        return $this->matches(BackedEnum::class);
69✔
159
    }
160

161
    // TODO: should be refactored outside of the reflector component
162
    public function isRelation(): bool
79✔
163
    {
164
        return $this->isClass() && ! $this->isEnum() && ! $this->isIterable() && ! $this->isStringable() && ! $this->matches(DateTimeInterface::class);
79✔
165
    }
166

UNCOV
167
    public function isInterface(): bool
×
168
    {
UNCOV
169
        return interface_exists($this->cleanDefinition);
×
170
    }
171

172
    public function isIterable(): bool
715✔
173
    {
174
        if ($this->matches(Iterator::class)) {
715✔
175
            return true;
2✔
176
        }
177

178
        return in_array(
715✔
179
            $this->cleanDefinition,
715✔
180
            [
715✔
181
                'array',
715✔
182
                'iterable',
715✔
183
                Generator::class,
715✔
184
            ],
715✔
185
            strict: true,
715✔
186
        );
715✔
187
    }
188

189
    public function isStringable(): bool
69✔
190
    {
191
        if ($this->matches(Stringable::class)) {
69✔
192
            return true;
62✔
193
        }
194

195
        return in_array(
46✔
196
            $this->cleanDefinition,
46✔
197
            [
46✔
198
                'string',
46✔
199
            ],
46✔
200
            strict: true,
46✔
201
        );
46✔
202
    }
203

204
    public function isNullable(): bool
19✔
205
    {
206
        return $this->isNullable;
19✔
207
    }
208

209
    /** @return self[] */
210
    public function split(): array
731✔
211
    {
212
        return array_map(
731✔
213
            fn (string $part) => new self($part),
731✔
214
            preg_split('/[&|]/', $this->definition),
731✔
215
        );
731✔
216
    }
217

218
    private function resolveDefinition(PHPReflector|PHPReflectionType|string $reflector): string
781✔
219
    {
220
        if (is_string($reflector)) {
781✔
221
            return $reflector;
743✔
222
        }
223

224
        if ($reflector instanceof PHPReflectionParameter || $reflector instanceof PHPReflectionProperty) {
768✔
225
            return $this->resolveDefinition($reflector->getType());
751✔
226
        }
227

228
        if ($reflector instanceof PHPReflectionClass) {
768✔
229
            return $reflector->getName();
741✔
230
        }
231

232
        if ($reflector instanceof PHPReflectionNamedType) {
759✔
233
            return $reflector->getName();
759✔
234
        }
235

236
        if ($reflector instanceof PHPReflectionUnionType) {
710✔
237
            return implode('|', array_map(
709✔
238
                fn (PHPReflectionType $reflectionType) => $this->resolveDefinition($reflectionType),
709✔
239
                $reflector->getTypes(),
709✔
240
            ));
709✔
241
        }
242

243
        if ($reflector instanceof PHPReflectionIntersectionType) {
1✔
244
            return implode('&', array_map(
1✔
245
                fn (PHPReflectionType $reflectionType) => $this->resolveDefinition($reflectionType),
1✔
246
                $reflector->getTypes(),
1✔
247
            ));
1✔
248
        }
249

UNCOV
250
        throw new Exception('Could not resolve type');
×
251
    }
252

253
    private function resolveIsNullable(PHPReflectionType|PHPReflector|string $reflector): bool
781✔
254
    {
255
        if (is_string($reflector)) {
781✔
256
            return str_contains($this->definition, '?') || str_contains($this->definition, 'null');
743✔
257
        }
258

259
        if ($reflector instanceof PHPReflectionParameter || $reflector instanceof PHPReflectionProperty) {
768✔
260
            return $reflector->getType()->allowsNull();
751✔
261
        }
262

263
        if ($reflector instanceof PHPReflectionType) {
741✔
264
            return $reflector->allowsNull();
717✔
265
        }
266

267
        return false;
741✔
268
    }
269
}
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