Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
100.00% |
95 / 95 |
|
95.59% |
65 / 68 |
|
55.77% |
29 / 52 |
|
57.14% |
4 / 7 |
CRAP | |
0.00% |
0 / 1 |
| StaticReflectionCache | |
100.00% |
95 / 95 |
|
95.59% |
65 / 68 |
|
55.77% |
29 / 52 |
|
100.00% |
7 / 7 |
114.16 | |
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% |
20 / 20 |
|
93.33% |
14 / 15 |
|
42.86% |
6 / 14 |
|
100.00% |
1 / 1 |
12.72 | |||
| parameterMetadata | |
100.00% |
18 / 18 |
|
100.00% |
10 / 10 |
|
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
5 | |||
| columnName | |
100.00% |
18 / 18 |
|
92.31% |
12 / 13 |
|
62.50% |
5 / 8 |
|
100.00% |
1 / 1 |
6.32 | |||
| typeMetadata | |
100.00% |
21 / 21 |
|
100.00% |
12 / 12 |
|
57.14% |
4 / 7 |
|
100.00% |
1 / 1 |
8.83 | |||
| namedTypeMetadata | |
100.00% |
14 / 14 |
|
92.86% |
13 / 14 |
|
40.00% |
6 / 15 |
|
100.00% |
1 / 1 |
13.78 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Celema\Quma\Hydration; |
| 6 | |
| 7 | use BackedEnum; |
| 8 | use Celema\Quma\Column; |
| 9 | use Celema\Quma\Exception\InvalidHydrationTarget; |
| 10 | use Celema\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 The tests clear the cache with it. */ |
| 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 | // Builtin type names like `int` are never class names. |
| 47 | if (!class_exists($class)) { |
| 48 | throw InvalidHydrationTarget::forTarget( |
| 49 | $class, |
| 50 | reason: 'target is not an existing class', |
| 51 | ); |
| 52 | } |
| 53 | |
| 54 | if (is_a($class, Hydratable::class, true)) { |
| 55 | return new ClassMetadata($class, true, true, null); |
| 56 | } |
| 57 | |
| 58 | $reflection = new ReflectionClass($class); |
| 59 | |
| 60 | if (!$reflection->isInstantiable()) { |
| 61 | throw InvalidHydrationTarget::forTarget($class, reason: 'target is not instantiable'); |
| 62 | } |
| 63 | |
| 64 | $constructor = $reflection->getConstructor(); |
| 65 | |
| 66 | if ($constructor === null) { |
| 67 | throw InvalidHydrationTarget::forTarget( |
| 68 | $class, |
| 69 | reason: 'target has no constructor; declare a public constructor or implement Hydratable', |
| 70 | ); |
| 71 | } |
| 72 | |
| 73 | $parameters = []; |
| 74 | |
| 75 | // Reflection lists the parameters in declaration order. |
| 76 | foreach ($constructor->getParameters() as $parameter) { |
| 77 | $parameters[] = $this->parameterMetadata($class, $parameter); |
| 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 | ); |
| 115 | } |
| 116 | |
| 117 | /** |
| 118 | * @param class-string $class |
| 119 | * @param non-empty-string $parameterName |
| 120 | * @return non-empty-string |
| 121 | */ |
| 122 | private function columnName( |
| 123 | string $class, |
| 124 | ReflectionParameter $parameter, |
| 125 | string $parameterName, |
| 126 | ): string { |
| 127 | $attributes = $parameter->getAttributes(Column::class); |
| 128 | |
| 129 | if ($attributes === []) { |
| 130 | return $parameterName; |
| 131 | } |
| 132 | |
| 133 | try { |
| 134 | $column = $attributes[0]->newInstance(); |
| 135 | } catch (Throwable) { |
| 136 | throw InvalidHydrationTarget::forParameter( |
| 137 | $class, |
| 138 | $parameterName, |
| 139 | 'has an invalid #[Column] attribute', |
| 140 | ); |
| 141 | } |
| 142 | |
| 143 | $columnName = $column->name; |
| 144 | |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 146 | throw InvalidHydrationTarget::forParameter( |
| 147 | $class, |
| 148 | $parameterName, |
| 149 | 'has an empty #[Column] name', |
| 150 | ); |
| 151 | } |
| 152 | |
| 153 | return $columnName; |
| 154 | } |
| 155 | |
| 156 | /** |
| 157 | * @param class-string $class |
| 158 | * @param non-empty-string $parameterName |
| 159 | */ |
| 160 | private function typeMetadata( |
| 161 | string $class, |
| 162 | string $parameterName, |
| 163 | ReflectionType $type, |
| 164 | ): TypeMetadata { |
| 165 | if ($type instanceof ReflectionNamedType) { |
| 166 | $name = $this->namedTypeMetadata($class, $parameterName, $type); |
| 167 | |
| 168 | return new TypeMetadata('named', $type->allowsNull(), [$name]); |
| 169 | } |
| 170 | |
| 171 | if ($type instanceof ReflectionUnionType) { |
| 172 | $names = []; |
| 173 | |
| 174 | foreach ($type->getTypes() as $inner) { |
| 175 | if (!$inner instanceof ReflectionNamedType) { |
| 176 | throw InvalidHydrationTarget::forParameter( |
| 177 | $class, |
| 178 | $parameterName, |
| 179 | 'uses an unsupported intersection or DNF type', |
| 180 | ); |
| 181 | } |
| 182 | |
| 183 | if ($inner->getName() === 'null') { |
| 184 | continue; |
| 185 | } |
| 186 | |
| 187 | $names[] = $this->namedTypeMetadata($class, $parameterName, $inner); |
| 188 | } |
| 189 | |
| 190 | /** |
| 191 | * ReflectionUnionType always contains at least one non-null arm in valid PHP; |
| 192 | * Psalm cannot infer that after the runtime null-arm filter above. |
| 193 | * |
| 194 | * @var non-empty-list<NamedTypeMetadata> $names |
| 195 | */ |
| 196 | return new TypeMetadata('union', $type->allowsNull(), $names); |
| 197 | } |
| 198 | |
| 199 | throw InvalidHydrationTarget::forParameter( |
| 200 | $class, |
| 201 | $parameterName, |
| 202 | 'uses an unsupported intersection type', |
| 203 | ); |
| 204 | } |
| 205 | |
| 206 | /** |
| 207 | * @param class-string $class |
| 208 | * @param non-empty-string $parameterName |
| 209 | */ |
| 210 | private function namedTypeMetadata( |
| 211 | string $class, |
| 212 | string $parameterName, |
| 213 | ReflectionNamedType $type, |
| 214 | ): NamedTypeMetadata { |
| 215 | // Reflection reports builtin type names in lowercase. |
| 216 | $name = $type->getName(); |
| 217 | |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 219 | return new NamedTypeMetadata($name, $name, null, null); |
| 220 | } |
| 221 | |
| 222 | if ($name === DateTimeImmutable::class) { |
| 223 | return new NamedTypeMetadata($name, null, 'immutable', null); |
| 224 | } |
| 225 | |
| 226 | if ($name === DateTime::class) { |
| 227 | return new NamedTypeMetadata($name, null, 'mutable', null); |
| 228 | } |
| 229 | |
| 230 | if (is_subclass_of($name, BackedEnum::class)) { |
| 231 | return new NamedTypeMetadata($name, null, null, $name); |
| 232 | } |
| 233 | |
| 234 | throw InvalidHydrationTarget::forParameter( |
| 235 | $class, |
| 236 | $parameterName, |
| 237 | "uses unsupported type {$name}", |
| 238 | ); |
| 239 | } |
| 240 | } |
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 | // Builtin type names like `int` are never class names. |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 48 | throw InvalidHydrationTarget::forTarget( |
| 49 | $class, |
| 50 | reason: 'target is not an existing class', |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | // Builtin type names like `int` are never class names. |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 54 | if (is_a($class, Hydratable::class, true)) { |
| 55 | return new ClassMetadata($class, true, true, null); |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | // Builtin type names like `int` are never class names. |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 54 | if (is_a($class, Hydratable::class, true)) { |
| 58 | $reflection = new ReflectionClass($class); |
| 59 | |
| 60 | if (!$reflection->isInstantiable()) { |
| 61 | throw InvalidHydrationTarget::forTarget($class, reason: 'target is not instantiable'); |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | // Builtin type names like `int` are never class names. |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 54 | if (is_a($class, Hydratable::class, true)) { |
| 58 | $reflection = new ReflectionClass($class); |
| 59 | |
| 60 | if (!$reflection->isInstantiable()) { |
| 64 | $constructor = $reflection->getConstructor(); |
| 65 | |
| 66 | if ($constructor === null) { |
| 67 | throw InvalidHydrationTarget::forTarget( |
| 68 | $class, |
| 69 | reason: 'target has no constructor; declare a public constructor or implement Hydratable', |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | // Builtin type names like `int` are never class names. |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 54 | if (is_a($class, Hydratable::class, true)) { |
| 58 | $reflection = new ReflectionClass($class); |
| 59 | |
| 60 | if (!$reflection->isInstantiable()) { |
| 64 | $constructor = $reflection->getConstructor(); |
| 65 | |
| 66 | if ($constructor === null) { |
| 73 | $parameters = []; |
| 74 | |
| 75 | // Reflection lists the parameters in declaration order. |
| 76 | foreach ($constructor->getParameters() as $parameter) { |
| 76 | foreach ($constructor->getParameters() as $parameter) { |
| 76 | foreach ($constructor->getParameters() as $parameter) { |
| 77 | $parameters[] = $this->parameterMetadata($class, $parameter); |
| 76 | foreach ($constructor->getParameters() as $parameter) { |
| 76 | foreach ($constructor->getParameters() as $parameter) { |
| 77 | $parameters[] = $this->parameterMetadata($class, $parameter); |
| 78 | } |
| 79 | |
| 80 | return new ClassMetadata($class, false, true, $parameters); |
| 81 | } |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | // Builtin type names like `int` are never class names. |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 54 | if (is_a($class, Hydratable::class, true)) { |
| 58 | $reflection = new ReflectionClass($class); |
| 59 | |
| 60 | if (!$reflection->isInstantiable()) { |
| 64 | $constructor = $reflection->getConstructor(); |
| 65 | |
| 66 | if ($constructor === null) { |
| 73 | $parameters = []; |
| 74 | |
| 75 | // Reflection lists the parameters in declaration order. |
| 76 | foreach ($constructor->getParameters() as $parameter) { |
| 76 | foreach ($constructor->getParameters() as $parameter) { |
| 76 | foreach ($constructor->getParameters() as $parameter) { |
| 77 | $parameters[] = $this->parameterMetadata($class, $parameter); |
| 78 | } |
| 79 | |
| 80 | return new ClassMetadata($class, false, true, $parameters); |
| 81 | } |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | // Builtin type names like `int` are never class names. |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 54 | if (is_a($class, Hydratable::class, true)) { |
| 58 | $reflection = new ReflectionClass($class); |
| 59 | |
| 60 | if (!$reflection->isInstantiable()) { |
| 64 | $constructor = $reflection->getConstructor(); |
| 65 | |
| 66 | if ($constructor === null) { |
| 73 | $parameters = []; |
| 74 | |
| 75 | // Reflection lists the parameters in declaration order. |
| 76 | foreach ($constructor->getParameters() as $parameter) { |
| 76 | foreach ($constructor->getParameters() as $parameter) { |
| 77 | $parameters[] = $this->parameterMetadata($class, $parameter); |
| 78 | } |
| 79 | |
| 80 | return new ClassMetadata($class, false, true, $parameters); |
| 81 | } |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | // Builtin type names like `int` are never class names. |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 48 | throw InvalidHydrationTarget::forTarget( |
| 49 | $class, |
| 50 | reason: 'target is not an existing class', |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | // Builtin type names like `int` are never class names. |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 54 | if (is_a($class, Hydratable::class, true)) { |
| 55 | return new ClassMetadata($class, true, true, null); |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | // Builtin type names like `int` are never class names. |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 54 | if (is_a($class, Hydratable::class, true)) { |
| 58 | $reflection = new ReflectionClass($class); |
| 59 | |
| 60 | if (!$reflection->isInstantiable()) { |
| 61 | throw InvalidHydrationTarget::forTarget($class, reason: 'target is not instantiable'); |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | // Builtin type names like `int` are never class names. |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 54 | if (is_a($class, Hydratable::class, true)) { |
| 58 | $reflection = new ReflectionClass($class); |
| 59 | |
| 60 | if (!$reflection->isInstantiable()) { |
| 64 | $constructor = $reflection->getConstructor(); |
| 65 | |
| 66 | if ($constructor === null) { |
| 67 | throw InvalidHydrationTarget::forTarget( |
| 68 | $class, |
| 69 | reason: 'target has no constructor; declare a public constructor or implement Hydratable', |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | // Builtin type names like `int` are never class names. |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 54 | if (is_a($class, Hydratable::class, true)) { |
| 58 | $reflection = new ReflectionClass($class); |
| 59 | |
| 60 | if (!$reflection->isInstantiable()) { |
| 64 | $constructor = $reflection->getConstructor(); |
| 65 | |
| 66 | if ($constructor === null) { |
| 73 | $parameters = []; |
| 74 | |
| 75 | // Reflection lists the parameters in declaration order. |
| 76 | foreach ($constructor->getParameters() as $parameter) { |
| 76 | foreach ($constructor->getParameters() as $parameter) { |
| 76 | foreach ($constructor->getParameters() as $parameter) { |
| 77 | $parameters[] = $this->parameterMetadata($class, $parameter); |
| 76 | foreach ($constructor->getParameters() as $parameter) { |
| 76 | foreach ($constructor->getParameters() as $parameter) { |
| 77 | $parameters[] = $this->parameterMetadata($class, $parameter); |
| 78 | } |
| 79 | |
| 80 | return new ClassMetadata($class, false, true, $parameters); |
| 81 | } |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | // Builtin type names like `int` are never class names. |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 54 | if (is_a($class, Hydratable::class, true)) { |
| 58 | $reflection = new ReflectionClass($class); |
| 59 | |
| 60 | if (!$reflection->isInstantiable()) { |
| 64 | $constructor = $reflection->getConstructor(); |
| 65 | |
| 66 | if ($constructor === null) { |
| 73 | $parameters = []; |
| 74 | |
| 75 | // Reflection lists the parameters in declaration order. |
| 76 | foreach ($constructor->getParameters() as $parameter) { |
| 76 | foreach ($constructor->getParameters() as $parameter) { |
| 76 | foreach ($constructor->getParameters() as $parameter) { |
| 77 | $parameters[] = $this->parameterMetadata($class, $parameter); |
| 78 | } |
| 79 | |
| 80 | return new ClassMetadata($class, false, true, $parameters); |
| 81 | } |
| 44 | private function build(string $class): ClassMetadata |
| 45 | { |
| 46 | // Builtin type names like `int` are never class names. |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 47 | if (!class_exists($class)) { |
| 54 | if (is_a($class, Hydratable::class, true)) { |
| 58 | $reflection = new ReflectionClass($class); |
| 59 | |
| 60 | if (!$reflection->isInstantiable()) { |
| 64 | $constructor = $reflection->getConstructor(); |
| 65 | |
| 66 | if ($constructor === null) { |
| 73 | $parameters = []; |
| 74 | |
| 75 | // Reflection lists the parameters in declaration order. |
| 76 | foreach ($constructor->getParameters() as $parameter) { |
| 76 | foreach ($constructor->getParameters() as $parameter) { |
| 77 | $parameters[] = $this->parameterMetadata($class, $parameter); |
| 78 | } |
| 79 | |
| 80 | return new ClassMetadata($class, false, true, $parameters); |
| 81 | } |
| 123 | string $class, |
| 124 | ReflectionParameter $parameter, |
| 125 | string $parameterName, |
| 126 | ): string { |
| 127 | $attributes = $parameter->getAttributes(Column::class); |
| 128 | |
| 129 | if ($attributes === []) { |
| 130 | return $parameterName; |
| 123 | string $class, |
| 124 | ReflectionParameter $parameter, |
| 125 | string $parameterName, |
| 126 | ): string { |
| 127 | $attributes = $parameter->getAttributes(Column::class); |
| 128 | |
| 129 | if ($attributes === []) { |
| 133 | try { |
| 134 | $column = $attributes[0]->newInstance(); |
| 143 | $columnName = $column->name; |
| 144 | |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 146 | throw InvalidHydrationTarget::forParameter( |
| 147 | $class, |
| 148 | $parameterName, |
| 149 | 'has an empty #[Column] name', |
| 123 | string $class, |
| 124 | ReflectionParameter $parameter, |
| 125 | string $parameterName, |
| 126 | ): string { |
| 127 | $attributes = $parameter->getAttributes(Column::class); |
| 128 | |
| 129 | if ($attributes === []) { |
| 133 | try { |
| 134 | $column = $attributes[0]->newInstance(); |
| 143 | $columnName = $column->name; |
| 144 | |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 153 | return $columnName; |
| 154 | } |
| 123 | string $class, |
| 124 | ReflectionParameter $parameter, |
| 125 | string $parameterName, |
| 126 | ): string { |
| 127 | $attributes = $parameter->getAttributes(Column::class); |
| 128 | |
| 129 | if ($attributes === []) { |
| 133 | try { |
| 134 | $column = $attributes[0]->newInstance(); |
| 143 | $columnName = $column->name; |
| 144 | |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 146 | throw InvalidHydrationTarget::forParameter( |
| 147 | $class, |
| 148 | $parameterName, |
| 149 | 'has an empty #[Column] name', |
| 123 | string $class, |
| 124 | ReflectionParameter $parameter, |
| 125 | string $parameterName, |
| 126 | ): string { |
| 127 | $attributes = $parameter->getAttributes(Column::class); |
| 128 | |
| 129 | if ($attributes === []) { |
| 133 | try { |
| 134 | $column = $attributes[0]->newInstance(); |
| 143 | $columnName = $column->name; |
| 144 | |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 153 | return $columnName; |
| 154 | } |
| 123 | string $class, |
| 124 | ReflectionParameter $parameter, |
| 125 | string $parameterName, |
| 126 | ): string { |
| 127 | $attributes = $parameter->getAttributes(Column::class); |
| 128 | |
| 129 | if ($attributes === []) { |
| 133 | try { |
| 134 | $column = $attributes[0]->newInstance(); |
| 143 | $columnName = $column->name; |
| 144 | |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 146 | throw InvalidHydrationTarget::forParameter( |
| 147 | $class, |
| 148 | $parameterName, |
| 149 | 'has an empty #[Column] name', |
| 123 | string $class, |
| 124 | ReflectionParameter $parameter, |
| 125 | string $parameterName, |
| 126 | ): string { |
| 127 | $attributes = $parameter->getAttributes(Column::class); |
| 128 | |
| 129 | if ($attributes === []) { |
| 133 | try { |
| 134 | $column = $attributes[0]->newInstance(); |
| 143 | $columnName = $column->name; |
| 144 | |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 145 | if ($columnName === '' || trim($columnName) === '') { |
| 153 | return $columnName; |
| 154 | } |
| 135 | } catch (Throwable) { |
| 136 | throw InvalidHydrationTarget::forParameter( |
| 137 | $class, |
| 138 | $parameterName, |
| 139 | 'has an invalid #[Column] attribute', |
| 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 | } |
| 211 | string $class, |
| 212 | string $parameterName, |
| 213 | ReflectionNamedType $type, |
| 214 | ): NamedTypeMetadata { |
| 215 | // Reflection reports builtin type names in lowercase. |
| 216 | $name = $type->getName(); |
| 217 | |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 219 | return new NamedTypeMetadata($name, $name, null, null); |
| 211 | string $class, |
| 212 | string $parameterName, |
| 213 | ReflectionNamedType $type, |
| 214 | ): NamedTypeMetadata { |
| 215 | // Reflection reports builtin type names in lowercase. |
| 216 | $name = $type->getName(); |
| 217 | |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 222 | if ($name === DateTimeImmutable::class) { |
| 223 | return new NamedTypeMetadata($name, null, 'immutable', null); |
| 211 | string $class, |
| 212 | string $parameterName, |
| 213 | ReflectionNamedType $type, |
| 214 | ): NamedTypeMetadata { |
| 215 | // Reflection reports builtin type names in lowercase. |
| 216 | $name = $type->getName(); |
| 217 | |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 222 | if ($name === DateTimeImmutable::class) { |
| 226 | if ($name === DateTime::class) { |
| 227 | return new NamedTypeMetadata($name, null, 'mutable', null); |
| 211 | string $class, |
| 212 | string $parameterName, |
| 213 | ReflectionNamedType $type, |
| 214 | ): NamedTypeMetadata { |
| 215 | // Reflection reports builtin type names in lowercase. |
| 216 | $name = $type->getName(); |
| 217 | |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 222 | if ($name === DateTimeImmutable::class) { |
| 226 | if ($name === DateTime::class) { |
| 230 | if (is_subclass_of($name, BackedEnum::class)) { |
| 231 | return new NamedTypeMetadata($name, null, null, $name); |
| 211 | string $class, |
| 212 | string $parameterName, |
| 213 | ReflectionNamedType $type, |
| 214 | ): NamedTypeMetadata { |
| 215 | // Reflection reports builtin type names in lowercase. |
| 216 | $name = $type->getName(); |
| 217 | |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 222 | if ($name === DateTimeImmutable::class) { |
| 226 | if ($name === DateTime::class) { |
| 230 | if (is_subclass_of($name, BackedEnum::class)) { |
| 234 | throw InvalidHydrationTarget::forParameter( |
| 235 | $class, |
| 236 | $parameterName, |
| 237 | "uses unsupported type {$name}", |
| 238 | ); |
| 239 | } |
| 211 | string $class, |
| 212 | string $parameterName, |
| 213 | ReflectionNamedType $type, |
| 214 | ): NamedTypeMetadata { |
| 215 | // Reflection reports builtin type names in lowercase. |
| 216 | $name = $type->getName(); |
| 217 | |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 219 | return new NamedTypeMetadata($name, $name, null, null); |
| 211 | string $class, |
| 212 | string $parameterName, |
| 213 | ReflectionNamedType $type, |
| 214 | ): NamedTypeMetadata { |
| 215 | // Reflection reports builtin type names in lowercase. |
| 216 | $name = $type->getName(); |
| 217 | |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 222 | if ($name === DateTimeImmutable::class) { |
| 223 | return new NamedTypeMetadata($name, null, 'immutable', null); |
| 211 | string $class, |
| 212 | string $parameterName, |
| 213 | ReflectionNamedType $type, |
| 214 | ): NamedTypeMetadata { |
| 215 | // Reflection reports builtin type names in lowercase. |
| 216 | $name = $type->getName(); |
| 217 | |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 222 | if ($name === DateTimeImmutable::class) { |
| 226 | if ($name === DateTime::class) { |
| 227 | return new NamedTypeMetadata($name, null, 'mutable', null); |
| 211 | string $class, |
| 212 | string $parameterName, |
| 213 | ReflectionNamedType $type, |
| 214 | ): NamedTypeMetadata { |
| 215 | // Reflection reports builtin type names in lowercase. |
| 216 | $name = $type->getName(); |
| 217 | |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 222 | if ($name === DateTimeImmutable::class) { |
| 226 | if ($name === DateTime::class) { |
| 230 | if (is_subclass_of($name, BackedEnum::class)) { |
| 231 | return new NamedTypeMetadata($name, null, null, $name); |
| 211 | string $class, |
| 212 | string $parameterName, |
| 213 | ReflectionNamedType $type, |
| 214 | ): NamedTypeMetadata { |
| 215 | // Reflection reports builtin type names in lowercase. |
| 216 | $name = $type->getName(); |
| 217 | |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 222 | if ($name === DateTimeImmutable::class) { |
| 226 | if ($name === DateTime::class) { |
| 230 | if (is_subclass_of($name, BackedEnum::class)) { |
| 234 | throw InvalidHydrationTarget::forParameter( |
| 235 | $class, |
| 236 | $parameterName, |
| 237 | "uses unsupported type {$name}", |
| 238 | ); |
| 239 | } |
| 211 | string $class, |
| 212 | string $parameterName, |
| 213 | ReflectionNamedType $type, |
| 214 | ): NamedTypeMetadata { |
| 215 | // Reflection reports builtin type names in lowercase. |
| 216 | $name = $type->getName(); |
| 217 | |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 219 | return new NamedTypeMetadata($name, $name, null, null); |
| 211 | string $class, |
| 212 | string $parameterName, |
| 213 | ReflectionNamedType $type, |
| 214 | ): NamedTypeMetadata { |
| 215 | // Reflection reports builtin type names in lowercase. |
| 216 | $name = $type->getName(); |
| 217 | |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 222 | if ($name === DateTimeImmutable::class) { |
| 223 | return new NamedTypeMetadata($name, null, 'immutable', null); |
| 211 | string $class, |
| 212 | string $parameterName, |
| 213 | ReflectionNamedType $type, |
| 214 | ): NamedTypeMetadata { |
| 215 | // Reflection reports builtin type names in lowercase. |
| 216 | $name = $type->getName(); |
| 217 | |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 222 | if ($name === DateTimeImmutable::class) { |
| 226 | if ($name === DateTime::class) { |
| 227 | return new NamedTypeMetadata($name, null, 'mutable', null); |
| 211 | string $class, |
| 212 | string $parameterName, |
| 213 | ReflectionNamedType $type, |
| 214 | ): NamedTypeMetadata { |
| 215 | // Reflection reports builtin type names in lowercase. |
| 216 | $name = $type->getName(); |
| 217 | |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 222 | if ($name === DateTimeImmutable::class) { |
| 226 | if ($name === DateTime::class) { |
| 230 | if (is_subclass_of($name, BackedEnum::class)) { |
| 231 | return new NamedTypeMetadata($name, null, null, $name); |
| 211 | string $class, |
| 212 | string $parameterName, |
| 213 | ReflectionNamedType $type, |
| 214 | ): NamedTypeMetadata { |
| 215 | // Reflection reports builtin type names in lowercase. |
| 216 | $name = $type->getName(); |
| 217 | |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 218 | if ($type->isBuiltin() && in_array($name, ['int', 'float', 'bool', 'string'], true)) { |
| 222 | if ($name === DateTimeImmutable::class) { |
| 226 | if ($name === DateTime::class) { |
| 230 | if (is_subclass_of($name, BackedEnum::class)) { |
| 234 | throw InvalidHydrationTarget::forParameter( |
| 235 | $class, |
| 236 | $parameterName, |
| 237 | "uses unsupported type {$name}", |
| 238 | ); |
| 239 | } |
| 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 | ); |
| 115 | } |
| 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 | ); |
| 115 | } |
| 40 | self::$entries = []; |
| 41 | } |
| 161 | string $class, |
| 162 | string $parameterName, |
| 163 | ReflectionType $type, |
| 164 | ): TypeMetadata { |
| 165 | if ($type instanceof ReflectionNamedType) { |
| 166 | $name = $this->namedTypeMetadata($class, $parameterName, $type); |
| 167 | |
| 168 | return new TypeMetadata('named', $type->allowsNull(), [$name]); |
| 161 | string $class, |
| 162 | string $parameterName, |
| 163 | ReflectionType $type, |
| 164 | ): TypeMetadata { |
| 165 | if ($type instanceof ReflectionNamedType) { |
| 171 | if ($type instanceof ReflectionUnionType) { |
| 172 | $names = []; |
| 173 | |
| 174 | foreach ($type->getTypes() as $inner) { |
| 174 | foreach ($type->getTypes() as $inner) { |
| 175 | if (!$inner instanceof ReflectionNamedType) { |
| 176 | throw InvalidHydrationTarget::forParameter( |
| 177 | $class, |
| 178 | $parameterName, |
| 179 | 'uses an unsupported intersection or DNF type', |
| 161 | string $class, |
| 162 | string $parameterName, |
| 163 | ReflectionType $type, |
| 164 | ): TypeMetadata { |
| 165 | if ($type instanceof ReflectionNamedType) { |
| 171 | if ($type instanceof ReflectionUnionType) { |
| 172 | $names = []; |
| 173 | |
| 174 | foreach ($type->getTypes() as $inner) { |
| 174 | foreach ($type->getTypes() as $inner) { |
| 175 | if (!$inner instanceof ReflectionNamedType) { |
| 183 | if ($inner->getName() === 'null') { |
| 184 | continue; |
| 174 | foreach ($type->getTypes() as $inner) { |
| 174 | foreach ($type->getTypes() as $inner) { |
| 175 | if (!$inner instanceof ReflectionNamedType) { |
| 176 | throw InvalidHydrationTarget::forParameter( |
| 177 | $class, |
| 178 | $parameterName, |
| 179 | 'uses an unsupported intersection or DNF type', |
| 180 | ); |
| 181 | } |
| 182 | |
| 183 | if ($inner->getName() === 'null') { |
| 184 | continue; |
| 185 | } |
| 186 | |
| 187 | $names[] = $this->namedTypeMetadata($class, $parameterName, $inner); |
| 188 | } |
| 189 | |
| 190 | /** |
| 191 | * ReflectionUnionType always contains at least one non-null arm in valid PHP; |
| 192 | * Psalm cannot infer that after the runtime null-arm filter above. |
| 193 | * |
| 194 | * @var non-empty-list<NamedTypeMetadata> $names |
| 195 | */ |
| 196 | return new TypeMetadata('union', $type->allowsNull(), $names); |
| 161 | string $class, |
| 162 | string $parameterName, |
| 163 | ReflectionType $type, |
| 164 | ): TypeMetadata { |
| 165 | if ($type instanceof ReflectionNamedType) { |
| 171 | if ($type instanceof ReflectionUnionType) { |
| 172 | $names = []; |
| 173 | |
| 174 | foreach ($type->getTypes() as $inner) { |
| 174 | foreach ($type->getTypes() as $inner) { |
| 175 | if (!$inner instanceof ReflectionNamedType) { |
| 183 | if ($inner->getName() === 'null') { |
| 174 | foreach ($type->getTypes() as $inner) { |
| 175 | if (!$inner instanceof ReflectionNamedType) { |
| 176 | throw InvalidHydrationTarget::forParameter( |
| 177 | $class, |
| 178 | $parameterName, |
| 179 | 'uses an unsupported intersection or DNF type', |
| 180 | ); |
| 181 | } |
| 182 | |
| 183 | if ($inner->getName() === 'null') { |
| 184 | continue; |
| 185 | } |
| 186 | |
| 187 | $names[] = $this->namedTypeMetadata($class, $parameterName, $inner); |
| 174 | foreach ($type->getTypes() as $inner) { |
| 174 | foreach ($type->getTypes() as $inner) { |
| 175 | if (!$inner instanceof ReflectionNamedType) { |
| 176 | throw InvalidHydrationTarget::forParameter( |
| 177 | $class, |
| 178 | $parameterName, |
| 179 | 'uses an unsupported intersection or DNF type', |
| 180 | ); |
| 181 | } |
| 182 | |
| 183 | if ($inner->getName() === 'null') { |
| 184 | continue; |
| 185 | } |
| 186 | |
| 187 | $names[] = $this->namedTypeMetadata($class, $parameterName, $inner); |
| 188 | } |
| 189 | |
| 190 | /** |
| 191 | * ReflectionUnionType always contains at least one non-null arm in valid PHP; |
| 192 | * Psalm cannot infer that after the runtime null-arm filter above. |
| 193 | * |
| 194 | * @var non-empty-list<NamedTypeMetadata> $names |
| 195 | */ |
| 196 | return new TypeMetadata('union', $type->allowsNull(), $names); |
| 161 | string $class, |
| 162 | string $parameterName, |
| 163 | ReflectionType $type, |
| 164 | ): TypeMetadata { |
| 165 | if ($type instanceof ReflectionNamedType) { |
| 171 | if ($type instanceof ReflectionUnionType) { |
| 172 | $names = []; |
| 173 | |
| 174 | foreach ($type->getTypes() as $inner) { |
| 174 | foreach ($type->getTypes() as $inner) { |
| 174 | foreach ($type->getTypes() as $inner) { |
| 175 | if (!$inner instanceof ReflectionNamedType) { |
| 176 | throw InvalidHydrationTarget::forParameter( |
| 177 | $class, |
| 178 | $parameterName, |
| 179 | 'uses an unsupported intersection or DNF type', |
| 180 | ); |
| 181 | } |
| 182 | |
| 183 | if ($inner->getName() === 'null') { |
| 184 | continue; |
| 185 | } |
| 186 | |
| 187 | $names[] = $this->namedTypeMetadata($class, $parameterName, $inner); |
| 188 | } |
| 189 | |
| 190 | /** |
| 191 | * ReflectionUnionType always contains at least one non-null arm in valid PHP; |
| 192 | * Psalm cannot infer that after the runtime null-arm filter above. |
| 193 | * |
| 194 | * @var non-empty-list<NamedTypeMetadata> $names |
| 195 | */ |
| 196 | return new TypeMetadata('union', $type->allowsNull(), $names); |
| 161 | string $class, |
| 162 | string $parameterName, |
| 163 | ReflectionType $type, |
| 164 | ): TypeMetadata { |
| 165 | if ($type instanceof ReflectionNamedType) { |
| 171 | if ($type instanceof ReflectionUnionType) { |
| 172 | $names = []; |
| 173 | |
| 174 | foreach ($type->getTypes() as $inner) { |
| 174 | foreach ($type->getTypes() as $inner) { |
| 175 | if (!$inner instanceof ReflectionNamedType) { |
| 176 | throw InvalidHydrationTarget::forParameter( |
| 177 | $class, |
| 178 | $parameterName, |
| 179 | 'uses an unsupported intersection or DNF type', |
| 180 | ); |
| 181 | } |
| 182 | |
| 183 | if ($inner->getName() === 'null') { |
| 184 | continue; |
| 185 | } |
| 186 | |
| 187 | $names[] = $this->namedTypeMetadata($class, $parameterName, $inner); |
| 188 | } |
| 189 | |
| 190 | /** |
| 191 | * ReflectionUnionType always contains at least one non-null arm in valid PHP; |
| 192 | * Psalm cannot infer that after the runtime null-arm filter above. |
| 193 | * |
| 194 | * @var non-empty-list<NamedTypeMetadata> $names |
| 195 | */ |
| 196 | return new TypeMetadata('union', $type->allowsNull(), $names); |
| 161 | string $class, |
| 162 | string $parameterName, |
| 163 | ReflectionType $type, |
| 164 | ): TypeMetadata { |
| 165 | if ($type instanceof ReflectionNamedType) { |
| 171 | if ($type instanceof ReflectionUnionType) { |
| 199 | throw InvalidHydrationTarget::forParameter( |
| 200 | $class, |
| 201 | $parameterName, |
| 202 | 'uses an unsupported intersection type', |
| 203 | ); |
| 204 | } |