Fix support for PHP8 union types

This commit is contained in:
Nicolas Grekas 2020-06-15 16:43:28 +02:00
parent 7eca3a5970
commit da68e66a99
10 changed files with 61 additions and 21 deletions

View File

@ -153,9 +153,26 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass
/**
* @throws InvalidParameterTypeException When a parameter is not compatible with the declared type
*/
private function checkType(Definition $checkedDefinition, $value, \ReflectionParameter $parameter, ?string $envPlaceholderUniquePrefix): void
private function checkType(Definition $checkedDefinition, $value, \ReflectionParameter $parameter, ?string $envPlaceholderUniquePrefix, string $type = null): void
{
$type = $parameter->getType()->getName();
if (null === $type) {
$type = $parameter->getType();
if ($type instanceof \ReflectionUnionType) {
foreach ($type->getTypes() as $type) {
try {
$this->checkType($checkedDefinition, $value, $parameter, $envPlaceholderUniquePrefix, $type);
return;
} catch (InvalidParameterTypeException $e) {
}
}
throw new InvalidParameterTypeException($this->currentId, $e->getCode(), $parameter);
}
$type = $type->getName();
}
if ($value instanceof Reference) {
if (!$this->container->has($value = (string) $value)) {
@ -266,7 +283,7 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass
return;
}
$checkFunction = sprintf('is_%s', $parameter->getType()->getName());
$checkFunction = sprintf('is_%s', $type);
if (!$parameter->getType()->isBuiltin() || !$checkFunction($value)) {
throw new InvalidParameterTypeException($this->currentId, \is_object($value) ? $class : \gettype($value), $parameter);

View File

@ -48,7 +48,7 @@ class Preloader
}
}
private static function doPreload(string $class, array &$preloaded)
private static function doPreload(string $class, array &$preloaded): void
{
if (isset($preloaded[$class]) || \in_array($class, ['self', 'static', 'parent'], true)) {
return;
@ -68,9 +68,7 @@ class Preloader
if (\PHP_VERSION_ID >= 70400) {
foreach ($r->getProperties(\ReflectionProperty::IS_PUBLIC) as $p) {
if (($t = $p->getType()) && !$t->isBuiltin()) {
self::doPreload($t->getName(), $preloaded);
}
self::preloadType($p->getType(), $preloaded);
}
}
@ -84,17 +82,26 @@ class Preloader
}
}
if (($t = $p->getType()) && !$t->isBuiltin()) {
self::doPreload($t->getName(), $preloaded);
}
self::preloadType($p->getType(), $preloaded);
}
if (($t = $m->getReturnType()) && !$t->isBuiltin()) {
self::doPreload($t->getName(), $preloaded);
}
self::preloadType($p->getReturnType(), $preloaded);
}
} catch (\ReflectionException $e) {
// ignore missing classes
}
}
private static function preloadType(?\ReflectionType $t, array &$preloaded): void
{
if (!$t || $t->isBuiltin()) {
return;
}
foreach ($t instanceof \ReflectionUnionType ? $t->getTypes() : [$t] as $t) {
if (!$t->isBuiltin()) {
self::doPreload($t instanceof \ReflectionNamedType ? $t->getName() : $t, $preloaded);
}
}
}
}

View File

@ -21,6 +21,9 @@ class InvalidParameterTypeException extends InvalidArgumentException
{
public function __construct(string $serviceId, string $type, \ReflectionParameter $parameter)
{
parent::__construct(sprintf('Invalid definition for service "%s": argument %d of "%s::%s" accepts "%s", "%s" passed.', $serviceId, 1 + $parameter->getPosition(), $parameter->getDeclaringClass()->getName(), $parameter->getDeclaringFunction()->getName(), $parameter->getType()->getName(), $type));
$acceptedType = $parameter->getType();
$acceptedType = $acceptedType instanceof \ReflectionNamedType ? $acceptedType->getName() : (string) $acceptedType;
parent::__construct(sprintf('Invalid definition for service "%s": argument %d of "%s::%s" accepts "%s", "%s" passed.', $serviceId, 1 + $parameter->getPosition(), $parameter->getDeclaringClass()->getName(), $parameter->getDeclaringFunction()->getName(), $acceptedType, $type), $type);
}
}

