diff --git a/src/DI/Helpers.php b/src/DI/Helpers.php index 35d0328e9..b8540f53e 100644 --- a/src/DI/Helpers.php +++ b/src/DI/Helpers.php @@ -234,8 +234,8 @@ public static function ensureClassType(?Type $type, string $hint): string { if (!$type) { throw new ServiceCreationException(sprintf('%s is not declared.', ucfirst($hint))); - } elseif (!$type->isClass() || $type->isUnion()) { - throw new ServiceCreationException(sprintf("%s is not expected to be nullable/union/intersection/built-in, '%s' given.", ucfirst($hint), $type)); + } elseif (!$type->isClass() || $type->allows('null')) { + throw new ServiceCreationException(sprintf("%s is expected to not be nullable/built-in/complex, '%s' given.", ucfirst($hint), $type)); } $class = $type->getSingleName(); diff --git a/src/DI/Resolver.php b/src/DI/Resolver.php index 33265e543..d687a8033 100644 --- a/src/DI/Resolver.php +++ b/src/DI/Resolver.php @@ -625,17 +625,10 @@ public static function autowireArguments( */ private static function autowireArgument(\ReflectionParameter $parameter, callable $getter) { - $method = $parameter->getDeclaringFunction(); $desc = Reflection::toString($parameter); $type = Nette\Utils\Type::fromReflection($parameter); - if ($parameter->getType() instanceof \ReflectionIntersectionType) { - throw new ServiceCreationException(sprintf( - 'Parameter %s has intersection type, so its value must be specified.', - $desc - )); - - } elseif ($type && $type->isClass()) { + if ($type && $type->isClass()) { $class = $type->getSingleName(); try { $res = $getter($class, true); @@ -660,13 +653,8 @@ private static function autowireArgument(\ReflectionParameter $parameter, callab $desc )); } - } elseif ( - $method instanceof \ReflectionMethod - && $type && $type->getSingleName() === 'array' - && preg_match('#@param[ \t]+(?|([\w\\\\]+)\[\]|array)[ \t]+\$' . $parameter->name . '#', (string) $method->getDocComment(), $m) - && ($itemType = Reflection::expandClassName($m[1], $method->getDeclaringClass())) - && (class_exists($itemType) || interface_exists($itemType)) - ) { + + } elseif ($itemType = self::isArrayOf($parameter, $type)) { return $getter($itemType, false); } elseif ( @@ -684,8 +672,26 @@ private static function autowireArgument(\ReflectionParameter $parameter, callab throw new ServiceCreationException(sprintf( 'Parameter %s has %s, so its value must be specified.', $desc, - $type && $type->isUnion() ? 'union type and no default value' : 'no class type or default value' + $type && !$type->isSingle() ? 'complex type and no default value' : 'no class type or default value' )); } } + + + private static function isArrayOf(\ReflectionParameter $parameter, ?Nette\Utils\Type $type): ?string + { + $method = $parameter->getDeclaringFunction(); + return $method instanceof \ReflectionMethod + && $type + && $type->getSingleName() === 'array' + && preg_match( + '#@param[ \t]+(?|([\w\\\\]+)\[\]|array)[ \t]+\$' . $parameter->name . '#', + (string) $method->getDocComment(), + $m + ) + && ($itemType = Reflection::expandClassName($m[1], $method->getDeclaringClass())) + && (class_exists($itemType) || interface_exists($itemType)) + ? $itemType + : null; + } } diff --git a/tests/DI/Container.dynamic.php80.phpt b/tests/DI/Container.dynamic.php80.phpt index a4166f118..384474b5e 100644 --- a/tests/DI/Container.dynamic.php80.phpt +++ b/tests/DI/Container.dynamic.php80.phpt @@ -19,4 +19,4 @@ $container = new Container; Assert::exception(function () use ($container) { @$container->addService('six', function (): stdClass|Closure {}); // @ triggers service should be defined as "imported" $container->getService('six'); -}, Nette\InvalidStateException::class, "Return type of closure is not expected to be nullable/union/intersection/built-in, 'stdClass|Closure' given."); +}, Nette\InvalidStateException::class, "Return type of closure is expected to not be nullable/built-in/complex, 'stdClass|Closure' given."); diff --git a/tests/DI/ContainerBuilder.resolveTypes.php80.phpt b/tests/DI/ContainerBuilder.resolveTypes.php80.phpt index 161e93e38..3e86e7085 100644 --- a/tests/DI/ContainerBuilder.resolveTypes.php80.phpt +++ b/tests/DI/ContainerBuilder.resolveTypes.php80.phpt @@ -27,4 +27,4 @@ Assert::exception(function () { $builder->addDefinition('a') ->setFactory([Factory::class, 'createUnion']); $container = createContainer($builder); -}, Nette\DI\ServiceCreationException::class, "Service 'a': Return type of Factory::createUnion() is not expected to be nullable/union/intersection/built-in, 'stdClass|array' given."); +}, Nette\DI\ServiceCreationException::class, "Service 'a': Return type of Factory::createUnion() is expected to not be nullable/built-in/complex, 'stdClass|array' given."); diff --git a/tests/DI/ContainerBuilder.resolveTypes.phpt b/tests/DI/ContainerBuilder.resolveTypes.phpt index 602cd36c6..e225d4843 100644 --- a/tests/DI/ContainerBuilder.resolveTypes.phpt +++ b/tests/DI/ContainerBuilder.resolveTypes.phpt @@ -119,21 +119,21 @@ Assert::exception(function () { $builder->addDefinition('a') ->setFactory([Factory::class, 'createNullableClass']); $container = createContainer($builder); -}, Nette\DI\ServiceCreationException::class, "Service 'a': Return type of Factory::createNullableClass() is not expected to be nullable/union/intersection/built-in, '?stdClass' given."); +}, Nette\DI\ServiceCreationException::class, "Service 'a': Return type of Factory::createNullableClass() is expected to not be nullable/built-in/complex, '?stdClass' given."); Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') ->setFactory([Factory::class, 'createScalarPhpDoc']); $container = createContainer($builder); -}, Nette\DI\ServiceCreationException::class, "Service 'a': Return type of Factory::createScalarPhpDoc() is not expected to be nullable/union/intersection/built-in, 'array' given."); +}, Nette\DI\ServiceCreationException::class, "Service 'a': Return type of Factory::createScalarPhpDoc() is expected to not be nullable/built-in/complex, 'array' given."); Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') ->setFactory([Factory::class, 'createScalar']); $container = createContainer($builder); -}, Nette\DI\ServiceCreationException::class, "Service 'a': Return type of Factory::createScalar() is not expected to be nullable/union/intersection/built-in, 'array' given."); +}, Nette\DI\ServiceCreationException::class, "Service 'a': Return type of Factory::createScalar() is expected to not be nullable/built-in/complex, 'array' given."); Assert::exception(function () { $builder = new DI\ContainerBuilder; @@ -154,7 +154,7 @@ Assert::exception(function () { $builder->addDefinition('a') ->setFactory([Factory::class, 'createObjectNullable']); $container = createContainer($builder); -}, Nette\DI\ServiceCreationException::class, "Service 'a': Return type of Factory::createObjectNullable() is not expected to be nullable/union/intersection/built-in, '?object' given."); +}, Nette\DI\ServiceCreationException::class, "Service 'a': Return type of Factory::createObjectNullable() is expected to not be nullable/built-in/complex, '?object' given."); Assert::exception(function () { $builder = new DI\ContainerBuilder; diff --git a/tests/DI/InjectExtension.getInjectProperties().php80.phpt b/tests/DI/InjectExtension.getInjectProperties().php80.phpt index bc72b460f..68c2a7ea7 100644 --- a/tests/DI/InjectExtension.getInjectProperties().php80.phpt +++ b/tests/DI/InjectExtension.getInjectProperties().php80.phpt @@ -29,7 +29,7 @@ class EClass Assert::exception(function () { InjectExtension::getInjectProperties(AClass::class); -}, Nette\InvalidStateException::class, "Type of property AClass::\$var is not expected to be nullable/union/intersection/built-in, 'AClass|stdClass' given."); +}, Nette\InvalidStateException::class, "Type of property AClass::\$var is expected to not be nullable/built-in/complex, 'AClass|stdClass' given."); Assert::same([ 'varA' => 'stdClass', diff --git a/tests/DI/Resolver.autowireArguments.80.phpt b/tests/DI/Resolver.autowireArguments.80.phpt index 7ebf0b136..f7f20d733 100644 --- a/tests/DI/Resolver.autowireArguments.80.phpt +++ b/tests/DI/Resolver.autowireArguments.80.phpt @@ -25,7 +25,7 @@ Assert::exception(function () { [], function () {} ); -}, Nette\InvalidStateException::class, 'Parameter $x in {closure}%a?% has union type and no default value, so its value must be specified.'); +}, Nette\InvalidStateException::class, 'Parameter $x in {closure}%a?% has complex type and no default value, so its value must be specified.'); // nullable union Assert::same( diff --git a/tests/DI/Resolver.autowireArguments.81.phpt b/tests/DI/Resolver.autowireArguments.81.phpt index 721cb1383..3da1ef8ec 100644 --- a/tests/DI/Resolver.autowireArguments.81.phpt +++ b/tests/DI/Resolver.autowireArguments.81.phpt @@ -30,7 +30,7 @@ Assert::exception(function () { [], function () {} ); -}, Nette\InvalidStateException::class, 'Parameter $x in {closure}%a?% has intersection type, so its value must be specified.'); +}, Nette\InvalidStateException::class, 'Parameter $x in {closure}%a?% has complex type and no default value, so its value must be specified.'); // object as default Assert::same( diff --git a/tests/DI/Resolver.autowireArguments.82.phpt b/tests/DI/Resolver.autowireArguments.82.phpt new file mode 100644 index 000000000..1258312b5 --- /dev/null +++ b/tests/DI/Resolver.autowireArguments.82.phpt @@ -0,0 +1,33 @@ +