[3.4] Fix support for PHP8 union types

This commit is contained in:
Nicolas Grekas 2020-06-15 16:43:28 +02:00
parent 57251cdcdd
commit e09372bcbf
7 changed files with 61 additions and 44 deletions

View File

@ -177,6 +177,7 @@ class ReflectionClassResource implements SelfCheckingResourceInterface, \Seriali
if (!$parametersWithUndefinedConstants) {
yield preg_replace('/^ @@.*/m', '', $m);
} else {
$t = \PHP_VERSION_ID >= 70000 ? $m->getReturnType() : '';
$stack = [
$m->getDocComment(),
$m->getName(),
@ -187,15 +188,16 @@ class ReflectionClassResource implements SelfCheckingResourceInterface, \Seriali
$m->isPrivate(),
$m->isProtected(),
$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) {
if (!isset($parametersWithUndefinedConstants[$p->name])) {
$stack[] = (string) $p;
} else {
$t = \PHP_VERSION_ID >= 70000 ? $p->getType() : '';
$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[] = \PHP_VERSION_ID >= 50600 ? $p->isVariadic() : '';
$stack[] = $p->getName();

View File

@ -39,26 +39,36 @@ class ProxyHelper
if (!$type) {
return null;
}
if (!\is_string($type)) {
$name = $type instanceof \ReflectionNamedType ? $type->getName() : $type->__toString();
if ($type->isBuiltin()) {
return $noBuiltin ? null : $name;
$types = [];
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);
$prefix = $noBuiltin ? '' : '\\';
if ('self' !== $lcName && 'parent' !== $lcName) {
$types[] = '' !== $prefix ? $prefix.$name : $name;
continue;
}
if (!$r instanceof \ReflectionMethod) {
continue;
}
if ('self' === $lcName) {
$types[] = $prefix.$r->getDeclaringClass()->name;
} else {
$types[] = ($parent = $r->getDeclaringClass()->getParentClass()) ? $prefix.$parent->name : null;
}
}
$lcName = strtolower($name);
$prefix = $noBuiltin ? '' : '\\';
if ('self' !== $lcName && 'parent' !== $lcName) {
return $prefix.$name;
}
if (!$r instanceof \ReflectionMethod) {
return null;
}
if ('self' === $lcName) {
return $prefix.$r->getDeclaringClass()->name;
}
return ($parent = $r->getDeclaringClass()->getParentClass()) ? $prefix.$parent->name : null;
return $types ? implode('|', $types) : null;
}
}

View File

@ -1076,7 +1076,7 @@ class OptionsResolver implements Options
return ($class = $parameter->getClass()) ? $class->name : null;
}
if (!($type = $parameter->getType()) || $type->isBuiltin()) {
if (!($type = $parameter->getType()) instanceof \ReflectionNamedType || $type->isBuiltin()) {
return null;
}

View File

@ -519,8 +519,9 @@ class PropertyAccessor implements PropertyAccessorInterface
// 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)) {
$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;

View File

@ -187,26 +187,26 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
$type = $this->extractFromReflectionType($reflectionType, $reflectionMethod);
// 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;
}
} elseif (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $reflectionParameter, $info)) {
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]) {
$type = new Type(Type::BUILTIN_TYPE_CALLABLE, $reflectionParameter->allowsNull());
$type = [new Type(Type::BUILTIN_TYPE_CALLABLE, $reflectionParameter->allowsNull())];
} 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 {
return null;
}
if (\in_array($prefix, $this->arrayMutatorPrefixes)) {
$type = new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $type);
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[0])];
}
return [$type];
return $type;
}
/**
@ -225,7 +225,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
}
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;
@ -234,24 +234,28 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
/**
* Extracts data from the PHP 7 reflection type.
*
* @return Type
* @return Type[]
*/
private function extractFromReflectionType(\ReflectionType $reflectionType, \ReflectionMethod $reflectionMethod)
{
$phpTypeOrClass = $reflectionType instanceof \ReflectionNamedType ? $reflectionType->getName() : $reflectionType->__toString();
$types = [];
$nullable = $reflectionType->allowsNull();
if (Type::BUILTIN_TYPE_ARRAY === $phpTypeOrClass) {
$type = new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true);
} elseif ('void' === $phpTypeOrClass) {
$type = new Type(Type::BUILTIN_TYPE_NULL, $nullable);
} elseif ($reflectionType->isBuiltin()) {
$type = new Type($phpTypeOrClass, $nullable);
} else {
$type = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $this->resolveTypeName($phpTypeOrClass, $reflectionMethod));
foreach ($reflectionType instanceof \ReflectionUnionType ? $reflectionType->getTypes() : [$reflectionType] as $type) {
$phpTypeOrClass = $reflectionType instanceof \ReflectionNamedType ? $reflectionType->getName() : (string) $type;
if (Type::BUILTIN_TYPE_ARRAY === $phpTypeOrClass) {
$types[] = new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true);
} elseif ('void' === $phpTypeOrClass || 'null' === $phpTypeOrClass) {
$types[] = new Type(Type::BUILTIN_TYPE_NULL, $nullable);
} elseif ($reflectionType->isBuiltin()) {
$types[] = new Type($phpTypeOrClass, $nullable);
} else {
$types[] = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $this->resolveTypeName($phpTypeOrClass, $reflectionMethod));
}
}
return $type;
return $types;
}
private function resolveTypeName($name, \ReflectionMethod $reflectionMethod)

View File

@ -388,7 +388,7 @@ abstract class AbstractNormalizer extends SerializerAwareNormalizer implements N
try {
if (\PHP_VERSION_ID < 70100 && null !== $parameterClass = $parameter->getClass()) {
$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();
new \ReflectionClass($parameterClass); // throws a \ReflectionException if the class doesn't exist
} else {

View File

@ -91,7 +91,7 @@ class ReflectionCaster
$prefix = Caster::PREFIX_VIRTUAL;
$a += [
$prefix.'name' => $c instanceof \ReflectionNamedType ? $c->getName() : $c->__toString(),
$prefix.'name' => $c instanceof \ReflectionNamedType ? $c->getName() : (string) $c,
$prefix.'allowsNull' => $c->allowsNull(),
$prefix.'isBuiltin' => $c->isBuiltin(),
];
@ -178,7 +178,7 @@ class ReflectionCaster
if (isset($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 : '', '']);
}
if (isset($a[$prefix.'class'])) {
@ -247,7 +247,7 @@ class ReflectionCaster
if (method_exists($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)) {
$a[$prefix.'typeHint'] = $v[1];