Skip to content

Commit cf66f03

Browse files
bug symfony#37340 [3.4] Fix support for PHP8 union types (nicolas-grekas)
This PR was merged into the 3.4 branch. Discussion ---------- [3.4] Fix support for PHP8 union types | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Tickets | - | License | MIT | Doc PR | - This fixes fatal errors on PHP 8 once union types are used. Note that this doesn't provide support for union types, eg autowiring, serializer, etc will just skip them. If another behavior is desired, that'd be for the `5.x` branch. With PHP 8 coming, calling the `getName()` on the object returned by `ReflectionParameter::getType()` or `ReflectionFunctionAbstract::getReturnType()` might fail. The reason is that these might return a [`ReflectionUnionType`](https://wiki.php.net/rfc/union_types_v2#reflection) now, which has no `getName()` method (same for `isBuiltin() btw.) Commits ------- e09372b [3.4] Fix support for PHP8 union types
2 parents 57251cd + e09372b commit cf66f03

File tree

7 files changed

+60
-43
lines changed

7 files changed

+60
-43
lines changed

src/Symfony/Component/Config/Resource/ReflectionClassResource.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ private function generateSignature(\ReflectionClass $class)
177177
if (!$parametersWithUndefinedConstants) {
178178
yield preg_replace('/^ @@.*/m', '', $m);
179179
} else {
180+
$t = \PHP_VERSION_ID >= 70000 ? $m->getReturnType() : '';
180181
$stack = [
181182
$m->getDocComment(),
182183
$m->getName(),
@@ -187,15 +188,16 @@ private function generateSignature(\ReflectionClass $class)
187188
$m->isPrivate(),
188189
$m->isProtected(),
189190
$m->returnsReference(),
190-
\PHP_VERSION_ID >= 70000 && $m->hasReturnType() ? (\PHP_VERSION_ID >= 70100 ? $m->getReturnType()->getName() : (string) $m->getReturnType()) : '',
191+
$t instanceof \ReflectionNamedType ? ((string) $t->allowsNull()).$t->getName() : (string) $t,
191192
];
192193

193194
foreach ($m->getParameters() as $p) {
194195
if (!isset($parametersWithUndefinedConstants[$p->name])) {
195196
$stack[] = (string) $p;
196197
} else {
198+
$t = \PHP_VERSION_ID >= 70000 ? $p->getType() : '';
197199
$stack[] = $p->isOptional();
198-
$stack[] = \PHP_VERSION_ID >= 70000 && $p->hasType() ? (\PHP_VERSION_ID >= 70100 ? $p->getType()->getName() : (string) $p->getType()) : '';
200+
$stack[] = $t instanceof \ReflectionNamedType ? $t->getName() : (string) $t;
199201
$stack[] = $p->isPassedByReference();
200202
$stack[] = \PHP_VERSION_ID >= 50600 ? $p->isVariadic() : '';
201203
$stack[] = $p->getName();

src/Symfony/Component/DependencyInjection/LazyProxy/ProxyHelper.php

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,26 +39,36 @@ public static function getTypeHint(\ReflectionFunctionAbstract $r, \ReflectionPa
3939
if (!$type) {
4040
return null;
4141
}
42-
if (!\is_string($type)) {
43-
$name = $type instanceof \ReflectionNamedType ? $type->getName() : $type->__toString();
4442

45-
if ($type->isBuiltin()) {
46-
return $noBuiltin ? null : $name;
43+
$types = [];
44+
45+
foreach ($type instanceof \ReflectionUnionType ? $type->getTypes() : [$type] as $type) {
46+
$name = $type instanceof \ReflectionNamedType ? $type->getName() : (string) $type;
47+
48+
if (!\is_string($type) && $type->isBuiltin()) {
49+
if (!$noBuiltin) {
50+
$types[] = $name;
51+
}
52+
continue;
4753
}
48-
}
49-
$lcName = strtolower($name);
50-
$prefix = $noBuiltin ? '' : '\\';
5154

52-
if ('self' !== $lcName && 'parent' !== $lcName) {
53-
return $prefix.$name;
54-
}
55-
if (!$r instanceof \ReflectionMethod) {
56-
return null;
57-
}
58-
if ('self' === $lcName) {
59-
return $prefix.$r->getDeclaringClass()->name;
55+
$lcName = strtolower($name);
56+
$prefix = $noBuiltin ? '' : '\\';
57+
58+
if ('self' !== $lcName && 'parent' !== $lcName) {
59+
$types[] = '' !== $prefix ? $prefix.$name : $name;
60+
continue;
61+
}
62+
if (!$r instanceof \ReflectionMethod) {
63+
continue;
64+
}
65+
if ('self' === $lcName) {
66+
$types[] = $prefix.$r->getDeclaringClass()->name;
67+
} else {
68+
$types[] = ($parent = $r->getDeclaringClass()->getParentClass()) ? $prefix.$parent->name : null;
69+
}
6070
}
6171

62-
return ($parent = $r->getDeclaringClass()->getParentClass()) ? $prefix.$parent->name : null;
72+
return $types ? implode('|', $types) : null;
6373
}
6474
}

src/Symfony/Component/OptionsResolver/OptionsResolver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1076,7 +1076,7 @@ private function getParameterClassName(\ReflectionParameter $parameter)
10761076
return ($class = $parameter->getClass()) ? $class->name : null;
10771077
}
10781078

1079-
if (!($type = $parameter->getType()) || $type->isBuiltin()) {
1079+
if (!($type = $parameter->getType()) instanceof \ReflectionNamedType || $type->isBuiltin()) {
10801080
return null;
10811081
}
10821082

src/Symfony/Component/PropertyAccess/PropertyAccessor.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,8 +519,9 @@ private function readProperty($zval, $property)
519519
// handle uninitialized properties in PHP >= 7.4
520520
if (\PHP_VERSION_ID >= 70400 && preg_match('/^Typed property ([\w\\\]+)::\$(\w+) must not be accessed before initialization$/', $e->getMessage(), $matches)) {
521521
$r = new \ReflectionProperty($matches[1], $matches[2]);
522+
$type = ($type = $r->getType()) instanceof \ReflectionNamedType ? $type->getName() : (string) $type;
522523

523-
throw new AccessException(sprintf('The property "%s::$%s" is not readable because it is typed "%s". You should initialize it or declare a default value instead.', $r->getDeclaringClass()->getName(), $r->getName(), $r->getType()->getName()), 0, $e);
524+
throw new AccessException(sprintf('The property "%s::$%s" is not readable because it is typed "%s". You should initialize it or declare a default value instead.', $r->getDeclaringClass()->getName(), $r->getName(), $type), 0, $e);
524525
}
525526

526527
throw $e;

src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -187,26 +187,26 @@ private function extractFromMutator($class, $property)
187187
$type = $this->extractFromReflectionType($reflectionType, $reflectionMethod);
188188

189189
// HHVM reports variadics with "array" but not builtin type hints
190-
if (!$reflectionType->isBuiltin() && Type::BUILTIN_TYPE_ARRAY === $type->getBuiltinType()) {
190+
if (1 === \count($type) && !$reflectionType->isBuiltin() && Type::BUILTIN_TYPE_ARRAY === $type[0]->getBuiltinType()) {
191191
return null;
192192
}
193193
} elseif (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $reflectionParameter, $info)) {
194194
if (Type::BUILTIN_TYPE_ARRAY === $info[1]) {
195-
$type = new Type(Type::BUILTIN_TYPE_ARRAY, $reflectionParameter->allowsNull(), null, true);
195+
$type = [new Type(Type::BUILTIN_TYPE_ARRAY, $reflectionParameter->allowsNull(), null, true)];
196196
} elseif (Type::BUILTIN_TYPE_CALLABLE === $info[1]) {
197-
$type = new Type(Type::BUILTIN_TYPE_CALLABLE, $reflectionParameter->allowsNull());
197+
$type = [new Type(Type::BUILTIN_TYPE_CALLABLE, $reflectionParameter->allowsNull())];
198198
} else {
199-
$type = new Type(Type::BUILTIN_TYPE_OBJECT, $reflectionParameter->allowsNull(), $this->resolveTypeName($info[1], $reflectionMethod));
199+
$type = [new Type(Type::BUILTIN_TYPE_OBJECT, $reflectionParameter->allowsNull(), $this->resolveTypeName($info[1], $reflectionMethod))];
200200
}
201201
} else {
202202
return null;
203203
}
204204

205-
if (\in_array($prefix, $this->arrayMutatorPrefixes)) {
206-
$type = new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $type);
205+
if (1 === \count($type) && \in_array($prefix, $this->arrayMutatorPrefixes)) {
206+
$type = [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $type[0])];
207207
}
208208

209-
return [$type];
209+
return $type;
210210
}
211211

212212
/**
@@ -225,7 +225,7 @@ private function extractFromAccessor($class, $property)
225225
}
226226

227227
if ($this->supportsParameterType && $reflectionType = $reflectionMethod->getReturnType()) {
228-
return [$this->extractFromReflectionType($reflectionType, $reflectionMethod)];
228+
return $this->extractFromReflectionType($reflectionType, $reflectionMethod);
229229
}
230230

231231
return \in_array($prefix, ['is', 'can']) ? [new Type(Type::BUILTIN_TYPE_BOOL)] : null;
@@ -234,24 +234,28 @@ private function extractFromAccessor($class, $property)
234234
/**
235235
* Extracts data from the PHP 7 reflection type.
236236
*
237-
* @return Type
237+
* @return Type[]
238238
*/
239239
private function extractFromReflectionType(\ReflectionType $reflectionType, \ReflectionMethod $reflectionMethod)
240240
{
241-
$phpTypeOrClass = $reflectionType instanceof \ReflectionNamedType ? $reflectionType->getName() : $reflectionType->__toString();
241+
$types = [];
242242
$nullable = $reflectionType->allowsNull();
243243

244-
if (Type::BUILTIN_TYPE_ARRAY === $phpTypeOrClass) {
245-
$type = new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true);
246-
} elseif ('void' === $phpTypeOrClass) {
247-
$type = new Type(Type::BUILTIN_TYPE_NULL, $nullable);
248-
} elseif ($reflectionType->isBuiltin()) {
249-
$type = new Type($phpTypeOrClass, $nullable);
250-
} else {
251-
$type = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $this->resolveTypeName($phpTypeOrClass, $reflectionMethod));
244+
foreach ($reflectionType instanceof \ReflectionUnionType ? $reflectionType->getTypes() : [$reflectionType] as $type) {
245+
$phpTypeOrClass = $reflectionType instanceof \ReflectionNamedType ? $reflectionType->getName() : (string) $type;
246+
247+
if (Type::BUILTIN_TYPE_ARRAY === $phpTypeOrClass) {
248+
$types[] = new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true);
249+
} elseif ('void' === $phpTypeOrClass || 'null' === $phpTypeOrClass) {
250+
$types[] = new Type(Type::BUILTIN_TYPE_NULL, $nullable);
251+
} elseif ($reflectionType->isBuiltin()) {
252+
$types[] = new Type($phpTypeOrClass, $nullable);
253+
} else {
254+
$types[] = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $this->resolveTypeName($phpTypeOrClass, $reflectionMethod));
255+
}
252256
}
253257

