bug #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
-------
e09372bcbf
[3.4] Fix support for PHP8 union types
This commit is contained in:
commit
cf66f03337
@ -177,6 +177,7 @@ class ReflectionClassResource implements SelfCheckingResourceInterface, \Seriali
|
|||||||
if (!$parametersWithUndefinedConstants) {
|
if (!$parametersWithUndefinedConstants) {
|
||||||
yield preg_replace('/^ @@.*/m', '', $m);
|
yield preg_replace('/^ @@.*/m', '', $m);
|
||||||
} else {
|
} else {
|
||||||
|
$t = \PHP_VERSION_ID >= 70000 ? $m->getReturnType() : '';
|
||||||
$stack = [
|
$stack = [
|
||||||
$m->getDocComment(),
|
$m->getDocComment(),
|
||||||
$m->getName(),
|
$m->getName(),
|
||||||
@ -187,15 +188,16 @@ class ReflectionClassResource implements SelfCheckingResourceInterface, \Seriali
|
|||||||
$m->isPrivate(),
|
$m->isPrivate(),
|
||||||
$m->isProtected(),
|
$m->isProtected(),
|
||||||
$m->returnsReference(),
|
$m->returnsReference(),
|
||||||
\PHP_VERSION_ID >= 70000 && $m->hasReturnType() ? (\PHP_VERSION_ID >= 70100 ? $m->getReturnType()->getName() : (string) $m->getReturnType()) : '',
|
$t instanceof \ReflectionNamedType ? ((string) $t->allowsNull()).$t->getName() : (string) $t,
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($m->getParameters() as $p) {
|
foreach ($m->getParameters() as $p) {
|
||||||
if (!isset($parametersWithUndefinedConstants[$p->name])) {
|
if (!isset($parametersWithUndefinedConstants[$p->name])) {
|
||||||
$stack[] = (string) $p;
|
$stack[] = (string) $p;
|
||||||
} else {
|
} else {
|
||||||
|
$t = \PHP_VERSION_ID >= 70000 ? $p->getType() : '';
|
||||||
$stack[] = $p->isOptional();
|
$stack[] = $p->isOptional();
|
||||||
$stack[] = \PHP_VERSION_ID >= 70000 && $p->hasType() ? (\PHP_VERSION_ID >= 70100 ? $p->getType()->getName() : (string) $p->getType()) : '';
|
$stack[] = $t instanceof \ReflectionNamedType ? $t->getName() : (string) $t;
|
||||||
$stack[] = $p->isPassedByReference();
|
$stack[] = $p->isPassedByReference();
|
||||||
$stack[] = \PHP_VERSION_ID >= 50600 ? $p->isVariadic() : '';
|
$stack[] = \PHP_VERSION_ID >= 50600 ? $p->isVariadic() : '';
|
||||||
$stack[] = $p->getName();
|
$stack[] = $p->getName();
|
||||||
|
@ -39,26 +39,36 @@ class ProxyHelper
|
|||||||
if (!$type) {
|
if (!$type) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (!\is_string($type)) {
|
|
||||||
$name = $type instanceof \ReflectionNamedType ? $type->getName() : $type->__toString();
|
|
||||||
|
|
||||||
if ($type->isBuiltin()) {
|
$types = [];
|
||||||
return $noBuiltin ? null : $name;
|
|
||||||
|
foreach ($type instanceof \ReflectionUnionType ? $type->getTypes() : [$type] as $type) {
|
||||||
|
$name = $type instanceof \ReflectionNamedType ? $type->getName() : (string) $type;
|
||||||
|
|
||||||
|
if (!\is_string($type) && $type->isBuiltin()) {
|
||||||
|
if (!$noBuiltin) {
|
||||||
|
$types[] = $name;
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$lcName = strtolower($name);
|
$lcName = strtolower($name);
|
||||||
$prefix = $noBuiltin ? '' : '\\';
|
$prefix = $noBuiltin ? '' : '\\';
|
||||||
|
|
||||||
if ('self' !== $lcName && 'parent' !== $lcName) {
|
if ('self' !== $lcName && 'parent' !== $lcName) {
|
||||||
return $prefix.$name;
|
$types[] = '' !== $prefix ? $prefix.$name : $name;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
if (!$r instanceof \ReflectionMethod) {
|
if (!$r instanceof \ReflectionMethod) {
|
||||||
return null;
|
continue;
|
||||||
}
|
}
|
||||||
if ('self' === $lcName) {
|
if ('self' === $lcName) {
|
||||||
return $prefix.$r->getDeclaringClass()->name;
|
$types[] = $prefix.$r->getDeclaringClass()->name;
|
||||||
|
} else {
|
||||||
|
$types[] = ($parent = $r->getDeclaringClass()->getParentClass()) ? $prefix.$parent->name : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ($parent = $r->getDeclaringClass()->getParentClass()) ? $prefix.$parent->name : null;
|
return $types ? implode('|', $types) : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1076,7 +1076,7 @@ class OptionsResolver implements Options
|
|||||||
return ($class = $parameter->getClass()) ? $class->name : null;
|
return ($class = $parameter->getClass()) ? $class->name : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!($type = $parameter->getType()) || $type->isBuiltin()) {
|
if (!($type = $parameter->getType()) instanceof \ReflectionNamedType || $type->isBuiltin()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -519,8 +519,9 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
// handle uninitialized properties in PHP >= 7.4
|
// handle uninitialized properties in PHP >= 7.4
|
||||||
if (\PHP_VERSION_ID >= 70400 && preg_match('/^Typed property ([\w\\\]+)::\$(\w+) must not be accessed before initialization$/', $e->getMessage(), $matches)) {
|
if (\PHP_VERSION_ID >= 70400 && preg_match('/^Typed property ([\w\\\]+)::\$(\w+) must not be accessed before initialization$/', $e->getMessage(), $matches)) {
|
||||||
$r = new \ReflectionProperty($matches[1], $matches[2]);
|
$r = new \ReflectionProperty($matches[1], $matches[2]);
|
||||||
|
$type = ($type = $r->getType()) instanceof \ReflectionNamedType ? $type->getName() : (string) $type;
|
||||||
|
|
||||||
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);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw $e;
|
throw $e;
|
||||||
|
@ -187,26 +187,26 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
|
|||||||
$type = $this->extractFromReflectionType($reflectionType, $reflectionMethod);
|
$type = $this->extractFromReflectionType($reflectionType, $reflectionMethod);
|
||||||
|
|
||||||
// HHVM reports variadics with "array" but not builtin type hints
|
// HHVM reports variadics with "array" but not builtin type hints
|
||||||
if (!$reflectionType->isBuiltin() && Type::BUILTIN_TYPE_ARRAY === $type->getBuiltinType()) {
|
if (1 === \count($type) && !$reflectionType->isBuiltin() && Type::BUILTIN_TYPE_ARRAY === $type[0]->getBuiltinType()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} elseif (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $reflectionParameter, $info)) {
|
} elseif (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $reflectionParameter, $info)) {
|
||||||
if (Type::BUILTIN_TYPE_ARRAY === $info[1]) {
|
if (Type::BUILTIN_TYPE_ARRAY === $info[1]) {
|
||||||
$type = new Type(Type::BUILTIN_TYPE_ARRAY, $reflectionParameter->allowsNull(), null, true);
|
$type = [new Type(Type::BUILTIN_TYPE_ARRAY, $reflectionParameter->allowsNull(), null, true)];
|
||||||
} elseif (Type::BUILTIN_TYPE_CALLABLE === $info[1]) {
|
} elseif (Type::BUILTIN_TYPE_CALLABLE === $info[1]) {
|
||||||
$type = new Type(Type::BUILTIN_TYPE_CALLABLE, $reflectionParameter->allowsNull());
|
$type = [new Type(Type::BUILTIN_TYPE_CALLABLE, $reflectionParameter->allowsNull())];
|
||||||
} else {
|
} else {
|
||||||
$type = new Type(Type::BUILTIN_TYPE_OBJECT, $reflectionParameter->allowsNull(), $this->resolveTypeName($info[1], $reflectionMethod));
|
$type = [new Type(Type::BUILTIN_TYPE_OBJECT, $reflectionParameter->allowsNull(), $this->resolveTypeName($info[1], $reflectionMethod))];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\in_array($prefix, $this->arrayMutatorPrefixes)) {
|
if (1 === \count($type) && \in_array($prefix, $this->arrayMutatorPrefixes)) {
|
||||||
$type = new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $type);
|
$type = [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $type[0])];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [$type];
|
return $type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -225,7 +225,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($this->supportsParameterType && $reflectionType = $reflectionMethod->getReturnType()) {
|
if ($this->supportsParameterType && $reflectionType = $reflectionMethod->getReturnType()) {
|
||||||
return [$this->extractFromReflectionType($reflectionType, $reflectionMethod)];
|
return $this->extractFromReflectionType($reflectionType, $reflectionMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
return \in_array($prefix, ['is', 'can']) ? [new Type(Type::BUILTIN_TYPE_BOOL)] : null;
|
return \in_array($prefix, ['is', 'can']) ? [new Type(Type::BUILTIN_TYPE_BOOL)] : null;
|
||||||
@ -234,24 +234,28 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
|
|||||||
/**
|
/**
|
||||||
* Extracts data from the PHP 7 reflection type.
|
* Extracts data from the PHP 7 reflection type.
|
||||||
*
|
*
|
||||||
* @return Type
|
* @return Type[]
|
||||||
*/
|
*/
|
||||||
private function extractFromReflectionType(\ReflectionType $reflectionType, \ReflectionMethod $reflectionMethod)
|
private function extractFromReflectionType(\ReflectionType $reflectionType, \ReflectionMethod $reflectionMethod)
|
||||||
{
|
{
|
||||||
$phpTypeOrClass = $reflectionType instanceof \ReflectionNamedType ? $reflectionType->getName() : $reflectionType->__toString();
|
$types = [];
|
||||||
$nullable = $reflectionType->allowsNull();
|
$nullable = $reflectionType->allowsNull();
|
||||||
|
|
||||||
|
foreach ($reflectionType instanceof \ReflectionUnionType ? $reflectionType->getTypes() : [$reflectionType] as $type) {
|
||||||
|
$phpTypeOrClass = $reflectionType instanceof \ReflectionNamedType ? $reflectionType->getName() : (string) $type;
|
||||||
|
|
||||||
if (Type::BUILTIN_TYPE_ARRAY === $phpTypeOrClass) {
|
if (Type::BUILTIN_TYPE_ARRAY === $phpTypeOrClass) {
|
||||||
$type = new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true);
|
$types[] = new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true);
|
||||||
} elseif ('void' === $phpTypeOrClass) {
|
} elseif ('void' === $phpTypeOrClass || 'null' === $phpTypeOrClass) {
|
||||||
$type = new Type(Type::BUILTIN_TYPE_NULL, $nullable);
|
$types[] = new Type(Type::BUILTIN_TYPE_NULL, $nullable);
|
||||||
} elseif ($reflectionType->isBuiltin()) {
|
} elseif ($reflectionType->isBuiltin()) {
|
||||||
$type = new Type($phpTypeOrClass, $nullable);
|
$types[] = new Type($phpTypeOrClass, $nullable);
|
||||||
} else {
|
} else {
|
||||||
$type = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $this->resolveTypeName($phpTypeOrClass, $reflectionMethod));
|
$types[] = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $this->resolveTypeName($phpTypeOrClass, $reflectionMethod));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $type;
|
return $types;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function resolveTypeName($name, \ReflectionMethod $reflectionMethod)
|
private function resolveTypeName($name, \ReflectionMethod $reflectionMethod)
|
||||||
|
@ -388,7 +388,7 @@ abstract class AbstractNormalizer extends SerializerAwareNormalizer implements N
|
|||||||
try {
|
try {
|
||||||
if (\PHP_VERSION_ID < 70100 && null !== $parameterClass = $parameter->getClass()) {
|
if (\PHP_VERSION_ID < 70100 && null !== $parameterClass = $parameter->getClass()) {
|
||||||
$parameterClass = $parameterClass->name;
|
$parameterClass = $parameterClass->name;
|
||||||
} elseif (\PHP_VERSION_ID >= 70100 && ($parameterType = $parameter->getType()) && !$parameterType->isBuiltin()) {
|
} elseif (\PHP_VERSION_ID >= 70100 && ($parameterType = $parameter->getType()) instanceof \ReflectionNamedType && !$parameterType->isBuiltin()) {
|
||||||
$parameterClass = $parameterType->getName();
|
$parameterClass = $parameterType->getName();
|
||||||
new \ReflectionClass($parameterClass); // throws a \ReflectionException if the class doesn't exist
|
new \ReflectionClass($parameterClass); // throws a \ReflectionException if the class doesn't exist
|
||||||
} else {
|
} else {
|
||||||
|
@ -91,7 +91,7 @@ class ReflectionCaster
|
|||||||
$prefix = Caster::PREFIX_VIRTUAL;
|
$prefix = Caster::PREFIX_VIRTUAL;
|
||||||
|
|
||||||
$a += [
|
$a += [
|
||||||
$prefix.'name' => $c instanceof \ReflectionNamedType ? $c->getName() : $c->__toString(),
|
$prefix.'name' => $c instanceof \ReflectionNamedType ? $c->getName() : (string) $c,
|
||||||
$prefix.'allowsNull' => $c->allowsNull(),
|
$prefix.'allowsNull' => $c->allowsNull(),
|
||||||
$prefix.'isBuiltin' => $c->isBuiltin(),
|
$prefix.'isBuiltin' => $c->isBuiltin(),
|
||||||
];
|
];
|
||||||
@ -178,7 +178,7 @@ class ReflectionCaster
|
|||||||
|
|
||||||
if (isset($a[$prefix.'returnType'])) {
|
if (isset($a[$prefix.'returnType'])) {
|
||||||
$v = $a[$prefix.'returnType'];
|
$v = $a[$prefix.'returnType'];
|
||||||
$v = $v instanceof \ReflectionNamedType ? $v->getName() : $v->__toString();
|
$v = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v;
|
||||||
$a[$prefix.'returnType'] = new ClassStub($a[$prefix.'returnType']->allowsNull() ? '?'.$v : $v, [class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', '']);
|
$a[$prefix.'returnType'] = new ClassStub($a[$prefix.'returnType']->allowsNull() ? '?'.$v : $v, [class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', '']);
|
||||||
}
|
}
|
||||||
if (isset($a[$prefix.'class'])) {
|
if (isset($a[$prefix.'class'])) {
|
||||||
@ -247,7 +247,7 @@ class ReflectionCaster
|
|||||||
|
|
||||||
if (method_exists($c, 'getType')) {
|
if (method_exists($c, 'getType')) {
|
||||||
if ($v = $c->getType()) {
|
if ($v = $c->getType()) {
|
||||||
$a[$prefix.'typeHint'] = $v instanceof \ReflectionNamedType ? $v->getName() : $v->__toString();
|
$a[$prefix.'typeHint'] = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v;
|
||||||
}
|
}
|
||||||
} elseif (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $c, $v)) {
|
} elseif (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $c, $v)) {
|
||||||
$a[$prefix.'typeHint'] = $v[1];
|
$a[$prefix.'typeHint'] = $v[1];
|
||||||
|
Reference in New Issue
Block a user