Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
100.00% |
124 / 124 |
|
94.59% |
70 / 74 |
|
51.85% |
28 / 54 |
|
50.00% |
4 / 8 |
CRAP | |
0.00% |
0 / 1 |
| StaticReflectionCache | |
100.00% |
124 / 124 |
|
94.59% |
70 / 74 |
|
51.85% |
28 / 54 |
|
100.00% |
8 / 8 |
154.55 | |
100.00% |
1 / 1 |
| metadata | |
100.00% |
3 / 3 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| reset | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| build | |
100.00% |
21 / 21 |
|
94.12% |
16 / 17 |
|
23.81% |
5 / 21 |
|
100.00% |
1 / 1 |
28.67 | |||
| parameterMetadata | |
100.00% |
19 / 19 |
|
100.00% |
10 / 10 |
|
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
5 | |||
| columnName | |
100.00% |
18 / 18 |
|
92.31% |
12 / 13 |
|
50.00% |
4 / 8 |
|
100.00% |
1 / 1 |
8.12 | |||
| typeMetadata | |
100.00% |
21 / 21 |
|
100.00% |
12 / 12 |
|
57.14% |
4 / 7 |
|
100.00% |
1 / 1 |
8.83 | |||
| namedTypeMetadata | |
100.00% |
21 / 21 |
|
92.86% |
13 / 14 |
|
75.00% |
6 / 8 |
|
100.00% |
1 / 1 |
6.56 | |||
| isBuiltinTypeName | |
100.00% |
20 / 20 |
|
75.00% |
3 / 4 |
|
50.00% |
1 / 2 |
|
100.00% |
1 / 1 |
1.12 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Celemas\Quma\Hydration; |
| 6 | |
| 7 | use BackedEnum; |
| 8 | use Celemas\Quma\Column; |
| 9 | use Celemas\Quma\Exception\InvalidHydrationTarget; |
| 10 | use Celemas\Quma\Hydratable; |
| 11 | use DateTime; |
| 12 | use DateTimeImmutable; |
| 13 | use ReflectionClass; |
| 14 | use ReflectionNamedType; |
| 15 | use ReflectionParameter; |
| 16 | use ReflectionType; |
| 17 | use ReflectionUnionType; |
| 18 | use Throwable; |
| 19 | |
| 20 | /** @internal */ |
| 21 | final class StaticReflectionCache implements MetadataCache |
| 22 | { |
| 23 | /** @var array<class-string, ClassMetadata> */ |
| 24 | private static array $entries = []; |
| 25 | |
| 26 | /** @param class-string $class */ |
| 27 | #[\Override] |
| 28 | public function metadata(string $class): ClassMetadata |
| 29 | { |
| 30 | if (!array_key_exists($class, self::$entries)) { |
| 31 | self::$entries[$class] = $this->build($class); |
| 32 | } |
| 33 | |
| 34 | return self::$entries[$class]; |
| 35 | } |
| 36 | |
| 37 | /** @psalm-suppress PossiblyUnusedMethod */ |
| 38 | public static function reset(): void |
| 39 | { |
| 40 | self::$entries = []; |
| 41 | } |
| 42 | |
| 43 | /** @param class-string $class */ |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 47 | throw InvalidHydrationTarget::forTarget( |
| 48 | $class, |
| 49 | reason: 'target is not an existing class', |
| 50 | ); |
| 51 | } |
| 52 | |
| 53 | if (is_a($class, Hydratable::class, true)) { |
| 54 | return new ClassMetadata($class, true, true, null); |
| 55 | } |
| 56 | |
| 57 | $reflection = new ReflectionClass($class); |
| 58 | |
| 59 | if (!$reflection->isInstantiable()) { |
| 60 | throw InvalidHydrationTarget::forTarget($class, reason: 'target is not instantiable'); |
| 61 | } |
| 62 | |
| 63 | $constructor = $reflection->getConstructor(); |
| 64 | |
| 65 | if ($constructor === null) { |
| 66 | return new ClassMetadata($class, false, true, []); |
| 67 | } |
| 68 | |
| 69 | $parameters = []; |
| 70 | |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 72 | $parameters[] = $this->parameterMetadata($class, $parameter); |
| 73 | } |
| 74 | |
| 75 | usort( |
| 76 | $parameters, |
| 77 | static fn(ParameterMetadata $a, ParameterMetadata $b): int => $a->position <=> $b->position, |
| 78 | ); |
| 79 | |
| 80 | return new ClassMetadata($class, false, true, $parameters); |
| 81 | } |
| 82 | |
| 83 | /** @param class-string $class */ |
| 84 | private function parameterMetadata( |
| 85 | string $class, |
| 86 | ReflectionParameter $parameter, |
| 87 | ): ParameterMetadata { |
| 88 | $name = $parameter->getName(); |
| 89 | |
| 90 | if ($parameter->isVariadic()) { |
| 91 | throw InvalidHydrationTarget::forParameter($class, $name, 'is variadic'); |
| 92 | } |
| 93 | |
| 94 | if ($parameter->isPassedByReference()) { |
| 95 | throw InvalidHydrationTarget::forParameter($class, $name, 'is by-reference'); |
| 96 | } |
| 97 | |
| 98 | $type = $parameter->getType(); |
| 99 | |
| 100 | if ($type === null) { |
| 101 | throw InvalidHydrationTarget::forParameter($class, $name, 'has no declared type'); |
| 102 | } |
| 103 | |
| 104 | $column = $this->columnName($class, $parameter, $name); |
| 105 | $hasDefault = $parameter->isDefaultValueAvailable(); |
| 106 | |
| 107 | return new ParameterMetadata( |
| 108 | $name, |
| 109 | $column, |
| 110 | $this->typeMetadata($class, $name, $type), |
| 111 | $type->allowsNull(), |
| 112 | $hasDefault, |
| 113 | $hasDefault ? $parameter->getDefaultValue() : null, |
| 114 | $parameter->getPosition(), |
| 115 | ); |
| 116 | } |
| 117 | |
| 118 | /** |
| 119 | * @param class-string $class |
| 120 | * @param non-empty-string $parameterName |
| 121 | * @return non-empty-string |
| 122 | */ |
| 123 | private function columnName( |
| 124 | string $class, |
| 125 | ReflectionParameter $parameter, |
| 126 | string $parameterName, |
| 127 | ): string { |
| 128 | $attributes = $parameter->getAttributes(Column::class); |
| 129 | |
| 130 | if ($attributes === []) { |
| 131 | return $parameterName; |
| 132 | } |
| 133 | |
| 134 | try { |
| 135 | $column = $attributes[0]->newInstance(); |
| 136 | } catch (Throwable) { |
| 137 | throw InvalidHydrationTarget::forParameter( |
| 138 | $class, |
| 139 | $parameterName, |
| 140 | 'has an invalid #[Column] attribute', |
| 141 | ); |
| 142 | } |
| 143 | |
| 144 | $columnName = $column->name; |
| 145 | |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 147 | throw InvalidHydrationTarget::forParameter( |
| 148 | $class, |
| 149 | $parameterName, |
| 150 | 'has an empty #[Column] name', |
| 151 | ); |
| 152 | } |
| 153 | |
| 154 | return $columnName; |
| 155 | } |
| 156 | |
| 157 | /** |
| 158 | * @param class-string $class |
| 159 | * @param non-empty-string $parameterName |
| 160 | */ |
| 161 | private function typeMetadata( |
| 162 | string $class, |
| 163 | string $parameterName, |
| 164 | ReflectionType $type, |
| 165 | ): TypeMetadata { |
| 166 | if ($type instanceof ReflectionNamedType) { |
| 167 | $name = $this->namedTypeMetadata($class, $parameterName, $type); |
| 168 | |
| 169 | return new TypeMetadata('named', $type->allowsNull(), [$name]); |
| 170 | } |
| 171 | |
| 172 | if ($type instanceof ReflectionUnionType) { |
| 173 | $names = []; |
| 174 | |
| 175 | foreach ($type->getTypes() as $inner) { |
| 176 | if (!$inner instanceof ReflectionNamedType) { |
| 177 | throw InvalidHydrationTarget::forParameter( |
| 178 | $class, |
| 179 | $parameterName, |
| 180 | 'uses an unsupported intersection or DNF type', |
| 181 | ); |
| 182 | } |
| 183 | |
| 184 | if (strtolower($inner->getName()) === 'null') { |
| 185 | continue; |
| 186 | } |
| 187 | |
| 188 | $names[] = $this->namedTypeMetadata($class, $parameterName, $inner); |
| 189 | } |
| 190 | |
| 191 | /** |
| 192 | * ReflectionUnionType always contains at least one non-null arm in valid PHP; |
| 193 | * Psalm cannot infer that after the runtime null-arm filter above. |
| 194 | * |
| 195 | * @var non-empty-list<NamedTypeMetadata> $names |
| 196 | */ |
| 197 | return new TypeMetadata('union', $type->allowsNull(), $names); |
| 198 | } |
| 199 | |
| 200 | throw InvalidHydrationTarget::forParameter( |
| 201 | $class, |
| 202 | $parameterName, |
| 203 | 'uses an unsupported intersection type', |
| 204 | ); |
| 205 | } |
| 206 | |
| 207 | /** |
| 208 | * @param class-string $class |
| 209 | * @param non-empty-string $parameterName |
| 210 | */ |
| 211 | private function namedTypeMetadata( |
| 212 | string $class, |
| 213 | string $parameterName, |
| 214 | ReflectionNamedType $type, |
| 215 | ): NamedTypeMetadata { |
| 216 | $name = $type->getName(); |
| 217 | $lower = strtolower($name); |
| 218 | |
| 219 | if ($type->isBuiltin()) { |
| 220 | if (in_array($lower, ['int', 'float', 'bool', 'string'], true)) { |
| 221 | return new NamedTypeMetadata($lower, true, null, $lower, null, null); |
| 222 | } |
| 223 | |
| 224 | throw InvalidHydrationTarget::forParameter( |
| 225 | $class, |
| 226 | $parameterName, |
| 227 | "uses unsupported type {$name}", |
| 228 | ); |
| 229 | } |
| 230 | |
| 231 | if ($name === DateTimeImmutable::class) { |
| 232 | return new NamedTypeMetadata($name, false, DateTimeImmutable::class, null, 'immutable', null); |
| 233 | } |
| 234 | |
| 235 | if ($name === DateTime::class) { |
| 236 | return new NamedTypeMetadata($name, false, DateTime::class, null, 'mutable', null); |
| 237 | } |
| 238 | |
| 239 | if (is_subclass_of($name, BackedEnum::class)) { |
| 240 | return new NamedTypeMetadata($name, false, $name, null, null, $name); |
| 241 | } |
| 242 | |
| 243 | throw InvalidHydrationTarget::forParameter( |
| 244 | $class, |
| 245 | $parameterName, |
| 246 | "uses unsupported type {$name}", |
| 247 | ); |
| 248 | } |
| 249 | |
| 250 | private function isBuiltinTypeName(string $target): bool |
| 251 | { |
| 252 | return in_array( |
| 253 | strtolower($target), |
| 254 | [ |
| 255 | 'array', |
| 256 | 'bool', |
| 257 | 'callable', |
| 258 | 'false', |
| 259 | 'float', |
| 260 | 'int', |
| 261 | 'iterable', |
| 262 | 'mixed', |
| 263 | 'never', |
| 264 | 'null', |
| 265 | 'object', |
| 266 | 'string', |
| 267 | 'true', |
| 268 | 'void', |
| 269 | ], |
| 270 | true, |
| 271 | ); |
| 272 | } |
| 273 | } |
Below are the source code lines that represent each code path as identified by Xdebug. Please note a path is not
necessarily coterminous with a line, a line may contain multiple paths and therefore show up more than once.
Please also be aware that some paths may include implicit rather than explicit branches, e.g. an if statement
always has an else as part of its logical flow even if you didn't write one.
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 47 | throw InvalidHydrationTarget::forTarget( |
| 48 | $class, |
| 49 | reason: 'target is not an existing class', |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 53 | if (is_a($class, Hydratable::class, true)) { |
| 54 | return new ClassMetadata($class, true, true, null); |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 53 | if (is_a($class, Hydratable::class, true)) { |
| 57 | $reflection = new ReflectionClass($class); |
| 58 | |
| 59 | if (!$reflection->isInstantiable()) { |
| 60 | throw InvalidHydrationTarget::forTarget($class, reason: 'target is not instantiable'); |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 53 | if (is_a($class, Hydratable::class, true)) { |
| 57 | $reflection = new ReflectionClass($class); |
| 58 | |
| 59 | if (!$reflection->isInstantiable()) { |
| 63 | $constructor = $reflection->getConstructor(); |
| 64 | |
| 65 | if ($constructor === null) { |
| 66 | return new ClassMetadata($class, false, true, []); |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 53 | if (is_a($class, Hydratable::class, true)) { |
| 57 | $reflection = new ReflectionClass($class); |
| 58 | |
| 59 | if (!$reflection->isInstantiable()) { |
| 63 | $constructor = $reflection->getConstructor(); |
| 64 | |
| 65 | if ($constructor === null) { |
| 69 | $parameters = []; |
| 70 | |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 72 | $parameters[] = $this->parameterMetadata($class, $parameter); |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 72 | $parameters[] = $this->parameterMetadata($class, $parameter); |
| 73 | } |
| 74 | |
| 75 | usort( |
| 76 | $parameters, |
| 77 | static fn(ParameterMetadata $a, ParameterMetadata $b): int => $a->position <=> $b->position, |
| 78 | ); |
| 79 | |
| 80 | return new ClassMetadata($class, false, true, $parameters); |
| 81 | } |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 53 | if (is_a($class, Hydratable::class, true)) { |
| 57 | $reflection = new ReflectionClass($class); |
| 58 | |
| 59 | if (!$reflection->isInstantiable()) { |
| 63 | $constructor = $reflection->getConstructor(); |
| 64 | |
| 65 | if ($constructor === null) { |
| 69 | $parameters = []; |
| 70 | |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 72 | $parameters[] = $this->parameterMetadata($class, $parameter); |
| 73 | } |
| 74 | |
| 75 | usort( |
| 76 | $parameters, |
| 77 | static fn(ParameterMetadata $a, ParameterMetadata $b): int => $a->position <=> $b->position, |
| 78 | ); |
| 79 | |
| 80 | return new ClassMetadata($class, false, true, $parameters); |
| 81 | } |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 53 | if (is_a($class, Hydratable::class, true)) { |
| 57 | $reflection = new ReflectionClass($class); |
| 58 | |
| 59 | if (!$reflection->isInstantiable()) { |
| 63 | $constructor = $reflection->getConstructor(); |
| 64 | |
| 65 | if ($constructor === null) { |
| 69 | $parameters = []; |
| 70 | |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 72 | $parameters[] = $this->parameterMetadata($class, $parameter); |
| 73 | } |
| 74 | |
| 75 | usort( |
| 76 | $parameters, |
| 77 | static fn(ParameterMetadata $a, ParameterMetadata $b): int => $a->position <=> $b->position, |
| 78 | ); |
| 79 | |
| 80 | return new ClassMetadata($class, false, true, $parameters); |
| 81 | } |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 47 | throw InvalidHydrationTarget::forTarget( |
| 48 | $class, |
| 49 | reason: 'target is not an existing class', |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 53 | if (is_a($class, Hydratable::class, true)) { |
| 54 | return new ClassMetadata($class, true, true, null); |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 53 | if (is_a($class, Hydratable::class, true)) { |
| 57 | $reflection = new ReflectionClass($class); |
| 58 | |
| 59 | if (!$reflection->isInstantiable()) { |
| 60 | throw InvalidHydrationTarget::forTarget($class, reason: 'target is not instantiable'); |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 53 | if (is_a($class, Hydratable::class, true)) { |
| 57 | $reflection = new ReflectionClass($class); |
| 58 | |
| 59 | if (!$reflection->isInstantiable()) { |
| 63 | $constructor = $reflection->getConstructor(); |
| 64 | |
| 65 | if ($constructor === null) { |
| 66 | return new ClassMetadata($class, false, true, []); |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 53 | if (is_a($class, Hydratable::class, true)) { |
| 57 | $reflection = new ReflectionClass($class); |
| 58 | |
| 59 | if (!$reflection->isInstantiable()) { |
| 63 | $constructor = $reflection->getConstructor(); |
| 64 | |
| 65 | if ($constructor === null) { |
| 69 | $parameters = []; |
| 70 | |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 72 | $parameters[] = $this->parameterMetadata($class, $parameter); |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 72 | $parameters[] = $this->parameterMetadata($class, $parameter); |
| 73 | } |
| 74 | |
| 75 | usort( |
| 76 | $parameters, |
| 77 | static fn(ParameterMetadata $a, ParameterMetadata $b): int => $a->position <=> $b->position, |
| 78 | ); |
| 79 | |
| 80 | return new ClassMetadata($class, false, true, $parameters); |
| 81 | } |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 53 | if (is_a($class, Hydratable::class, true)) { |
| 57 | $reflection = new ReflectionClass($class); |
| 58 | |
| 59 | if (!$reflection->isInstantiable()) { |
| 63 | $constructor = $reflection->getConstructor(); |
| 64 | |
| 65 | if ($constructor === null) { |
| 69 | $parameters = []; |
| 70 | |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 72 | $parameters[] = $this->parameterMetadata($class, $parameter); |
| 73 | } |
| 74 | |
| 75 | usort( |
| 76 | $parameters, |
| 77 | static fn(ParameterMetadata $a, ParameterMetadata $b): int => $a->position <=> $b->position, |
| 78 | ); |
| 79 | |
| 80 | return new ClassMetadata($class, false, true, $parameters); |
| 81 | } |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 53 | if (is_a($class, Hydratable::class, true)) { |
| 57 | $reflection = new ReflectionClass($class); |
| 58 | |
| 59 | if (!$reflection->isInstantiable()) { |
| 63 | $constructor = $reflection->getConstructor(); |
| 64 | |
| 65 | if ($constructor === null) { |
| 69 | $parameters = []; |
| 70 | |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 72 | $parameters[] = $this->parameterMetadata($class, $parameter); |
| 73 | } |
| 74 | |
| 75 | usort( |
| 76 | $parameters, |
| 77 | static fn(ParameterMetadata $a, ParameterMetadata $b): int => $a->position <=> $b->position, |
| 78 | ); |
| 79 | |
| 80 | return new ClassMetadata($class, false, true, $parameters); |
| 81 | } |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 47 | throw InvalidHydrationTarget::forTarget( |
| 48 | $class, |
| 49 | reason: 'target is not an existing class', |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 53 | if (is_a($class, Hydratable::class, true)) { |
| 54 | return new ClassMetadata($class, true, true, null); |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 53 | if (is_a($class, Hydratable::class, true)) { |
| 57 | $reflection = new ReflectionClass($class); |
| 58 | |
| 59 | if (!$reflection->isInstantiable()) { |
| 60 | throw InvalidHydrationTarget::forTarget($class, reason: 'target is not instantiable'); |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 53 | if (is_a($class, Hydratable::class, true)) { |
| 57 | $reflection = new ReflectionClass($class); |
| 58 | |
| 59 | if (!$reflection->isInstantiable()) { |
| 63 | $constructor = $reflection->getConstructor(); |
| 64 | |
| 65 | if ($constructor === null) { |
| 66 | return new ClassMetadata($class, false, true, []); |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 53 | if (is_a($class, Hydratable::class, true)) { |
| 57 | $reflection = new ReflectionClass($class); |
| 58 | |
| 59 | if (!$reflection->isInstantiable()) { |
| 63 | $constructor = $reflection->getConstructor(); |
| 64 | |
| 65 | if ($constructor === null) { |
| 69 | $parameters = []; |
| 70 | |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 72 | $parameters[] = $this->parameterMetadata($class, $parameter); |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 72 | $parameters[] = $this->parameterMetadata($class, $parameter); |
| 73 | } |
| 74 | |
| 75 | usort( |
| 76 | $parameters, |
| 77 | static fn(ParameterMetadata $a, ParameterMetadata $b): int => $a->position <=> $b->position, |
| 78 | ); |
| 79 | |
| 80 | return new ClassMetadata($class, false, true, $parameters); |
| 81 | } |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 53 | if (is_a($class, Hydratable::class, true)) { |
| 57 | $reflection = new ReflectionClass($class); |
| 58 | |
| 59 | if (!$reflection->isInstantiable()) { |
| 63 | $constructor = $reflection->getConstructor(); |
| 64 | |
| 65 | if ($constructor === null) { |
| 69 | $parameters = []; |
| 70 | |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 72 | $parameters[] = $this->parameterMetadata($class, $parameter); |
| 73 | } |
| 74 | |
| 75 | usort( |
| 76 | $parameters, |
| 77 | static fn(ParameterMetadata $a, ParameterMetadata $b): int => $a->position <=> $b->position, |
| 78 | ); |
| 79 | |
| 80 | return new ClassMetadata($class, false, true, $parameters); |
| 81 | } |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 46 | if ($this->isBuiltinTypeName($class) || !class_exists($class)) { |
| 53 | if (is_a($class, Hydratable::class, true)) { |
| 57 | $reflection = new ReflectionClass($class); |
| 58 | |
| 59 | if (!$reflection->isInstantiable()) { |
| 63 | $constructor = $reflection->getConstructor(); |
| 64 | |
| 65 | if ($constructor === null) { |
| 69 | $parameters = []; |
| 70 | |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 71 | foreach ($constructor->getParameters() as $parameter) { |
| 72 | $parameters[] = $this->parameterMetadata($class, $parameter); |
| 73 | } |
| 74 | |
| 75 | usort( |
| 76 | $parameters, |
| 77 | static fn(ParameterMetadata $a, ParameterMetadata $b): int => $a->position <=> $b->position, |
| 78 | ); |
| 79 | |
| 80 | return new ClassMetadata($class, false, true, $parameters); |
| 81 | } |
| 124 | string $class, |
| 125 | ReflectionParameter $parameter, |
| 126 | string $parameterName, |
| 127 | ): string { |
| 128 | $attributes = $parameter->getAttributes(Column::class); |
| 129 | |
| 130 | if ($attributes === []) { |
| 131 | return $parameterName; |
| 124 | string $class, |
| 125 | ReflectionParameter $parameter, |
| 126 | string $parameterName, |
| 127 | ): string { |
| 128 | $attributes = $parameter->getAttributes(Column::class); |
| 129 | |
| 130 | if ($attributes === []) { |
| 134 | try { |
| 135 | $column = $attributes[0]->newInstance(); |
| 144 | $columnName = $column->name; |
| 145 | |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 147 | throw InvalidHydrationTarget::forParameter( |
| 148 | $class, |
| 149 | $parameterName, |
| 150 | 'has an empty #[Column] name', |
| 124 | string $class, |
| 125 | ReflectionParameter $parameter, |
| 126 | string $parameterName, |
| 127 | ): string { |
| 128 | $attributes = $parameter->getAttributes(Column::class); |
| 129 | |
| 130 | if ($attributes === []) { |
| 134 | try { |
| 135 | $column = $attributes[0]->newInstance(); |
| 144 | $columnName = $column->name; |
| 145 | |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 154 | return $columnName; |
| 155 | } |
| 124 | string $class, |
| 125 | ReflectionParameter $parameter, |
| 126 | string $parameterName, |
| 127 | ): string { |
| 128 | $attributes = $parameter->getAttributes(Column::class); |
| 129 | |
| 130 | if ($attributes === []) { |
| 134 | try { |
| 135 | $column = $attributes[0]->newInstance(); |
| 144 | $columnName = $column->name; |
| 145 | |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 147 | throw InvalidHydrationTarget::forParameter( |
| 148 | $class, |
| 149 | $parameterName, |
| 150 | 'has an empty #[Column] name', |
| 124 | string $class, |
| 125 | ReflectionParameter $parameter, |
| 126 | string $parameterName, |
| 127 | ): string { |
| 128 | $attributes = $parameter->getAttributes(Column::class); |
| 129 | |
| 130 | if ($attributes === []) { |
| 134 | try { |
| 135 | $column = $attributes[0]->newInstance(); |
| 144 | $columnName = $column->name; |
| 145 | |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 154 | return $columnName; |
| 155 | } |
| 124 | string $class, |
| 125 | ReflectionParameter $parameter, |
| 126 | string $parameterName, |
| 127 | ): string { |
| 128 | $attributes = $parameter->getAttributes(Column::class); |
| 129 | |
| 130 | if ($attributes === []) { |
| 134 | try { |
| 135 | $column = $attributes[0]->newInstance(); |
| 144 | $columnName = $column->name; |
| 145 | |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 147 | throw InvalidHydrationTarget::forParameter( |
| 148 | $class, |
| 149 | $parameterName, |
| 150 | 'has an empty #[Column] name', |
| 124 | string $class, |
| 125 | ReflectionParameter $parameter, |
| 126 | string $parameterName, |
| 127 | ): string { |
| 128 | $attributes = $parameter->getAttributes(Column::class); |
| 129 | |
| 130 | if ($attributes === []) { |
| 134 | try { |
| 135 | $column = $attributes[0]->newInstance(); |
| 144 | $columnName = $column->name; |
| 145 | |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 146 | if ($columnName === '' || trim($columnName) === '') { |
| 154 | return $columnName; |
| 155 | } |
| 136 | } catch (Throwable) { |
| 137 | throw InvalidHydrationTarget::forParameter( |
| 138 | $class, |
| 139 | $parameterName, |
| 140 | 'has an invalid #[Column] attribute', |
| 250 | private function isBuiltinTypeName(string $target): bool |
| 251 | { |
| 252 | return in_array( |
| 252 | return in_array( |
| 270 | true, |
| 271 | ); |
| 272 | } |
| 250 | private function isBuiltinTypeName(string $target): bool |
| 251 | { |
| 252 | return in_array( |
| 253 | strtolower($target), |
| 254 | [ |
| 255 | 'array', |
| 256 | 'bool', |
| 257 | 'callable', |
| 258 | 'false', |
| 259 | 'float', |
| 260 | 'int', |
| 261 | 'iterable', |
| 262 | 'mixed', |
| 263 | 'never', |
| 264 | 'null', |
| 265 | 'object', |
| 266 | 'string', |
| 267 | 'true', |
| 268 | 'void', |
| 269 | ], |
| 270 | true, |
| 270 | true, |
| 271 | ); |
| 272 | } |
| 28 | public function metadata(string $class): ClassMetadata |
| 29 | { |
| 30 | if (!array_key_exists($class, self::$entries)) { |
| 31 | self::$entries[$class] = $this->build($class); |
| 32 | } |
| 33 | |
| 34 | return self::$entries[$class]; |
| 34 | return self::$entries[$class]; |
| 35 | } |
| 28 | public function metadata(string $class): ClassMetadata |
| 29 | { |
| 30 | if (!array_key_exists($class, self::$entries)) { |
| 34 | return self::$entries[$class]; |
| 35 | } |
| 212 | string $class, |
| 213 | string $parameterName, |
| 214 | ReflectionNamedType $type, |
| 215 | ): NamedTypeMetadata { |
| 216 | $name = $type->getName(); |
| 217 | $lower = strtolower($name); |
| 218 | |
| 219 | if ($type->isBuiltin()) { |
| 220 | if (in_array($lower, ['int', 'float', 'bool', 'string'], true)) { |
| 220 | if (in_array($lower, ['int', 'float', 'bool', 'string'], true)) { |
| 220 | if (in_array($lower, ['int', 'float', 'bool', 'string'], true)) { |
| 221 | return new NamedTypeMetadata($lower, true, null, $lower, null, null); |
| 212 | string $class, |
| 213 | string $parameterName, |
| 214 | ReflectionNamedType $type, |
| 215 | ): NamedTypeMetadata { |
| 216 | $name = $type->getName(); |
| 217 | $lower = strtolower($name); |
| 218 | |
| 219 | if ($type->isBuiltin()) { |
| 220 | if (in_array($lower, ['int', 'float', 'bool', 'string'], true)) { |
| 220 | if (in_array($lower, ['int', 'float', 'bool', 'string'], true)) { |
| 220 | if (in_array($lower, ['int', 'float', 'bool', 'string'], true)) { |
| 224 | throw InvalidHydrationTarget::forParameter( |
| 225 | $class, |
| 226 | $parameterName, |
| 227 | "uses unsupported type {$name}", |
| 212 | string $class, |
| 213 | string $parameterName, |
| 214 | ReflectionNamedType $type, |
| 215 | ): NamedTypeMetadata { |
| 216 | $name = $type->getName(); |
| 217 | $lower = strtolower($name); |
| 218 | |
| 219 | if ($type->isBuiltin()) { |
| 220 | if (in_array($lower, ['int', 'float', 'bool', 'string'], true)) { |
| 220 | if (in_array($lower, ['int', 'float', 'bool', 'string'], true)) { |
| 220 | if (in_array($lower, ['int', 'float', 'bool', 'string'], true)) { |
| 221 | return new NamedTypeMetadata($lower, true, null, $lower, null, null); |
| 212 | string $class, |
| 213 | string $parameterName, |
| 214 | ReflectionNamedType $type, |
| 215 | ): NamedTypeMetadata { |
| 216 | $name = $type->getName(); |
| 217 | $lower = strtolower($name); |
| 218 | |
| 219 | if ($type->isBuiltin()) { |
| 220 | if (in_array($lower, ['int', 'float', 'bool', 'string'], true)) { |
| 220 | if (in_array($lower, ['int', 'float', 'bool', 'string'], true)) { |
| 220 | if (in_array($lower, ['int', 'float', 'bool', 'string'], true)) { |
| 224 | throw InvalidHydrationTarget::forParameter( |
| 225 | $class, |
| 226 | $parameterName, |
| 227 | "uses unsupported type {$name}", |
| 212 | string $class, |
| 213 | string $parameterName, |
| 214 | ReflectionNamedType $type, |
| 215 | ): NamedTypeMetadata { |
| 216 | $name = $type->getName(); |
| 217 | $lower = strtolower($name); |
| 218 | |
| 219 | if ($type->isBuiltin()) { |
| 231 | if ($name === DateTimeImmutable::class) { |
| 232 | return new NamedTypeMetadata($name, false, DateTimeImmutable::class, null, 'immutable', null); |
| 212 | string $class, |
| 213 | string $parameterName, |
| 214 | ReflectionNamedType $type, |
| 215 | ): NamedTypeMetadata { |
| 216 | $name = $type->getName(); |
| 217 | $lower = strtolower($name); |
| 218 | |
| 219 | if ($type->isBuiltin()) { |
| 231 | if ($name === DateTimeImmutable::class) { |
| 235 | if ($name === DateTime::class) { |
| 236 | return new NamedTypeMetadata($name, false, DateTime::class, null, 'mutable', null); |
| 212 | string $class, |
| 213 | string $parameterName, |
| 214 | ReflectionNamedType $type, |
| 215 | ): NamedTypeMetadata { |
| 216 | $name = $type->getName(); |
| 217 | $lower = strtolower($name); |
| 218 | |
| 219 | if ($type->isBuiltin()) { |
| 231 | if ($name === DateTimeImmutable::class) { |
| 235 | if ($name === DateTime::class) { |
| 239 | if (is_subclass_of($name, BackedEnum::class)) { |
| 240 | return new NamedTypeMetadata($name, false, $name, null, null, $name); |
| 212 | string $class, |
| 213 | string $parameterName, |
| 214 | ReflectionNamedType $type, |
| 215 | ): NamedTypeMetadata { |
| 216 | $name = $type->getName(); |
| 217 | $lower = strtolower($name); |
| 218 | |
| 219 | if ($type->isBuiltin()) { |
| 231 | if ($name === DateTimeImmutable::class) { |
| 235 | if ($name === DateTime::class) { |
| 239 | if (is_subclass_of($name, BackedEnum::class)) { |
| 243 | throw InvalidHydrationTarget::forParameter( |
| 244 | $class, |
| 245 | $parameterName, |
| 246 | "uses unsupported type {$name}", |
| 247 | ); |
| 248 | } |
| 85 | string $class, |
| 86 | ReflectionParameter $parameter, |
| 87 | ): ParameterMetadata { |
| 88 | $name = $parameter->getName(); |
| 89 | |
| 90 | if ($parameter->isVariadic()) { |
| 91 | throw InvalidHydrationTarget::forParameter($class, $name, 'is variadic'); |
| 85 | string $class, |
| 86 | ReflectionParameter $parameter, |
| 87 | ): ParameterMetadata { |
| 88 | $name = $parameter->getName(); |
| 89 | |
| 90 | if ($parameter->isVariadic()) { |
| 94 | if ($parameter->isPassedByReference()) { |
| 95 | throw InvalidHydrationTarget::forParameter($class, $name, 'is by-reference'); |
| 85 | string $class, |
| 86 | ReflectionParameter $parameter, |
| 87 | ): ParameterMetadata { |
| 88 | $name = $parameter->getName(); |
| 89 | |
| 90 | if ($parameter->isVariadic()) { |
| 94 | if ($parameter->isPassedByReference()) { |
| 98 | $type = $parameter->getType(); |
| 99 | |
| 100 | if ($type === null) { |
| 101 | throw InvalidHydrationTarget::forParameter($class, $name, 'has no declared type'); |
| 85 | string $class, |
| 86 | ReflectionParameter $parameter, |
| 87 | ): ParameterMetadata { |
| 88 | $name = $parameter->getName(); |
| 89 | |
| 90 | if ($parameter->isVariadic()) { |
| 94 | if ($parameter->isPassedByReference()) { |
| 98 | $type = $parameter->getType(); |
| 99 | |
| 100 | if ($type === null) { |
| 104 | $column = $this->columnName($class, $parameter, $name); |
| 105 | $hasDefault = $parameter->isDefaultValueAvailable(); |
| 106 | |
| 107 | return new ParameterMetadata( |
| 108 | $name, |
| 109 | $column, |
| 110 | $this->typeMetadata($class, $name, $type), |
| 111 | $type->allowsNull(), |
| 112 | $hasDefault, |
| 113 | $hasDefault ? $parameter->getDefaultValue() : null, |
| 113 | $hasDefault ? $parameter->getDefaultValue() : null, |
| 113 | $hasDefault ? $parameter->getDefaultValue() : null, |
| 114 | $parameter->getPosition(), |
| 115 | ); |
| 116 | } |
| 85 | string $class, |
| 86 | ReflectionParameter $parameter, |
| 87 | ): ParameterMetadata { |
| 88 | $name = $parameter->getName(); |
| 89 | |
| 90 | if ($parameter->isVariadic()) { |
| 94 | if ($parameter->isPassedByReference()) { |
| 98 | $type = $parameter->getType(); |
| 99 | |
| 100 | if ($type === null) { |
| 104 | $column = $this->columnName($class, $parameter, $name); |
| 105 | $hasDefault = $parameter->isDefaultValueAvailable(); |
| 106 | |
| 107 | return new ParameterMetadata( |
| 108 | $name, |
| 109 | $column, |
| 110 | $this->typeMetadata($class, $name, $type), |
| 111 | $type->allowsNull(), |
| 112 | $hasDefault, |
| 113 | $hasDefault ? $parameter->getDefaultValue() : null, |
| 113 | $hasDefault ? $parameter->getDefaultValue() : null, |
| 113 | $hasDefault ? $parameter->getDefaultValue() : null, |
| 114 | $parameter->getPosition(), |
| 115 | ); |
| 116 | } |
| 40 | self::$entries = []; |
| 41 | } |
| 162 | string $class, |
| 163 | string $parameterName, |
| 164 | ReflectionType $type, |
| 165 | ): TypeMetadata { |
| 166 | if ($type instanceof ReflectionNamedType) { |
| 167 | $name = $this->namedTypeMetadata($class, $parameterName, $type); |
| 168 | |
| 169 | return new TypeMetadata('named', $type->allowsNull(), [$name]); |
| 162 | string $class, |
| 163 | string $parameterName, |
| 164 | ReflectionType $type, |
| 165 | ): TypeMetadata { |
| 166 | if ($type instanceof ReflectionNamedType) { |
| 172 | if ($type instanceof ReflectionUnionType) { |
| 173 | $names = []; |
| 174 | |
| 175 | foreach ($type->getTypes() as $inner) { |
| 175 | foreach ($type->getTypes() as $inner) { |
| 176 | if (!$inner instanceof ReflectionNamedType) { |
| 177 | throw InvalidHydrationTarget::forParameter( |
| 178 | $class, |
| 179 | $parameterName, |
| 180 | 'uses an unsupported intersection or DNF type', |
| 162 | string $class, |
| 163 | string $parameterName, |
| 164 | ReflectionType $type, |
| 165 | ): TypeMetadata { |
| 166 | if ($type instanceof ReflectionNamedType) { |
| 172 | if ($type instanceof ReflectionUnionType) { |
| 173 | $names = []; |
| 174 | |
| 175 | foreach ($type->getTypes() as $inner) { |
| 175 | foreach ($type->getTypes() as $inner) { |
| 176 | if (!$inner instanceof ReflectionNamedType) { |
| 184 | if (strtolower($inner->getName()) === 'null') { |
| 185 | continue; |
| 175 | foreach ($type->getTypes() as $inner) { |
| 175 | foreach ($type->getTypes() as $inner) { |
| 176 | if (!$inner instanceof ReflectionNamedType) { |
| 177 | throw InvalidHydrationTarget::forParameter( |
| 178 | $class, |
| 179 | $parameterName, |
| 180 | 'uses an unsupported intersection or DNF type', |
| 181 | ); |
| 182 | } |
| 183 | |
| 184 | if (strtolower($inner->getName()) === 'null') { |
| 185 | continue; |
| 186 | } |
| 187 | |
| 188 | $names[] = $this->namedTypeMetadata($class, $parameterName, $inner); |
| 189 | } |
| 190 | |
| 191 | /** |
| 192 | * ReflectionUnionType always contains at least one non-null arm in valid PHP; |
| 193 | * Psalm cannot infer that after the runtime null-arm filter above. |
| 194 | * |
| 195 | * @var non-empty-list<NamedTypeMetadata> $names |
| 196 | */ |
| 197 | return new TypeMetadata('union', $type->allowsNull(), $names); |
| 162 | string $class, |
| 163 | string $parameterName, |
| 164 | ReflectionType $type, |
| 165 | ): TypeMetadata { |
| 166 | if ($type instanceof ReflectionNamedType) { |
| 172 | if ($type instanceof ReflectionUnionType) { |
| 173 | $names = []; |
| 174 | |
| 175 | foreach ($type->getTypes() as $inner) { |
| 175 | foreach ($type->getTypes() as $inner) { |
| 176 | if (!$inner instanceof ReflectionNamedType) { |
| 184 | if (strtolower($inner->getName()) === 'null') { |
| 175 | foreach ($type->getTypes() as $inner) { |
| 176 | if (!$inner instanceof ReflectionNamedType) { |
| 177 | throw InvalidHydrationTarget::forParameter( |
| 178 | $class, |
| 179 | $parameterName, |
| 180 | 'uses an unsupported intersection or DNF type', |
| 181 | ); |
| 182 | } |
| 183 | |
| 184 | if (strtolower($inner->getName()) === 'null') { |
| 185 | continue; |
| 186 | } |
| 187 | |
| 188 | $names[] = $this->namedTypeMetadata($class, $parameterName, $inner); |
| 175 | foreach ($type->getTypes() as $inner) { |
| 175 | foreach ($type->getTypes() as $inner) { |
| 176 | if (!$inner instanceof ReflectionNamedType) { |
| 177 | throw InvalidHydrationTarget::forParameter( |
| 178 | $class, |
| 179 | $parameterName, |
| 180 | 'uses an unsupported intersection or DNF type', |
| 181 | ); |
| 182 | } |
| 183 | |
| 184 | if (strtolower($inner->getName()) === 'null') { |
| 185 | continue; |
| 186 | } |
| 187 | |
| 188 | $names[] = $this->namedTypeMetadata($class, $parameterName, $inner); |
| 189 | } |
| 190 | |
| 191 | /** |
| 192 | * ReflectionUnionType always contains at least one non-null arm in valid PHP; |
| 193 | * Psalm cannot infer that after the runtime null-arm filter above. |
| 194 | * |
| 195 | * @var non-empty-list<NamedTypeMetadata> $names |
| 196 | */ |
| 197 | return new TypeMetadata('union', $type->allowsNull(), $names); |
| 162 | string $class, |
| 163 | string $parameterName, |
| 164 | ReflectionType $type, |
| 165 | ): TypeMetadata { |
| 166 | if ($type instanceof ReflectionNamedType) { |
| 172 | if ($type instanceof ReflectionUnionType) { |
| 173 | $names = []; |
| 174 | |
| 175 | foreach ($type->getTypes() as $inner) { |
| 175 | foreach ($type->getTypes() as $inner) { |
| 175 | foreach ($type->getTypes() as $inner) { |
| 176 | if (!$inner instanceof ReflectionNamedType) { |
| 177 | throw InvalidHydrationTarget::forParameter( |
| 178 | $class, |
| 179 | $parameterName, |
| 180 | 'uses an unsupported intersection or DNF type', |
| 181 | ); |
| 182 | } |
| 183 | |
| 184 | if (strtolower($inner->getName()) === 'null') { |
| 185 | continue; |
| 186 | } |
| 187 | |
| 188 | $names[] = $this->namedTypeMetadata($class, $parameterName, $inner); |
| 189 | } |
| 190 | |
| 191 | /** |
| 192 | * ReflectionUnionType always contains at least one non-null arm in valid PHP; |
| 193 | * Psalm cannot infer that after the runtime null-arm filter above. |
| 194 | * |
| 195 | * @var non-empty-list<NamedTypeMetadata> $names |
| 196 | */ |
| 197 | return new TypeMetadata('union', $type->allowsNull(), $names); |
| 162 | string $class, |
| 163 | string $parameterName, |
| 164 | ReflectionType $type, |
| 165 | ): TypeMetadata { |
| 166 | if ($type instanceof ReflectionNamedType) { |
| 172 | if ($type instanceof ReflectionUnionType) { |
| 173 | $names = []; |
| 174 | |
| 175 | foreach ($type->getTypes() as $inner) { |
| 175 | foreach ($type->getTypes() as $inner) { |
| 176 | if (!$inner instanceof ReflectionNamedType) { |
| 177 | throw InvalidHydrationTarget::forParameter( |
| 178 | $class, |
| 179 | $parameterName, |
| 180 | 'uses an unsupported intersection or DNF type', |
| 181 | ); |
| 182 | } |
| 183 | |
| 184 | if (strtolower($inner->getName()) === 'null') { |
| 185 | continue; |
| 186 | } |
| 187 | |
| 188 | $names[] = $this->namedTypeMetadata($class, $parameterName, $inner); |
| 189 | } |
| 190 | |
| 191 | /** |
| 192 | * ReflectionUnionType always contains at least one non-null arm in valid PHP; |
| 193 | * Psalm cannot infer that after the runtime null-arm filter above. |
| 194 | * |
| 195 | * @var non-empty-list<NamedTypeMetadata> $names |
| 196 | */ |
| 197 | return new TypeMetadata('union', $type->allowsNull(), $names); |
| 162 | string $class, |
| 163 | string $parameterName, |
| 164 | ReflectionType $type, |
| 165 | ): TypeMetadata { |
| 166 | if ($type instanceof ReflectionNamedType) { |
| 172 | if ($type instanceof ReflectionUnionType) { |
| 200 | throw InvalidHydrationTarget::forParameter( |
| 201 | $class, |
| 202 | $parameterName, |
| 203 | 'uses an unsupported intersection type', |
| 204 | ); |
| 205 | } |
| 77 | static fn(ParameterMetadata $a, ParameterMetadata $b): int => $a->position <=> $b->position, |