View File

@ -138,7 +138,7 @@ class RegisterListenersPass implements CompilerPassInterface
|| !($r = $container->getReflectionClass($class, false))
|| !$r->hasMethod($method)
|| 1 > ($m = $r->getMethod($method))->getNumberOfParameters()
|| !($type = $m->getParameters()[0]->getType())
|| !($type = $m->getParameters()[0]->getType()) instanceof \ReflectionNamedType
|| $type->isBuiltin()
|| Event::class === ($name = $type->getName())
|| LegacyEvent::class === $name

View File

@ -45,7 +45,7 @@ final class ArgumentMetadataFactory implements ArgumentMetadataFactoryInterface
*/
private function getType(\ReflectionParameter $parameter, \ReflectionFunctionAbstract $function): ?string
{
if (!$type = $parameter->getType()) {
if (!($type = $parameter->getType()) instanceof \ReflectionNamedType) {
return null;
}
$name = $type->getName();

View File

@ -99,7 +99,7 @@ class ErrorListener implements EventSubscriberInterface
$r = new \ReflectionFunction(\Closure::fromCallable($event->getController()));
$r = $r->getParameters()[$k] ?? null;
if ($r && (!$r->hasType() || \in_array($r->getType()->getName(), [FlattenException::class, LegacyFlattenException::class], true))) {
if ($r && (!($r = $r->getType()) instanceof \ReflectionNamedType || \in_array($r->getName(), [FlattenException::class, LegacyFlattenException::class], true))) {
$arguments = $event->getArguments();
$arguments[$k] = FlattenException::createFromThrowable($e);
$event->setArguments($arguments);

View File

@ -228,11 +228,24 @@ class MessengerPass implements CompilerPassInterface
throw new RuntimeException(sprintf('Invalid handler service "%s": argument "$%s" of method "%s::__invoke()" must have a type-hint corresponding to the message class it handles.', $serviceId, $parameters[0]->getName(), $handlerClass->getName()));
}
if ($type instanceof \ReflectionUnionType) {
$types = [];
foreach ($type->getTypes() as $type) {
if (!$type->isBuiltin()) {
$types[] = (string) $type;
}
}
if ($types) {
return $types;
}
}
if ($type->isBuiltin()) {
throw new RuntimeException(sprintf('Invalid handler service "%s": type-hint of argument "$%s" in method "%s::__invoke()" must be a class , "%s" given.', $serviceId, $parameters[0]->getName(), $handlerClass->getName(), $type instanceof \ReflectionNamedType ? $type->getName() : (string) $type));
}
return [$parameters[0]->getType()->getName()];
return [$type->getName()];
}
private function registerReceivers(ContainerBuilder $container, array $busIds)

View File

@ -201,7 +201,7 @@ class OptionsResolver implements Options
return $this;
}
if (isset($params[0]) && null !== ($type = $params[0]->getType()) && self::class === $type->getName() && (!isset($params[1]) || (null !== ($type = $params[1]->getType()) && Options::class === $type->getName()))) {
if (isset($params[0]) && null !== ($type = $params[0]->getType()) && self::class === $type->getName() && (!isset($params[1]) || (($type = $params[1]->getType()) instanceof \ReflectionNamedType && Options::class === $type->getName()))) {
// Store closure for later evaluation
$this->nested[$option][] = $value;
$this->defaults[$option] = [];

View File

@ -87,7 +87,7 @@ trait ServiceLocatorTrait
} else {
$type = (new \ReflectionFunction($factory))->getReturnType();
$this->providedTypes[$name] = $type ? ($type->allowsNull() ? '?' : '').$type->getName() : '?';
$this->providedTypes[$name] = $type ? ($type->allowsNull() ? '?' : '').($type instanceof \ReflectionNamedType ? $type->getName() : $type) : '?';
}
}
}

View File

@ -40,7 +40,7 @@ trait ServiceSubscriberTrait
}
if (self::class === $method->getDeclaringClass()->name && ($returnType = $method->getReturnType()) && !$returnType->isBuiltin()) {
$services[self::class.'::'.$method->name] = '?'.$returnType->getName();
$services[self::class.'::'.$method->name] = '?'.($returnType instanceof \ReflectionNamedType ? $returnType->getName() : $type);
}
}