bug #37341 [4.4] Fix support for PHP8 union types (nicolas-grekas)
This PR was merged into the 4.4 branch.
Discussion
----------
[4.4] Fix support for PHP8 union types
| Q | A
| ------------- | ---
| Branch? | 4.4
| Bug fix? | yes
| New feature? | no
| Deprecations? | no
| Tickets | -
| License | MIT
| Doc PR | -
Same as #37340 for 4.4
Commits
-------
da68e66a99
Fix support for PHP8 union types
This commit is contained in:
commit
a6e3789882
@ -153,9 +153,26 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass
|
|||||||
/**
|
/**
|
||||||
* @throws InvalidParameterTypeException When a parameter is not compatible with the declared type
|
* @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 ($value instanceof Reference) {
|
||||||
if (!$this->container->has($value = (string) $value)) {
|
if (!$this->container->has($value = (string) $value)) {
|
||||||
@ -266,7 +283,7 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$checkFunction = sprintf('is_%s', $parameter->getType()->getName());
|
$checkFunction = sprintf('is_%s', $type);
|
||||||
|
|
||||||
if (!$parameter->getType()->isBuiltin() || !$checkFunction($value)) {
|
if (!$parameter->getType()->isBuiltin() || !$checkFunction($value)) {
|
||||||
throw new InvalidParameterTypeException($this->currentId, \is_object($value) ? $class : \gettype($value), $parameter);
|
throw new InvalidParameterTypeException($this->currentId, \is_object($value) ? $class : \gettype($value), $parameter);
|
||||||
|
@ -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)) {
|
if (isset($preloaded[$class]) || \in_array($class, ['self', 'static', 'parent'], true)) {
|
||||||
return;
|
return;
|
||||||
@ -68,9 +68,7 @@ class Preloader
|
|||||||
|
|
||||||
if (\PHP_VERSION_ID >= 70400) {
|
if (\PHP_VERSION_ID >= 70400) {
|
||||||
foreach ($r->getProperties(\ReflectionProperty::IS_PUBLIC) as $p) {
|
foreach ($r->getProperties(\ReflectionProperty::IS_PUBLIC) as $p) {
|
||||||
if (($t = $p->getType()) && !$t->isBuiltin()) {
|
self::preloadType($p->getType(), $preloaded);
|
||||||
self::doPreload($t->getName(), $preloaded);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,17 +82,26 @@ class Preloader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (($t = $p->getType()) && !$t->isBuiltin()) {
|
self::preloadType($p->getType(), $preloaded);
|
||||||
self::doPreload($t->getName(), $preloaded);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (($t = $m->getReturnType()) && !$t->isBuiltin()) {
|
self::preloadType($p->getReturnType(), $preloaded);
|
||||||
self::doPreload($t->getName(), $preloaded);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (\ReflectionException $e) {
|
} catch (\ReflectionException $e) {
|
||||||
// ignore missing classes
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,9 @@ class InvalidParameterTypeException extends InvalidArgumentException
|
|||||||
{
|
{
|
||||||
public function __construct(string $serviceId, string $type, \ReflectionParameter $parameter)
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,7 +138,7 @@ class RegisterListenersPass implements CompilerPassInterface
|
|||||||
|| !($r = $container->getReflectionClass($class, false))
|
|| !($r = $container->getReflectionClass($class, false))
|
||||||
|| !$r->hasMethod($method)
|
|| !$r->hasMethod($method)
|
||||||
|| 1 > ($m = $r->getMethod($method))->getNumberOfParameters()
|
|| 1 > ($m = $r->getMethod($method))->getNumberOfParameters()
|
||||||
|| !($type = $m->getParameters()[0]->getType())
|
|| !($type = $m->getParameters()[0]->getType()) instanceof \ReflectionNamedType
|
||||||
|| $type->isBuiltin()
|
|| $type->isBuiltin()
|
||||||
|| Event::class === ($name = $type->getName())
|
|| Event::class === ($name = $type->getName())
|
||||||
|| LegacyEvent::class === $name
|
|| LegacyEvent::class === $name
|
||||||
|
@ -45,7 +45,7 @@ final class ArgumentMetadataFactory implements ArgumentMetadataFactoryInterface
|
|||||||
*/
|
*/
|
||||||
private function getType(\ReflectionParameter $parameter, \ReflectionFunctionAbstract $function): ?string
|
private function getType(\ReflectionParameter $parameter, \ReflectionFunctionAbstract $function): ?string
|
||||||
{
|
{
|
||||||
if (!$type = $parameter->getType()) {
|
if (!($type = $parameter->getType()) instanceof \ReflectionNamedType) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
$name = $type->getName();
|
$name = $type->getName();
|
||||||
|
@ -99,7 +99,7 @@ class ErrorListener implements EventSubscriberInterface
|
|||||||
$r = new \ReflectionFunction(\Closure::fromCallable($event->getController()));
|
$r = new \ReflectionFunction(\Closure::fromCallable($event->getController()));
|
||||||
$r = $r->getParameters()[$k] ?? null;
|
$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 = $event->getArguments();
|
||||||
$arguments[$k] = FlattenException::createFromThrowable($e);
|
$arguments[$k] = FlattenException::createFromThrowable($e);
|
||||||
$event->setArguments($arguments);
|
$event->setArguments($arguments);
|
||||||
|
@ -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()));
|
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()) {
|
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));
|
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)
|
private function registerReceivers(ContainerBuilder $container, array $busIds)
|
||||||
|
@ -201,7 +201,7 @@ class OptionsResolver implements Options
|
|||||||
return $this;
|
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
|
// Store closure for later evaluation
|
||||||
$this->nested[$option][] = $value;
|
$this->nested[$option][] = $value;
|
||||||
$this->defaults[$option] = [];
|
$this->defaults[$option] = [];
|
||||||
|
@ -87,7 +87,7 @@ trait ServiceLocatorTrait
|
|||||||
} else {
|
} else {
|
||||||
$type = (new \ReflectionFunction($factory))->getReturnType();
|
$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) : '?';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ trait ServiceSubscriberTrait
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (self::class === $method->getDeclaringClass()->name && ($returnType = $method->getReturnType()) && !$returnType->isBuiltin()) {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user