254-
return $type;
258+
return $types;
255259
}
256260

257261
private function resolveTypeName($name, \ReflectionMethod $reflectionMethod)

src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionPara
388388
try {
389389
if (\PHP_VERSION_ID < 70100 && null !== $parameterClass = $parameter->getClass()) {
390390
$parameterClass = $parameterClass->name;
391-
} elseif (\PHP_VERSION_ID >= 70100 && ($parameterType = $parameter->getType()) && !$parameterType->isBuiltin()) {
391+
} elseif (\PHP_VERSION_ID >= 70100 && ($parameterType = $parameter->getType()) instanceof \ReflectionNamedType && !$parameterType->isBuiltin()) {
392392
$parameterClass = $parameterType->getName();
393393
new \ReflectionClass($parameterClass); // throws a \ReflectionException if the class doesn't exist
394394
} else {

src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public static function castType(\ReflectionType $c, array $a, Stub $stub, $isNes
9191
$prefix = Caster::PREFIX_VIRTUAL;
9292

9393
$a += [
94-
$prefix.'name' => $c instanceof \ReflectionNamedType ? $c->getName() : $c->__toString(),
94+
$prefix.'name' => $c instanceof \ReflectionNamedType ? $c->getName() : (string) $c,
9595
$prefix.'allowsNull' => $c->allowsNull(),
9696
$prefix.'isBuiltin' => $c->isBuiltin(),
9797
];
@@ -178,7 +178,7 @@ public static function castFunctionAbstract(\ReflectionFunctionAbstract $c, arra
178178

179179
if (isset($a[$prefix.'returnType'])) {
180180
$v = $a[$prefix.'returnType'];
181-
$v = $v instanceof \ReflectionNamedType ? $v->getName() : $v->__toString();
181+
$v = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v;
182182
$a[$prefix.'returnType'] = new ClassStub($a[$prefix.'returnType']->allowsNull() ? '?'.$v : $v, [class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', '']);
183183
}
184184
if (isset($a[$prefix.'class'])) {
@@ -247,7 +247,7 @@ public static function castParameter(\ReflectionParameter $c, array $a, Stub $st
247247

248248
if (method_exists($c, 'getType')) {
249249
if ($v = $c->getType()) {
250-
$a[$prefix.'typeHint'] = $v instanceof \ReflectionNamedType ? $v->getName() : $v->__toString();
250+
$a[$prefix.'typeHint'] = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v;
251251
}
252252
} elseif (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $c, $v)) {
253253
$a[$prefix.'typeHint'] = $v[1];

0 commit comments

Comments
 (0)