From 877e98ec3f5be38f78ecfcf0277a4f7e502b73f8 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 29 Nov 2024 21:16:07 +0100 Subject: [PATCH 01/22] Resolver: triggers error when reference is called with arguments --- src/DI/Resolver.php | 4 ++++ tests/DI/Compiler.services.create.phpt | 7 ------- tests/DI/files/compiler.services.create.neon | 2 -- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/DI/Resolver.php b/src/DI/Resolver.php index 3e6afc3ff..c9942d3ad 100644 --- a/src/DI/Resolver.php +++ b/src/DI/Resolver.php @@ -241,6 +241,10 @@ public function completeStatement(Statement $statement, bool $currentServiceAllo break; case $entity instanceof Reference: + if ($arguments) { + $e = $this->completeException(new ServiceCreationException(sprintf('Parameters were passed to reference @%s, although references cannot have any parameters.', $entity->getValue())), $this->currentService); + trigger_error($e->getMessage(), E_USER_DEPRECATED); + } $entity = [new Reference(ContainerBuilder::ThisContainer), Container::getMethodName($entity->getValue())]; break; diff --git a/tests/DI/Compiler.services.create.phpt b/tests/DI/Compiler.services.create.phpt index 9f8b11b8a..b840d0bea 100644 --- a/tests/DI/Compiler.services.create.phpt +++ b/tests/DI/Compiler.services.create.phpt @@ -91,9 +91,6 @@ Assert::notSame($container->getService('one'), $container->getService('reference Assert::type(Ipsum::class, $container->getService('calledService')); Assert::same($container->getService('one'), $container->getService('calledService')); // called without arguments is reference -Assert::type(Ipsum::class, $container->getService('calledServiceWithArgs')); -Assert::notSame($container->getService('one'), $container->getService('calledServiceWithArgs')); - Assert::type(stdClass::class, $container->getByType('\stdClass')); @@ -105,10 +102,6 @@ Assert::type(Ipsum::class, $container->getService('calledServiceAsParam')); Assert::type(Ipsum::class, $container->getService('calledServiceAsParam')->arg); Assert::notSame($container->getService('one'), $container->getService('calledServiceAsParam')->arg); -Assert::type(Ipsum::class, $container->getService('calledServiceWithArgsAsParam')); -Assert::type(Ipsum::class, $container->getService('calledServiceWithArgsAsParam')->arg); -Assert::notSame($container->getService('one'), $container->getService('calledServiceWithArgsAsParam')->arg); - Assert::type(Lorem::class, $container->getService('rich1')); Assert::same(1, $container->getService('rich1')->arg); diff --git a/tests/DI/files/compiler.services.create.neon b/tests/DI/files/compiler.services.create.neon index 26c24e412..6b2bafc82 100644 --- a/tests/DI/files/compiler.services.create.neon +++ b/tests/DI/files/compiler.services.create.neon @@ -11,9 +11,7 @@ services: serviceAsParam: Ipsum(@one) calledService: @one() - calledServiceWithArgs: @one(1) calledServiceAsParam: Ipsum(@one()) - calledServiceWithArgsAsParam: Ipsum(@one(1)) one: type: %class% From b06e66e5128da898c14b1b21ad915acecfbf6432 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 2 Dec 2024 00:10:08 +0100 Subject: [PATCH 02/22] Definition::generateMethod() replaced with generateCode() --- src/DI/Definitions/AccessorDefinition.php | 4 ++-- src/DI/Definitions/Definition.php | 9 ++++++++- src/DI/Definitions/FactoryDefinition.php | 4 ++-- src/DI/Definitions/ImportedDefinition.php | 5 ++--- src/DI/Definitions/LocatorDefinition.php | 4 ++-- src/DI/Definitions/ServiceDefinition.php | 7 +++---- 6 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/DI/Definitions/AccessorDefinition.php b/src/DI/Definitions/AccessorDefinition.php index bd6c1a1a5..3c4b1d3fd 100644 --- a/src/DI/Definitions/AccessorDefinition.php +++ b/src/DI/Definitions/AccessorDefinition.php @@ -107,7 +107,7 @@ public function complete(Nette\DI\Resolver $resolver): void } - public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void + public function generateCode(Nette\DI\PhpGenerator $generator): string { $class = (new Nette\PhpGenerator\ClassType) ->addImplement($this->getType()); @@ -123,6 +123,6 @@ public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGe ->setBody('return $this->container->getService(?);', [$this->reference->getValue()]) ->setReturnType((string) Type::fromReflection($rm)); - $method->setBody('return new class ($this) ' . $class . ';'); + return 'return new class ($this) ' . $class . ';'; } } diff --git a/src/DI/Definitions/Definition.php b/src/DI/Definitions/Definition.php index ca96659b8..d8ac3a0d1 100644 --- a/src/DI/Definitions/Definition.php +++ b/src/DI/Definitions/Definition.php @@ -147,7 +147,7 @@ abstract public function resolveType(Nette\DI\Resolver $resolver): void; abstract public function complete(Nette\DI\Resolver $resolver): void; - abstract public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void; + //abstract public function generateCode(Nette\DI\PhpGenerator $generator): string; final public function setNotifier(?\Closure $notifier): void @@ -159,6 +159,13 @@ final public function setNotifier(?\Closure $notifier): void /********************* deprecated stuff from former ServiceDefinition ****************d*g**/ + /** @deprecated */ + public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void + { + $method->setBody($this->generateCode($generator)); + } + + /** @deprecated Use setType() */ public function setClass(?string $type) { diff --git a/src/DI/Definitions/FactoryDefinition.php b/src/DI/Definitions/FactoryDefinition.php index 7c2e69caa..41deea3b7 100644 --- a/src/DI/Definitions/FactoryDefinition.php +++ b/src/DI/Definitions/FactoryDefinition.php @@ -196,7 +196,7 @@ public function convertArguments(array &$args): void } - public function generateMethod(Php\Method $method, Nette\DI\PhpGenerator $generator): void + public function generateCode(Nette\DI\PhpGenerator $generator): string { $class = (new Php\ClassType) ->addImplement($this->getType()); @@ -218,7 +218,7 @@ public function generateMethod(Php\Method $method, Nette\DI\PhpGenerator $genera ->setReturnType((string) Type::fromReflection($rm)) ->setBody($body); - $method->setBody('return new class ($this) ' . $class . ';'); + return 'return new class ($this) ' . $class . ';'; } diff --git a/src/DI/Definitions/ImportedDefinition.php b/src/DI/Definitions/ImportedDefinition.php index 501e558cc..cfcab8eba 100644 --- a/src/DI/Definitions/ImportedDefinition.php +++ b/src/DI/Definitions/ImportedDefinition.php @@ -10,7 +10,6 @@ namespace Nette\DI\Definitions; use Nette; -use Nette\DI\PhpGenerator; /** @@ -34,9 +33,9 @@ public function complete(Nette\DI\Resolver $resolver): void } - public function generateMethod(Nette\PhpGenerator\Method $method, PhpGenerator $generator): void + public function generateCode(Nette\DI\PhpGenerator $generator): string { - $method->setBody( + return $generator->formatPhp( 'throw new Nette\\DI\\ServiceCreationException(?);', ["Unable to create imported service '{$this->getName()}', it must be added using addService()"], ); diff --git a/src/DI/Definitions/LocatorDefinition.php b/src/DI/Definitions/LocatorDefinition.php index a9e3f735a..5693302a4 100644 --- a/src/DI/Definitions/LocatorDefinition.php +++ b/src/DI/Definitions/LocatorDefinition.php @@ -127,7 +127,7 @@ public function complete(Nette\DI\Resolver $resolver): void } - public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void + public function generateCode(Nette\DI\PhpGenerator $generator): string { $class = (new Nette\PhpGenerator\ClassType) ->addImplement($this->getType()); @@ -171,6 +171,6 @@ public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGe } } - $method->setBody('return new class ($this) ' . $class . ';'); + return 'return new class ($this) ' . $class . ';'; } } diff --git a/src/DI/Definitions/ServiceDefinition.php b/src/DI/Definitions/ServiceDefinition.php index 6cec76a86..a2c129f86 100644 --- a/src/DI/Definitions/ServiceDefinition.php +++ b/src/DI/Definitions/ServiceDefinition.php @@ -179,12 +179,11 @@ private function prependSelf(Statement $setup): Statement } - public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void + public function generateCode(Nette\DI\PhpGenerator $generator): string { $code = $generator->formatStatement($this->creator) . ";\n"; if (!$this->setup) { - $method->setBody('return ' . $code); - return; + return 'return ' . $code; } $code = '$service = ' . $code; @@ -193,7 +192,7 @@ public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGe } $code .= 'return $service;'; - $method->setBody($code); + return $code; } From 3fc5456549be1c9905f83903b6791f07103601de Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 29 Nov 2024 22:23:10 +0100 Subject: [PATCH 03/22] Resolver: restrictions for named parameters have been removed --- src/DI/Resolver.php | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/DI/Resolver.php b/src/DI/Resolver.php index c9942d3ad..a1e594495 100644 --- a/src/DI/Resolver.php +++ b/src/DI/Resolver.php @@ -258,12 +258,7 @@ public function completeStatement(Statement $statement, bool $currentServiceAllo switch (true) { case $entity[0] === '': // function call - if (!Arrays::isList($arguments)) { - throw new ServiceCreationException(sprintf( - 'Unable to pass specified arguments to %s.', - $entity[0], - )); - } elseif (!function_exists($entity[1])) { + if (!function_exists($entity[1])) { throw new ServiceCreationException(sprintf("Function %s doesn't exist.", $entity[1])); } @@ -297,9 +292,6 @@ public function completeStatement(Statement $statement, bool $currentServiceAllo $arguments = self::autowireArguments($rm, $arguments, $getter); $this->addDependency($rm); - - } elseif (!Arrays::isList($arguments)) { - throw new ServiceCreationException(sprintf('Unable to pass specified arguments to %s::%s().', $type, $entity[1])); } } } From 4d4eed733398820078a17175fea3e0db677d7414 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 1 Dec 2024 23:49:52 +0100 Subject: [PATCH 04/22] Resolver: used withCurrentServiceAvailable() to control $currentServiceAllowed --- src/DI/Definitions/ServiceDefinition.php | 2 +- src/DI/Resolver.php | 39 +++++++++++++++--------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/DI/Definitions/ServiceDefinition.php b/src/DI/Definitions/ServiceDefinition.php index a2c129f86..c593fe7de 100644 --- a/src/DI/Definitions/ServiceDefinition.php +++ b/src/DI/Definitions/ServiceDefinition.php @@ -166,7 +166,7 @@ public function complete(Nette\DI\Resolver $resolver): void $this->creator = $resolver->completeStatement($this->creator); foreach ($this->setup as &$setup) { - $setup = $resolver->completeStatement($setup, true); + $setup = $resolver->withCurrentServiceAvailable()->completeStatement($setup); } } diff --git a/src/DI/Resolver.php b/src/DI/Resolver.php index a1e594495..f8afcea11 100644 --- a/src/DI/Resolver.php +++ b/src/DI/Resolver.php @@ -42,6 +42,25 @@ public function __construct(ContainerBuilder $builder) } + private function withCurrentService(Definition $definition): self + { + $dolly = clone $this; + $dolly->currentService = in_array($definition, $this->builder->getDefinitions(), strict: true) + ? $definition + : null; + $dolly->currentServiceType = $definition->getType(); + return $dolly; + } + + + public function withCurrentServiceAvailable(): self + { + $dolly = clone $this; + $dolly->currentServiceAllowed = true; + return $dolly; + } + + public function getContainerBuilder(): ContainerBuilder { return $this->builder; @@ -159,29 +178,19 @@ interface_exists($entity) public function completeDefinition(Definition $def): void { - $this->currentService = in_array($def, $this->builder->getDefinitions(), strict: true) - ? $def - : null; - $this->currentServiceType = $def->getType(); - $this->currentServiceAllowed = false; - try { - $def->complete($this); + $def->complete($this->withCurrentService($def)); $this->addDependency(new \ReflectionClass($def->getType())); } catch (\Throwable $e) { throw $this->completeException($e, $def); - - } finally { - $this->currentService = $this->currentServiceType = null; } } - public function completeStatement(Statement $statement, bool $currentServiceAllowed = false): Statement + public function completeStatement(Statement $statement): Statement { - $this->currentServiceAllowed = $currentServiceAllowed; $entity = $this->normalizeEntity($statement); $arguments = $this->convertReferences($statement->arguments); $getter = fn(string $type, bool $single) => $single @@ -194,7 +203,7 @@ public function completeStatement(Statement $statement, bool $currentServiceAllo throw new ServiceCreationException(sprintf('Cannot create closure for %s(...)', $entity)); } if ($entity[0] instanceof Statement) { - $entity[0] = $this->completeStatement($entity[0], $this->currentServiceAllowed); + $entity[0] = $this->completeStatement($entity[0]); } break; @@ -268,7 +277,7 @@ public function completeStatement(Statement $statement, bool $currentServiceAllo break; case $entity[0] instanceof Statement: - $entity[0] = $this->completeStatement($entity[0], $this->currentServiceAllowed); + $entity[0] = $this->completeStatement($entity[0]); // break omitted case is_string($entity[0]): // static method call @@ -329,7 +338,7 @@ public function completeArguments(array $arguments): array $val = $this->completeArguments($services); } else { - $val = $this->completeStatement($val, $this->currentServiceAllowed); + $val = $this->completeStatement($val); } } elseif ($val instanceof Definition || $val instanceof Reference) { $val = $this->normalizeEntity(new Statement($val)); From 30adf163cb2adfae4d4092238029cf14b6161cfe Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 2 Dec 2024 06:11:21 +0100 Subject: [PATCH 05/22] added Definitions\Expression --- src/DI/Definitions/Expression.php | 17 +++++++++++++++++ src/DI/Definitions/Reference.php | 3 ++- src/DI/Definitions/Statement.php | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 src/DI/Definitions/Expression.php diff --git a/src/DI/Definitions/Expression.php b/src/DI/Definitions/Expression.php new file mode 100644 index 000000000..f4f0ae4d2 --- /dev/null +++ b/src/DI/Definitions/Expression.php @@ -0,0 +1,17 @@ + Date: Mon, 2 Dec 2024 00:12:29 +0100 Subject: [PATCH 06/22] PhpGenerator::formatStatement() moved to Statement & Reference --- src/DI/Definitions/Expression.php | 1 + src/DI/Definitions/Reference.php | 12 ++++ src/DI/Definitions/ServiceDefinition.php | 4 +- src/DI/Definitions/Statement.php | 57 +++++++++++++++++++ src/DI/PhpGenerator.php | 72 ++---------------------- 5 files changed, 78 insertions(+), 68 deletions(-) diff --git a/src/DI/Definitions/Expression.php b/src/DI/Definitions/Expression.php index f4f0ae4d2..de3a3a246 100644 --- a/src/DI/Definitions/Expression.php +++ b/src/DI/Definitions/Expression.php @@ -14,4 +14,5 @@ abstract class Expression { + abstract public function generateCode(Nette\DI\PhpGenerator $generator): string; } diff --git a/src/DI/Definitions/Reference.php b/src/DI/Definitions/Reference.php index afc0ad7d3..742cc658b 100644 --- a/src/DI/Definitions/Reference.php +++ b/src/DI/Definitions/Reference.php @@ -9,6 +9,8 @@ namespace Nette\DI\Definitions; +use Nette\DI; + /** @@ -62,4 +64,14 @@ public function isSelf(): bool { return $this->value === self::Self; } + + + public function generateCode(DI\PhpGenerator $generator): string + { + return match (true) { + $this->isSelf() => '$service', + $this->value === DI\ContainerBuilder::ThisContainer => '$this', + default => $generator->formatPhp('$this->getService(?)', [$this->value]), + }; + } } diff --git a/src/DI/Definitions/ServiceDefinition.php b/src/DI/Definitions/ServiceDefinition.php index c593fe7de..611d68eef 100644 --- a/src/DI/Definitions/ServiceDefinition.php +++ b/src/DI/Definitions/ServiceDefinition.php @@ -181,14 +181,14 @@ private function prependSelf(Statement $setup): Statement public function generateCode(Nette\DI\PhpGenerator $generator): string { - $code = $generator->formatStatement($this->creator) . ";\n"; + $code = $this->creator->generateCode($generator) . ";\n"; if (!$this->setup) { return 'return ' . $code; } $code = '$service = ' . $code; foreach ($this->setup as $setup) { - $code .= $generator->formatStatement($setup) . ";\n"; + $code .= $setup->generateCode($generator) . ";\n"; } $code .= 'return $service;'; diff --git a/src/DI/Definitions/Statement.php b/src/DI/Definitions/Statement.php index ce830de73..312e15eb6 100644 --- a/src/DI/Definitions/Statement.php +++ b/src/DI/Definitions/Statement.php @@ -10,6 +10,8 @@ namespace Nette\DI\Definitions; use Nette; +use Nette\DI; +use Nette\PhpGenerator as Php; /** @@ -62,6 +64,61 @@ public function getEntity(): string|array|Definition|Reference|null { return $this->entity; } + + + /** + * Formats PHP code for class instantiating, function calling or property setting in PHP. + */ + public function generateCode(DI\PhpGenerator $generator): string + { + $entity = $this->entity; + $arguments = $this->arguments; + + switch (true) { + case is_string($entity) && str_contains($entity, '?'): // PHP literal + return $generator->formatPhp($entity, $arguments); + + case is_string($entity): // create class + return $arguments + ? $generator->formatPhp("new $entity(...?:)", [$arguments]) + : $generator->formatPhp("new $entity", []); + + case is_array($entity): + switch (true) { + case $entity[1][0] === '$': // property getter, setter or appender + $name = substr($entity[1], 1); + if ($append = (str_ends_with($name, '[]'))) { + $name = substr($name, 0, -2); + } + + $prop = $entity[0] instanceof Reference + ? $generator->formatPhp('?->?', [$entity[0], $name]) + : $generator->formatPhp('?::$?', [$entity[0], $name]); + return $arguments + ? $generator->formatPhp(($append ? '?[]' : '?') . ' = ?', [new Php\Literal($prop), $arguments[0]]) + : $prop; + + case $entity[0] instanceof self: + $inner = $generator->formatPhp('?', [$entity[0]]); + if (str_starts_with($inner, 'new ')) { + $inner = "($inner)"; + } + + return $generator->formatPhp('?->?(...?:)', [new Php\Literal($inner), $entity[1], $arguments]); + + case $entity[0] instanceof Reference: + return $generator->formatPhp('?->?(...?:)', [$entity[0], $entity[1], $arguments]); + + case $entity[0] === '': // function call + return $generator->formatPhp('?(...?:)', [new Php\Literal($entity[1]), $arguments]); + + case is_string($entity[0]): // static method call + return $generator->formatPhp('?::?(...?:)', [new Php\Literal($entity[0]), $entity[1], $arguments]); + } + } + + throw new Nette\InvalidStateException; + } } diff --git a/src/DI/PhpGenerator.php b/src/DI/PhpGenerator.php index b5176c40d..8f16e2dc3 100644 --- a/src/DI/PhpGenerator.php +++ b/src/DI/PhpGenerator.php @@ -9,9 +9,7 @@ namespace Nette\DI; -use Nette; -use Nette\DI\Definitions\Reference; -use Nette\DI\Definitions\Statement; +use Nette\DI\Definitions\Expression; use Nette\PhpGenerator as Php; @@ -103,58 +101,10 @@ public function generateMethod(Definitions\Definition $def): Php\Method } - /** - * Formats PHP code for class instantiating, function calling or property setting in PHP. - */ - public function formatStatement(Statement $statement): string + /** @deprecated */ + public function formatStatement(Definitions\Statement $statement): string { - $entity = $statement->getEntity(); - $arguments = $statement->arguments; - - switch (true) { - case is_string($entity) && str_contains($entity, '?'): // PHP literal - return $this->formatPhp($entity, $arguments); - - case is_string($entity): // create class - return $arguments - ? $this->formatPhp("new $entity(...?:)", [$arguments]) - : $this->formatPhp("new $entity", []); - - case is_array($entity): - switch (true) { - case $entity[1][0] === '$': // property getter, setter or appender - $name = substr($entity[1], 1); - if ($append = (str_ends_with($name, '[]'))) { - $name = substr($name, 0, -2); - } - - $prop = $entity[0] instanceof Reference - ? $this->formatPhp('?->?', [$entity[0], $name]) - : $this->formatPhp('?::$?', [$entity[0], $name]); - return $arguments - ? $this->formatPhp(($append ? '?[]' : '?') . ' = ?', [new Php\Literal($prop), $arguments[0]]) - : $prop; - - case $entity[0] instanceof Statement: - $inner = $this->formatPhp('?', [$entity[0]]); - if (str_starts_with($inner, 'new ')) { - $inner = "($inner)"; - } - - return $this->formatPhp('?->?(...?:)', [new Php\Literal($inner), $entity[1], $arguments]); - - case $entity[0] instanceof Reference: - return $this->formatPhp('?->?(...?:)', [$entity[0], $entity[1], $arguments]); - - case $entity[0] === '': // function call - return $this->formatPhp('?(...?:)', [new Php\Literal($entity[1]), $arguments]); - - case is_string($entity[0]): // static method call - return $this->formatPhp('?::?(...?:)', [new Php\Literal($entity[0]), $entity[1], $arguments]); - } - } - - throw new Nette\InvalidStateException; + return $statement->generateCode($this); } @@ -171,18 +121,8 @@ public function formatPhp(string $statement, array $args): string public function convertArguments(array $args): array { array_walk_recursive($args, function (&$val): void { - if ($val instanceof Statement) { - $val = new Php\Literal($this->formatStatement($val)); - - } elseif ($val instanceof Reference) { - $name = $val->getValue(); - if ($val->isSelf()) { - $val = new Php\Literal('$service'); - } elseif ($name === ContainerBuilder::ThisContainer) { - $val = new Php\Literal('$this'); - } else { - $val = ContainerBuilder::literal('$this->getService(?)', [$name]); - } + if ($val instanceof Expression) { + $val = new Php\Literal($val->generateCode($this)); } elseif ( is_object($val) && !$val instanceof Php\Literal && !$val instanceof \DateTimeInterface From 93de5d5f1a4cf1a1a132a4d062027f888f0d6b52 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 1 Dec 2024 19:46:30 +0100 Subject: [PATCH 07/22] Resolver::resolve*Type() moved to Statement & Reference --- src/DI/Definitions/Expression.php | 3 + src/DI/Definitions/FactoryDefinition.php | 2 +- src/DI/Definitions/Reference.php | 18 +++ src/DI/Definitions/ServiceDefinition.php | 2 +- src/DI/Definitions/Statement.php | 71 ++++++++++++ src/DI/Extensions/InjectExtension.php | 2 +- src/DI/Resolver.php | 134 ++++++----------------- 7 files changed, 130 insertions(+), 102 deletions(-) diff --git a/src/DI/Definitions/Expression.php b/src/DI/Definitions/Expression.php index de3a3a246..df8eb0ee5 100644 --- a/src/DI/Definitions/Expression.php +++ b/src/DI/Definitions/Expression.php @@ -14,5 +14,8 @@ abstract class Expression { + abstract public function resolveType(Nette\DI\Resolver $resolver): ?string; + + abstract public function generateCode(Nette\DI\PhpGenerator $generator): string; } diff --git a/src/DI/Definitions/FactoryDefinition.php b/src/DI/Definitions/FactoryDefinition.php index 41deea3b7..2bcee4519 100644 --- a/src/DI/Definitions/FactoryDefinition.php +++ b/src/DI/Definitions/FactoryDefinition.php @@ -143,7 +143,7 @@ private function completeParameters(Nette\DI\Resolver $resolver): void $ctorParams = []; if ( - ($class = $resolver->resolveEntityType($this->resultDefinition->getCreator())) + ($class = $this->resultDefinition->getCreator()->resolveType($resolver)) && ($ctor = (new \ReflectionClass($class))->getConstructor()) ) { foreach ($ctor->getParameters() as $param) { diff --git a/src/DI/Definitions/Reference.php b/src/DI/Definitions/Reference.php index 742cc658b..c28f7bb59 100644 --- a/src/DI/Definitions/Reference.php +++ b/src/DI/Definitions/Reference.php @@ -66,6 +66,24 @@ public function isSelf(): bool } + public function resolveType(DI\Resolver $resolver): ?string + { + if ($this->isSelf()) { + return $resolver->getCurrentService(type: true); + + } elseif ($this->isType()) { + return ltrim($this->value, '\\'); + } + + $def = $resolver->getContainerBuilder()->getDefinition($this->value); + if (!$def->getType()) { + $resolver->resolveDefinition($def); + } + + return $def->getType(); + } + + public function generateCode(DI\PhpGenerator $generator): string { return match (true) { diff --git a/src/DI/Definitions/ServiceDefinition.php b/src/DI/Definitions/ServiceDefinition.php index 611d68eef..ae926ff17 100644 --- a/src/DI/Definitions/ServiceDefinition.php +++ b/src/DI/Definitions/ServiceDefinition.php @@ -139,7 +139,7 @@ public function resolveType(Nette\DI\Resolver $resolver): void $this->setCreator($this->getType(), $this->creator->arguments ?? []); } elseif (!$this->getType()) { - $type = $resolver->resolveEntityType($this->creator); + $type = $this->creator->resolveType($resolver); if (!$type) { throw new ServiceCreationException('Unknown service type, specify it or declare return type of factory method.'); } diff --git a/src/DI/Definitions/Statement.php b/src/DI/Definitions/Statement.php index 312e15eb6..31212b7ba 100644 --- a/src/DI/Definitions/Statement.php +++ b/src/DI/Definitions/Statement.php @@ -11,7 +11,10 @@ use Nette; use Nette\DI; +use Nette\DI\Resolver; +use Nette\DI\ServiceCreationException; use Nette\PhpGenerator as Php; +use Nette\Utils\Callback; /** @@ -66,6 +69,74 @@ public function getEntity(): string|array|Definition|Reference|null } + public function resolveType(Resolver $resolver): ?string + { + $entity = $resolver->normalizeEntity($this); + + if ($this->arguments === Resolver::getFirstClassCallable()) { + return \Closure::class; + + } elseif (is_array($entity)) { + if ($entity[0] instanceof Expression) { + $entity[0] = $entity[0]->resolveType($resolver); + if (!$entity[0]) { + return null; + } + } + + try { + $reflection = Callback::toReflection($entity[0] === '' ? $entity[1] : $entity); + assert($reflection instanceof \ReflectionMethod || $reflection instanceof \ReflectionFunction); + $refClass = $reflection instanceof \ReflectionMethod + ? $reflection->getDeclaringClass() + : null; + } catch (\ReflectionException $e) { + $refClass = $reflection = null; + } + + if (isset($e) || ($refClass && (!$reflection->isPublic() + || ($refClass->isTrait() && !$reflection->isStatic()) + ))) { + throw new ServiceCreationException(sprintf('Method %s() is not callable.', Callback::toString($entity)), 0, $e ?? null); + } + + $resolver->addDependency($reflection); + + $type = Nette\Utils\Type::fromReflection($reflection) ?? ($annotation = DI\Helpers::getReturnTypeAnnotation($reflection)); + if ($type && !in_array($type->getSingleName(), ['object', 'mixed'], strict: true)) { + if (isset($annotation)) { + trigger_error('Annotation @return should be replaced with native return type at ' . Callback::toString($entity), E_USER_DEPRECATED); + } + + return DI\Helpers::ensureClassType( + $type, + sprintf('return type of %s()', Callback::toString($entity)), + allowNullable: true, + ); + } + + return null; + + } elseif ($entity instanceof Expression) { + return $entity->resolveType($resolver); + + } elseif (is_string($entity)) { // class + if (!class_exists($entity)) { + throw new ServiceCreationException(sprintf( + interface_exists($entity) + ? "Interface %s can not be used as 'create' or 'factory', did you mean 'implement'?" + : "Class '%s' not found.", + $entity, + )); + } + + return $entity; + } + + return null; + } + + /** * Formats PHP code for class instantiating, function calling or property setting in PHP. */ diff --git a/src/DI/Extensions/InjectExtension.php b/src/DI/Extensions/InjectExtension.php index 4c40b30c0..d0da1a927 100644 --- a/src/DI/Extensions/InjectExtension.php +++ b/src/DI/Extensions/InjectExtension.php @@ -49,7 +49,7 @@ public function beforeCompile(): void private function updateDefinition(Definitions\ServiceDefinition $def): void { - $resolvedType = (new DI\Resolver($this->getContainerBuilder()))->resolveEntityType($def->getCreator()); + $resolvedType = $def->getCreator()->resolveType(new DI\Resolver($this->getContainerBuilder())); $class = is_subclass_of($resolvedType, $def->getType()) ? $resolvedType : $def->getType(); diff --git a/src/DI/Resolver.php b/src/DI/Resolver.php index f8afcea11..8bc5488f9 100644 --- a/src/DI/Resolver.php +++ b/src/DI/Resolver.php @@ -11,6 +11,7 @@ use Nette; use Nette\DI\Definitions\Definition; +use Nette\DI\Definitions\Expression; use Nette\DI\Definitions\Reference; use Nette\DI\Definitions\Statement; use Nette\PhpGenerator\Helpers as PhpHelpers; @@ -61,6 +62,12 @@ public function withCurrentServiceAvailable(): self } + public function getCurrentService(bool $type = false): Definition|string|null + { + return $type ? $this->currentServiceType : $this->currentService; + } + + public function getContainerBuilder(): ContainerBuilder { return $this->builder; @@ -91,91 +98,6 @@ public function resolveDefinition(Definition $def): void } - public function resolveReferenceType(Reference $ref): ?string - { - if ($ref->isSelf()) { - return $this->currentServiceType; - } elseif ($ref->isType()) { - return ltrim($ref->getValue(), '\\'); - } - - $def = $this->resolveReference($ref); - if (!$def->getType()) { - $this->resolveDefinition($def); - } - - return $def->getType(); - } - - - public function resolveEntityType(Statement $statement): ?string - { - $entity = $this->normalizeEntity($statement); - - if ($statement->arguments === self::getFirstClassCallable()) { - return \Closure::class; - - } elseif (is_array($entity)) { - if ($entity[0] instanceof Reference || $entity[0] instanceof Statement) { - $entity[0] = $this->resolveEntityType($entity[0] instanceof Statement ? $entity[0] : new Statement($entity[0])); - if (!$entity[0]) { - return null; - } - } - - try { - $reflection = Callback::toReflection($entity[0] === '' ? $entity[1] : $entity); - assert($reflection instanceof \ReflectionMethod || $reflection instanceof \ReflectionFunction); - $refClass = $reflection instanceof \ReflectionMethod - ? $reflection->getDeclaringClass() - : null; - } catch (\ReflectionException $e) { - $refClass = $reflection = null; - } - - if (isset($e) || ($refClass && (!$reflection->isPublic() - || ($refClass->isTrait() && !$reflection->isStatic()) - ))) { - throw new ServiceCreationException(sprintf('Method %s() is not callable.', Callback::toString($entity)), 0, $e ?? null); - } - - $this->addDependency($reflection); - - $type = Nette\Utils\Type::fromReflection($reflection) ?? ($annotation = Helpers::getReturnTypeAnnotation($reflection)); - if ($type && !in_array($type->getSingleName(), ['object', 'mixed'], strict: true)) { - if (isset($annotation)) { - trigger_error('Annotation @return should be replaced with native return type at ' . Callback::toString($entity), E_USER_DEPRECATED); - } - - return Helpers::ensureClassType( - $type, - sprintf('return type of %s()', Callback::toString($entity)), - allowNullable: true, - ); - } - - return null; - - } elseif ($entity instanceof Reference) { // alias or factory - return $this->resolveReferenceType($entity); - - } elseif (is_string($entity)) { // class - if (!class_exists($entity)) { - throw new ServiceCreationException(sprintf( - interface_exists($entity) - ? "Interface %s can not be used as 'create' or 'factory', did you mean 'implement'?" - : "Class '%s' not found.", - $entity, - )); - } - - return $entity; - } - - return null; - } - - public function completeDefinition(Definition $def): void { try { @@ -288,9 +210,7 @@ public function completeStatement(Statement $statement): Statement throw new ServiceCreationException(sprintf('Missing argument for %s.', $entity[1])); } } elseif ( - $type = $entity[0] instanceof Reference - ? $this->resolveReferenceType($entity[0]) - : $this->resolveEntityType($entity[0] instanceof Statement ? $entity[0] : new Statement($entity[0])) + $type = ($entity[0] instanceof Expression ? $entity[0] : new Statement($entity[0]))->resolveType($this) ) { $rc = new \ReflectionClass($type); if ($rc->hasMethod($entity[1])) { @@ -349,7 +269,7 @@ public function completeArguments(array $arguments): array /** Returns literal, Class, Reference, [Class, member], [, globalFunc], [Reference, member], [Statement, member] */ - private function normalizeEntity(Statement $statement): string|array|Reference|null + public function normalizeEntity(Statement $statement): string|array|Reference|null { $entity = $statement->getEntity(); if (is_array($entity)) { @@ -400,14 +320,6 @@ public function normalizeReference(Reference $ref): Reference } - public function resolveReference(Reference $ref): Definition - { - return $ref->isSelf() - ? $this->currentService - : $this->builder->getDefinition($ref->getValue()); - } - - /** * Returns named reference to service resolved by type (or 'self' reference for local-autowiring). * @throws ServiceCreationException when multiple found @@ -445,7 +357,8 @@ public function addDependency(\ReflectionClass|\ReflectionFunctionAbstract|strin } - private function completeException(\Throwable $e, Definition $def): ServiceCreationException + /** @internal */ + public function completeException(\Throwable $e, Definition $def): ServiceCreationException { if ($e instanceof ServiceCreationException && str_starts_with($e->getMessage(), "Service '")) { return $e; @@ -508,7 +421,7 @@ private function convertReferences(array $arguments): array if (!isset($pair[1])) { // @service $val = new Reference($pair[0]); } elseif (preg_match('#^[A-Z][a-zA-Z0-9_]*$#D', $pair[1])) { // @service::CONSTANT - $val = ContainerBuilder::literal($this->resolveReferenceType(new Reference($pair[0])) . '::' . $pair[1]); + $val = ContainerBuilder::literal((new Reference($pair[0]))->resolveType($this) . '::' . $pair[1]); } else { // @service::property $val = new Statement([new Reference($pair[0]), '$' . $pair[1]]); } @@ -669,4 +582,27 @@ public static function getFirstClassCallable(): array static $x = [new Nette\PhpGenerator\Literal('...')]; return $x; } + + + /** @deprecated */ + public function resolveReferenceType(Reference $ref): ?string + { + return $ref->resolveType($this); + } + + + /** @deprecated */ + public function resolveEntityType(Statement $statement): ?string + { + return $statement->resolveType($this); + } + + + /** @deprecated */ + public function resolveReference(Reference $ref): Definition + { + return $ref->isSelf() + ? $this->currentService + : $this->builder->getDefinition($ref->getValue()); + } } From f55b977ba6acd3b9cc60bae4010e656f65077a8d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 1 Dec 2024 21:06:01 +0100 Subject: [PATCH 08/22] Resolver::completeStatement() moved to Statement & Reference --- src/DI/ContainerBuilder.php | 4 +- src/DI/Definitions/AccessorDefinition.php | 2 +- src/DI/Definitions/Expression.php | 3 + src/DI/Definitions/LocatorDefinition.php | 4 +- src/DI/Definitions/Reference.php | 26 +++ src/DI/Definitions/ServiceDefinition.php | 10 +- src/DI/Definitions/Statement.php | 199 +++++++++++++++- src/DI/Resolver.php | 264 +++------------------- 8 files changed, 270 insertions(+), 242 deletions(-) diff --git a/src/DI/ContainerBuilder.php b/src/DI/ContainerBuilder.php index f2844d508..089ef301a 100644 --- a/src/DI/ContainerBuilder.php +++ b/src/DI/ContainerBuilder.php @@ -394,8 +394,8 @@ public static function literal(string $code, ?array $args = null): Nette\PhpGene public function formatPhp(string $statement, array $args): string { array_walk_recursive($args, function (&$val): void { - if ($val instanceof Nette\DI\Definitions\Statement) { - $val = (new Resolver($this))->completeStatement($val); + if ($val instanceof Nette\DI\Definitions\Expression) { + $val->complete(new Resolver($this)); } elseif ($val instanceof Definition) { $val = new Definitions\Reference($val->getName()); diff --git a/src/DI/Definitions/AccessorDefinition.php b/src/DI/Definitions/AccessorDefinition.php index 3c4b1d3fd..c416ca9a7 100644 --- a/src/DI/Definitions/AccessorDefinition.php +++ b/src/DI/Definitions/AccessorDefinition.php @@ -103,7 +103,7 @@ public function complete(Nette\DI\Resolver $resolver): void $this->setReference(Type::fromReflection($method)->getSingleName()); } - $this->reference = $resolver->normalizeReference($this->reference); + $this->reference->complete($resolver); } diff --git a/src/DI/Definitions/Expression.php b/src/DI/Definitions/Expression.php index df8eb0ee5..da7b4a24f 100644 --- a/src/DI/Definitions/Expression.php +++ b/src/DI/Definitions/Expression.php @@ -17,5 +17,8 @@ abstract class Expression abstract public function resolveType(Nette\DI\Resolver $resolver): ?string; + abstract public function complete(Nette\DI\Resolver $resolver): void; + + abstract public function generateCode(Nette\DI\PhpGenerator $generator): string; } diff --git a/src/DI/Definitions/LocatorDefinition.php b/src/DI/Definitions/LocatorDefinition.php index 5693302a4..3cdfb1615 100644 --- a/src/DI/Definitions/LocatorDefinition.php +++ b/src/DI/Definitions/LocatorDefinition.php @@ -121,8 +121,8 @@ public function complete(Nette\DI\Resolver $resolver): void } } - foreach ($this->references as $name => $ref) { - $this->references[$name] = $resolver->normalizeReference($ref); + foreach ($this->references as $ref) { + $ref->complete($resolver); } } diff --git a/src/DI/Definitions/Reference.php b/src/DI/Definitions/Reference.php index c28f7bb59..ed6db4305 100644 --- a/src/DI/Definitions/Reference.php +++ b/src/DI/Definitions/Reference.php @@ -84,6 +84,32 @@ public function resolveType(DI\Resolver $resolver): ?string } + /** + * Normalizes reference to 'self' or named reference (or leaves it typed if it is not possible during resolving) and checks existence of service. + */ + public function complete(DI\Resolver $resolver): void + { + if ($this->isSelf()) { + return; + + } elseif ($this->isType()) { + try { + $this->value = $resolver->getByType($this->value)->value; + } catch (DI\NotAllowedDuringResolvingException) { + } + return; + } + + if (!$resolver->getContainerBuilder()->hasDefinition($this->value)) { + throw new DI\ServiceCreationException(sprintf("Reference to missing service '%s'.", $this->value)); + } + + if ($this->value === $resolver->getCurrentService()?->getName()) { + $this->value = self::Self; + } + } + + public function generateCode(DI\PhpGenerator $generator): string { return match (true) { diff --git a/src/DI/Definitions/ServiceDefinition.php b/src/DI/Definitions/ServiceDefinition.php index ae926ff17..0546b54b3 100644 --- a/src/DI/Definitions/ServiceDefinition.php +++ b/src/DI/Definitions/ServiceDefinition.php @@ -159,14 +159,14 @@ public function complete(Nette\DI\Resolver $resolver): void { $entity = $this->creator->getEntity(); if ($entity instanceof Reference && !$this->creator->arguments && !$this->setup) { - $ref = $resolver->normalizeReference($entity); - $this->setCreator([new Reference(Nette\DI\ContainerBuilder::ThisContainer), 'getService'], [$ref->getValue()]); + $entity->complete($resolver); + $this->setCreator([new Reference(Nette\DI\ContainerBuilder::ThisContainer), 'getService'], [$entity->getValue()]); } - $this->creator = $resolver->completeStatement($this->creator); + $this->creator->complete($resolver); - foreach ($this->setup as &$setup) { - $setup = $resolver->withCurrentServiceAvailable()->completeStatement($setup); + foreach ($this->setup as $setup) { + $setup->complete($resolver->withCurrentServiceAvailable()); } } diff --git a/src/DI/Definitions/Statement.php b/src/DI/Definitions/Statement.php index 31212b7ba..ba65b1ee2 100644 --- a/src/DI/Definitions/Statement.php +++ b/src/DI/Definitions/Statement.php @@ -15,6 +15,7 @@ use Nette\DI\ServiceCreationException; use Nette\PhpGenerator as Php; use Nette\Utils\Callback; +use Nette\Utils\Validators; /** @@ -71,7 +72,7 @@ public function getEntity(): string|array|Definition|Reference|null public function resolveType(Resolver $resolver): ?string { - $entity = $resolver->normalizeEntity($this); + $entity = $this->normalizeEntity($resolver); if ($this->arguments === Resolver::getFirstClassCallable()) { return \Closure::class; @@ -137,6 +138,202 @@ interface_exists($entity) } + public function complete(Resolver $resolver): void + { + $entity = $this->normalizeEntity($resolver); + $this->convertReferences($resolver); + $arguments = $this->arguments; + + switch (true) { + case $this->arguments === Resolver::getFirstClassCallable(): + if (!is_array($entity) || !Php\Helpers::isIdentifier($entity[1])) { + throw new ServiceCreationException(sprintf('Cannot create closure for %s(...)', $entity)); + } + if ($entity[0] instanceof self) { + $entity[0]->complete($resolver); + } + break; + + case is_string($entity) && str_contains($entity, '?'): // PHP literal + break; + + case $entity === 'not': + if (count($arguments) !== 1) { + throw new ServiceCreationException(sprintf('Function %s() expects 1 parameter, %s given.', $entity, count($arguments))); + } + + $this->entity = ['', '!']; + break; + + case $entity === 'bool': + case $entity === 'int': + case $entity === 'float': + case $entity === 'string': + if (count($arguments) !== 1) { + throw new ServiceCreationException(sprintf('Function %s() expects 1 parameter, %s given.', $entity, count($arguments))); + } + + $arguments = [$arguments[0], $entity]; + $this->entity = [DI\Helpers::class, 'convertType']; + break; + + case is_string($entity): // create class + if (!class_exists($entity)) { + throw new ServiceCreationException(sprintf("Class '%s' not found.", $entity)); + } elseif ((new \ReflectionClass($entity))->isAbstract()) { + throw new ServiceCreationException(sprintf('Class %s is abstract.', $entity)); + } elseif (($rm = (new \ReflectionClass($entity))->getConstructor()) !== null && !$rm->isPublic()) { + throw new ServiceCreationException(sprintf('Class %s has %s constructor.', $entity, $rm->isProtected() ? 'protected' : 'private')); + } elseif ($constructor = (new \ReflectionClass($entity))->getConstructor()) { + $arguments = $resolver->autowireServices($constructor, $arguments); + $resolver->addDependency($constructor); + } elseif ($arguments) { + throw new ServiceCreationException(sprintf( + 'Unable to pass arguments, class %s has no constructor.', + $entity, + )); + } + + break; + + case $entity instanceof Reference: + if ($arguments) { + $e = $resolver->completeException(new ServiceCreationException(sprintf('Parameters were passed to reference @%s, although references cannot have any parameters.', $entity->getValue())), $resolver->getCurrentService()); + trigger_error($e->getMessage(), E_USER_DEPRECATED); + } + $this->entity = [new Reference(DI\ContainerBuilder::ThisContainer), DI\Container::getMethodName($entity->getValue())]; + break; + + case is_array($entity): + if (!preg_match('#^\$?(\\\\?' . Php\Helpers::ReIdentifier . ')+(\[\])?$#D', $entity[1])) { + throw new ServiceCreationException(sprintf( + "Expected function, method or property name, '%s' given.", + $entity[1], + )); + } + + switch (true) { + case $entity[0] === '': // function call + if (!function_exists($entity[1])) { + throw new ServiceCreationException(sprintf("Function %s doesn't exist.", $entity[1])); + } + + $rf = new \ReflectionFunction($entity[1]); + $arguments = $resolver->autowireServices($rf, $arguments); + $resolver->addDependency($rf); + break; + + case $entity[0] instanceof self: + $entity[0]->complete($resolver); + // break omitted + + case is_string($entity[0]): // static method call + case $entity[0] instanceof Reference: + if ($entity[1][0] === '$') { // property getter, setter or appender + Validators::assert($arguments, 'list:0..1', "setup arguments for '" . Callback::toString($entity) . "'"); + if (!$arguments && str_ends_with($entity[1], '[]')) { + throw new ServiceCreationException(sprintf('Missing argument for %s.', $entity[1])); + } + } elseif ( + $type = ($entity[0] instanceof Expression ? $entity[0] : new self($entity[0]))->resolveType($resolver) + ) { + $rc = new \ReflectionClass($type); + if ($rc->hasMethod($entity[1])) { + $rm = $rc->getMethod($entity[1]); + if (!$rm->isPublic()) { + throw new ServiceCreationException(sprintf('%s::%s() is not callable.', $type, $entity[1])); + } + + $arguments = $resolver->autowireServices($rm, $arguments); + $resolver->addDependency($rm); + } + } + } + } + + try { + $this->arguments = $this->completeArguments($resolver, $arguments); + } catch (ServiceCreationException $e) { + if (!str_contains($e->getMessage(), ' (used in')) { + $e->setMessage($e->getMessage() . " (used in {$resolver->entityToString($entity)})"); + } + + throw $e; + } + } + + + public function completeArguments(Resolver $resolver, array $arguments): array + { + array_walk_recursive($arguments, function (&$val) use ($resolver): void { + if ($val instanceof self) { + if ($val->entity === 'typed' || $val->entity === 'tagged') { + $services = []; + $current = $resolver->getCurrentService()?->getName(); + foreach ($val->arguments as $argument) { + foreach ($val->entity === 'tagged' ? $resolver->getContainerBuilder()->findByTag($argument) : $resolver->getContainerBuilder()->findAutowired($argument) as $name => $foo) { + if ($name !== $current) { + $services[] = new Reference($name); + } + } + } + + $val = $this->completeArguments($resolver, $services); + } else { + $val->complete($resolver); + } + } elseif ($val instanceof Definition || $val instanceof Reference) { + $val = (new self($val))->normalizeEntity($resolver); + } + }); + return $arguments; + } + + + /** Returns literal, Class, Reference, [Class, member], [, globalFunc], [Reference, member], [Statement, member] */ + private function normalizeEntity(Resolver $resolver): string|array|Reference|null + { + if (is_array($this->entity)) { + $item = &$this->entity[0]; + } else { + $item = &$this->entity; + } + + if ($item instanceof Definition) { + if ($resolver->getContainerBuilder()->getDefinition($item->getName()) !== $item) { + throw new ServiceCreationException(sprintf("Service '%s' does not match the expected service.", $item->getName())); + + } + $item = new Reference($item->getName()); + } + + if ($item instanceof Reference) { + $item->complete($resolver); + } + + return $this->entity; + } + + + private function convertReferences(Resolver $resolver): void + { + array_walk_recursive($this->arguments, function (&$val) use ($resolver): void { + if (is_string($val) && strlen($val) > 1 && $val[0] === '@' && $val[1] !== '@') { + $pair = explode('::', substr($val, 1), 2); + if (!isset($pair[1])) { // @service + $val = new Reference($pair[0]); + } elseif (preg_match('#^[A-Z][a-zA-Z0-9_]*$#D', $pair[1])) { // @service::CONSTANT + $val = DI\ContainerBuilder::literal((new Reference($pair[0]))->resolveType($resolver) . '::' . $pair[1]); + } else { // @service::property + $val = new self([new Reference($pair[0]), '$' . $pair[1]]); + } + } elseif (is_string($val) && str_starts_with($val, '@@')) { // escaped text @@ + $val = substr($val, 1); + } + }); + } + + /** * Formats PHP code for class instantiating, function calling or property setting in PHP. */ diff --git a/src/DI/Resolver.php b/src/DI/Resolver.php index 8bc5488f9..18a6e6fa0 100644 --- a/src/DI/Resolver.php +++ b/src/DI/Resolver.php @@ -11,14 +11,10 @@ use Nette; use Nette\DI\Definitions\Definition; -use Nette\DI\Definitions\Expression; use Nette\DI\Definitions\Reference; use Nette\DI\Definitions\Statement; -use Nette\PhpGenerator\Helpers as PhpHelpers; use Nette\Utils\Arrays; -use Nette\Utils\Callback; use Nette\Utils\Reflection; -use Nette\Utils\Validators; /** @@ -43,7 +39,7 @@ public function __construct(ContainerBuilder $builder) } - private function withCurrentService(Definition $definition): self + public function withCurrentService(Definition $definition): self { $dolly = clone $this; $dolly->currentService = in_array($definition, $this->builder->getDefinitions(), strict: true) @@ -111,215 +107,6 @@ public function completeDefinition(Definition $def): void } - public function completeStatement(Statement $statement): Statement - { - $entity = $this->normalizeEntity($statement); - $arguments = $this->convertReferences($statement->arguments); - $getter = fn(string $type, bool $single) => $single - ? $this->getByType($type) - : array_values(array_filter($this->builder->findAutowired($type), fn($obj) => $obj !== $this->currentService)); - - switch (true) { - case $statement->arguments === self::getFirstClassCallable(): - if (!is_array($entity) || !PhpHelpers::isIdentifier($entity[1])) { - throw new ServiceCreationException(sprintf('Cannot create closure for %s(...)', $entity)); - } - if ($entity[0] instanceof Statement) { - $entity[0] = $this->completeStatement($entity[0]); - } - break; - - case is_string($entity) && str_contains($entity, '?'): // PHP literal - break; - - case $entity === 'not': - if (count($arguments) !== 1) { - throw new ServiceCreationException(sprintf('Function %s() expects 1 parameter, %s given.', $entity, count($arguments))); - } - - $entity = ['', '!']; - break; - - case $entity === 'bool': - case $entity === 'int': - case $entity === 'float': - case $entity === 'string': - if (count($arguments) !== 1) { - throw new ServiceCreationException(sprintf('Function %s() expects 1 parameter, %s given.', $entity, count($arguments))); - } - - $arguments = [$arguments[0], $entity]; - $entity = [Helpers::class, 'convertType']; - break; - - case is_string($entity): // create class - if (!class_exists($entity)) { - throw new ServiceCreationException(sprintf("Class '%s' not found.", $entity)); - } elseif ((new \ReflectionClass($entity))->isAbstract()) { - throw new ServiceCreationException(sprintf('Class %s is abstract.', $entity)); - } elseif (($rm = (new \ReflectionClass($entity))->getConstructor()) !== null && !$rm->isPublic()) { - throw new ServiceCreationException(sprintf('Class %s has %s constructor.', $entity, $rm->isProtected() ? 'protected' : 'private')); - } elseif ($constructor = (new \ReflectionClass($entity))->getConstructor()) { - $arguments = self::autowireArguments($constructor, $arguments, $getter); - $this->addDependency($constructor); - } elseif ($arguments) { - throw new ServiceCreationException(sprintf( - 'Unable to pass arguments, class %s has no constructor.', - $entity, - )); - } - - break; - - case $entity instanceof Reference: - if ($arguments) { - $e = $this->completeException(new ServiceCreationException(sprintf('Parameters were passed to reference @%s, although references cannot have any parameters.', $entity->getValue())), $this->currentService); - trigger_error($e->getMessage(), E_USER_DEPRECATED); - } - $entity = [new Reference(ContainerBuilder::ThisContainer), Container::getMethodName($entity->getValue())]; - break; - - case is_array($entity): - if (!preg_match('#^\$?(\\\\?' . PhpHelpers::ReIdentifier . ')+(\[\])?$#D', $entity[1])) { - throw new ServiceCreationException(sprintf( - "Expected function, method or property name, '%s' given.", - $entity[1], - )); - } - - switch (true) { - case $entity[0] === '': // function call - if (!function_exists($entity[1])) { - throw new ServiceCreationException(sprintf("Function %s doesn't exist.", $entity[1])); - } - - $rf = new \ReflectionFunction($entity[1]); - $arguments = self::autowireArguments($rf, $arguments, $getter); - $this->addDependency($rf); - break; - - case $entity[0] instanceof Statement: - $entity[0] = $this->completeStatement($entity[0]); - // break omitted - - case is_string($entity[0]): // static method call - case $entity[0] instanceof Reference: - if ($entity[1][0] === '$') { // property getter, setter or appender - Validators::assert($arguments, 'list:0..1', "setup arguments for '" . Callback::toString($entity) . "'"); - if (!$arguments && str_ends_with($entity[1], '[]')) { - throw new ServiceCreationException(sprintf('Missing argument for %s.', $entity[1])); - } - } elseif ( - $type = ($entity[0] instanceof Expression ? $entity[0] : new Statement($entity[0]))->resolveType($this) - ) { - $rc = new \ReflectionClass($type); - if ($rc->hasMethod($entity[1])) { - $rm = $rc->getMethod($entity[1]); - if (!$rm->isPublic()) { - throw new ServiceCreationException(sprintf('%s::%s() is not callable.', $type, $entity[1])); - } - - $arguments = self::autowireArguments($rm, $arguments, $getter); - $this->addDependency($rm); - } - } - } - } - - try { - $arguments = $this->completeArguments($arguments); - } catch (ServiceCreationException $e) { - if (!str_contains($e->getMessage(), ' (used in')) { - $e->setMessage($e->getMessage() . " (used in {$this->entityToString($entity)})"); - } - - throw $e; - } - - return new Statement($entity, $arguments); - } - - - public function completeArguments(array $arguments): array - { - array_walk_recursive($arguments, function (&$val): void { - if ($val instanceof Statement) { - $entity = $val->getEntity(); - if ($entity === 'typed' || $entity === 'tagged') { - $services = []; - $current = $this->currentService?->getName(); - foreach ($val->arguments as $argument) { - foreach ($entity === 'tagged' ? $this->builder->findByTag($argument) : $this->builder->findAutowired($argument) as $name => $foo) { - if ($name !== $current) { - $services[] = new Reference($name); - } - } - } - - $val = $this->completeArguments($services); - } else { - $val = $this->completeStatement($val); - } - } elseif ($val instanceof Definition || $val instanceof Reference) { - $val = $this->normalizeEntity(new Statement($val)); - } - }); - return $arguments; - } - - - /** Returns literal, Class, Reference, [Class, member], [, globalFunc], [Reference, member], [Statement, member] */ - public function normalizeEntity(Statement $statement): string|array|Reference|null - { - $entity = $statement->getEntity(); - if (is_array($entity)) { - $item = &$entity[0]; - } else { - $item = &$entity; - } - - if ($item instanceof Definition) { - if ($this->builder->getDefinition($item->getName()) !== $item) { - throw new ServiceCreationException(sprintf("Service '%s' does not match the expected service.", $item->getName())); - - } - $item = new Reference($item->getName()); - } - - if ($item instanceof Reference) { - $item = $this->normalizeReference($item); - } - - return $entity; - } - - - /** - * Normalizes reference to 'self' or named reference (or leaves it typed if it is not possible during resolving) and checks existence of service. - */ - public function normalizeReference(Reference $ref): Reference - { - $service = $ref->getValue(); - if ($ref->isSelf()) { - return $ref; - } elseif ($ref->isName()) { - if (!$this->builder->hasDefinition($service)) { - throw new ServiceCreationException(sprintf("Reference to missing service '%s'.", $service)); - } - - return $this->currentService && $service === $this->currentService->getName() - ? new Reference(Reference::Self) - : $ref; - } - - try { - return $this->getByType($service); - } catch (NotAllowedDuringResolvingException) { - return new Reference($service); - } - } - - /** * Returns named reference to service resolved by type (or 'self' reference for local-autowiring). * @throws ServiceCreationException when multiple found @@ -386,7 +173,8 @@ public function completeException(\Throwable $e, Definition $def): ServiceCreati } - private function entityToString($entity): string + /** @internal */ + public function entityToString($entity): string { $referenceToText = fn(Reference $ref): string => $ref->isSelf() && $this->currentService ? '@' . $this->currentService->getName() @@ -413,23 +201,12 @@ private function entityToString($entity): string } - private function convertReferences(array $arguments): array + public function autowireServices(\ReflectionFunctionAbstract $method, array $arguments): array { - array_walk_recursive($arguments, function (&$val): void { - if (is_string($val) && strlen($val) > 1 && $val[0] === '@' && $val[1] !== '@') { - $pair = explode('::', substr($val, 1), 2); - if (!isset($pair[1])) { // @service - $val = new Reference($pair[0]); - } elseif (preg_match('#^[A-Z][a-zA-Z0-9_]*$#D', $pair[1])) { // @service::CONSTANT - $val = ContainerBuilder::literal((new Reference($pair[0]))->resolveType($this) . '::' . $pair[1]); - } else { // @service::property - $val = new Statement([new Reference($pair[0]), '$' . $pair[1]]); - } - } elseif (is_string($val) && str_starts_with($val, '@@')) { // escaped text @@ - $val = substr($val, 1); - } - }); - return $arguments; + $getter = fn(string $type, bool $single) => $single + ? $this->getByType($type) + : array_values(array_filter($this->builder->findAutowired($type), fn($obj) => $obj !== $this->currentService)); + return self::autowireArguments($method, $arguments, $getter); } @@ -605,4 +382,29 @@ public function resolveReference(Reference $ref): Definition ? $this->currentService : $this->builder->getDefinition($ref->getValue()); } + + + /** @deprecated */ + public function normalizeReference(Reference $ref): Reference + { + $ref->complete($this); + return $ref; + } + + + /** @deprecated */ + public function completeStatement(Statement $statement, bool $currentServiceAllowed = false): Statement + { + $resolver = $this->withCurrentService($this->currentService); + $resolver->currentServiceAllowed = $currentServiceAllowed; + $statement->complete($resolver); + return $statement; + } + + + /** @deprecated */ + public function completeArguments(array $arguments): array + { + return (new Statement(null, $arguments))->completeArguments($this, $arguments); + } } From 4862aaa6593cd75c845000f95bcbd4bfcd7f1d27 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 2 Dec 2024 05:27:24 +0100 Subject: [PATCH 09/22] NeonAdapter: processing of 'prevent merging' and 'entity to statement' moved to visitors --- composer.json | 2 +- src/DI/Config/Adapters/NeonAdapter.php | 134 ++++++++++++++++--------- 2 files changed, 90 insertions(+), 46 deletions(-) diff --git a/composer.json b/composer.json index f8342787f..f40ee38e6 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "php": "8.1 - 8.4", "ext-tokenizer": "*", "ext-ctype": "*", - "nette/neon": "^3.3 || ^4.0", + "nette/neon": "^3.3.3 || ^4.0", "nette/php-generator": "^4.1.6", "nette/robot-loader": "^4.0", "nette/schema": "^1.2.5", diff --git a/src/DI/Config/Adapters/NeonAdapter.php b/src/DI/Config/Adapters/NeonAdapter.php index 7de1eee1e..c41491429 100644 --- a/src/DI/Config/Adapters/NeonAdapter.php +++ b/src/DI/Config/Adapters/NeonAdapter.php @@ -24,6 +24,7 @@ final class NeonAdapter implements Nette\DI\Config\Adapter { private const PreventMergingSuffix = '!'; private string $file; + private \WeakMap $parents; /** @@ -45,56 +46,17 @@ public function load(string $file): array $node = $traverser->traverse($node, $this->convertAtSignVisitor(...)); $node = $traverser->traverse($node, $this->deprecatedParametersVisitor(...)); $node = $traverser->traverse($node, $this->resolveConstantsVisitor(...)); - return $this->process((array) $node->toValue()); + $node = $traverser->traverse($node, $this->preventMergingVisitor(...)); + $this->connectParentsVisitor($traverser, $node); + $node = $traverser->traverse($node, leave: $this->entityToExpressionVisitor(...)); + return (array) $node->toValue(); } - /** @throws Nette\InvalidStateException */ + /** @deprecated */ public function process(array $arr): array { - $res = []; - foreach ($arr as $key => $val) { - if (is_string($key) && str_ends_with($key, self::PreventMergingSuffix)) { - if (!is_array($val) && $val !== null) { - throw new Nette\DI\InvalidConfigurationException(sprintf( - "Replacing operator is available only for arrays, item '%s' is not array (used in '%s')", - $key, - $this->file, - )); - } - - $key = substr($key, 0, -1); - $val[DI\Config\Helpers::PREVENT_MERGING] = true; - } - - if (is_array($val)) { - $val = $this->process($val); - - } elseif ($val instanceof Neon\Entity) { - if ($val->value === Neon\Neon::CHAIN) { - $tmp = null; - foreach ($this->process($val->attributes) as $st) { - $tmp = new Statement( - $tmp === null ? $st->getEntity() : [$tmp, ltrim(implode('::', (array) $st->getEntity()), ':')], - $st->arguments, - ); - } - - $val = $tmp; - } else { - $tmp = $this->process([$val->value]); - if (is_string($tmp[0]) && str_contains($tmp[0], '?')) { - throw new Nette\DI\InvalidConfigurationException("Operator ? is deprecated in config file (used in '$this->file')"); - } - - $val = new Statement($tmp[0], $this->process($val->attributes)); - } - } - - $res[$key] = $val; - } - - return $res; + return $arr; } @@ -164,6 +126,71 @@ private function firstClassCallableVisitor(Node $node): void } + private function preventMergingVisitor(Node $node): void + { + if (!$node instanceof Node\ArrayNode) { + return; + } + + foreach ($node->items as $item) { + if ( + $item->key instanceof Node\LiteralNode + && is_string($item->key->value) + && str_ends_with($item->key->value, self::PreventMergingSuffix) + ) { + if ($item->value instanceof Node\LiteralNode && $item->value->value === null) { + $item->value = new Node\InlineArrayNode('['); + } elseif (!$item->value instanceof Node\ArrayNode) { + throw new Nette\DI\InvalidConfigurationException(sprintf( + "Replacing operator is available only for arrays, item '%s' is not array (used in '%s')", + $item->key->value, + $this->file, + )); + } + + $item->key->value = substr($item->key->value, 0, -1); + $item->value->items[] = $newItem = new Node\ArrayItemNode; + $newItem->key = new Node\LiteralNode(DI\Config\Helpers::PREVENT_MERGING); + $newItem->value = new Node\LiteralNode(true); + } + } + } + + + private function entityToExpressionVisitor(Node $node): Node + { + if ($node instanceof Node\EntityChainNode) { + return new Node\LiteralNode($this->buildExpression($node->chain)); + + } elseif ( + $node instanceof Node\EntityNode + && !$this->parents[$node] instanceof Node\EntityChainNode + ) { + return new Node\LiteralNode($this->buildExpression([$node])); + + } else { + return $node; + } + } + + + private function buildExpression(array $chain): Statement + { + $node = array_pop($chain); + $entity = $node->toValue(); + if (is_string($entity->value) && str_contains($entity->value, '?')) { + throw new Nette\DI\InvalidConfigurationException("Operator ? is deprecated in config file (used in '$this->file')"); + } elseif ($chain) { + $entity->value = [$this->buildExpression($chain), ltrim($entity->value, ':')]; + } + + return new Statement( + $entity->value, + $entity->attributes, + ); + } + + private function removeUnderscoreVisitor(Node $node): void { if (!$node instanceof Node\EntityNode) { @@ -240,4 +267,21 @@ private function resolveConstantsVisitor(Node $node): void } } } + + + private function connectParentsVisitor(Neon\Traverser $traverser, Node $node): void + { + $this->parents = new \WeakMap; + $stack = []; + $traverser->traverse( + $node, + enter: function (Node $node) use (&$stack) { + $this->parents[$node] = end($stack); + $stack[] = $node; + }, + leave: function () use (&$stack) { + array_pop($stack); + }, + ); + } } From 981f0a5e95790cb9dbdfda31969108ff842ab74c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 2 Dec 2024 02:17:53 +0100 Subject: [PATCH 10/22] added FunctionCallable & MethodCallable, expressions representing first-class callables --- src/DI/Config/Adapters/NeonAdapter.php | 52 ++++++++++++-------- src/DI/Definitions/FunctionCallable.php | 44 +++++++++++++++++ src/DI/Definitions/MethodCallable.php | 53 +++++++++++++++++++++ src/DI/Definitions/Statement.php | 14 +----- src/DI/Resolver.php | 8 ---- tests/DI/Compiler.first-class-callable.phpt | 8 ++-- 6 files changed, 134 insertions(+), 45 deletions(-) create mode 100644 src/DI/Definitions/FunctionCallable.php create mode 100644 src/DI/Definitions/MethodCallable.php diff --git a/src/DI/Config/Adapters/NeonAdapter.php b/src/DI/Config/Adapters/NeonAdapter.php index c41491429..fa08c27a7 100644 --- a/src/DI/Config/Adapters/NeonAdapter.php +++ b/src/DI/Config/Adapters/NeonAdapter.php @@ -11,6 +11,7 @@ use Nette; use Nette\DI; +use Nette\DI\Definitions; use Nette\DI\Definitions\Reference; use Nette\DI\Definitions\Statement; use Nette\Neon; @@ -41,7 +42,6 @@ public function load(string $file): array $decoder = new Neon\Decoder; $node = $decoder->parseToNode($input); $traverser = new Neon\Traverser; - $node = $traverser->traverse($node, $this->firstClassCallableVisitor(...)); $node = $traverser->traverse($node, $this->removeUnderscoreVisitor(...)); $node = $traverser->traverse($node, $this->convertAtSignVisitor(...)); $node = $traverser->traverse($node, $this->deprecatedParametersVisitor(...)); @@ -113,19 +113,6 @@ function (&$val): void { } - private function firstClassCallableVisitor(Node $node): void - { - if ($node instanceof Node\EntityNode - && count($node->attributes) === 1 - && $node->attributes[0]->key === null - && $node->attributes[0]->value instanceof Node\LiteralNode - && $node->attributes[0]->value->value === '...' - ) { - $node->attributes[0]->value->value = Nette\DI\Resolver::getFirstClassCallable()[0]; - } - } - - private function preventMergingVisitor(Node $node): void { if (!$node instanceof Node\ArrayNode) { @@ -174,20 +161,41 @@ private function entityToExpressionVisitor(Node $node): Node } - private function buildExpression(array $chain): Statement + private function buildExpression(array $chain): Definitions\Expression { $node = array_pop($chain); $entity = $node->toValue(); if (is_string($entity->value) && str_contains($entity->value, '?')) { throw new Nette\DI\InvalidConfigurationException("Operator ? is deprecated in config file (used in '$this->file')"); - } elseif ($chain) { - $entity->value = [$this->buildExpression($chain), ltrim($entity->value, ':')]; } - return new Statement( - $entity->value, + $stmt = new Statement( + $chain ? [$this->buildExpression($chain), ltrim($entity->value, ':')] : $entity->value, $entity->attributes, ); + + if ($this->isFirstClassCallable($node)) { + $entity = $stmt->getEntity(); + if (is_array($entity)) { + if ($entity[0] === '') { + return new Definitions\FunctionCallable($entity[1]); + } + return new Definitions\MethodCallable(...$entity); + } else { + throw new Nette\DI\InvalidConfigurationException("Cannot create closure for '$entity' in config file (used in '$this->file')"); + } + } + + return $stmt; + } + + + private function isFirstClassCallable(Node\EntityNode $node): bool + { + return array_keys($node->attributes) === [0] + && $node->attributes[0]->key === null + && $node->attributes[0]->value instanceof Node\LiteralNode + && $node->attributes[0]->value->value === '...'; } @@ -209,7 +217,11 @@ private function removeUnderscoreVisitor(Node $node): void unset($node->attributes[$i]); $index = true; - } elseif ($attr->value instanceof Node\LiteralNode && $attr->value->value === '...') { + } elseif ( + $attr->value instanceof Node\LiteralNode + && $attr->value->value === '...' + && !$this->isFirstClassCallable($node) + ) { trigger_error("Replace ... with _ in configuration file '$this->file'.", E_USER_DEPRECATED); unset($node->attributes[$i]); $index = true; diff --git a/src/DI/Definitions/FunctionCallable.php b/src/DI/Definitions/FunctionCallable.php new file mode 100644 index 000000000..1de1a15ab --- /dev/null +++ b/src/DI/Definitions/FunctionCallable.php @@ -0,0 +1,44 @@ +function . '(...)'; + } +} diff --git a/src/DI/Definitions/MethodCallable.php b/src/DI/Definitions/MethodCallable.php new file mode 100644 index 000000000..116926c3d --- /dev/null +++ b/src/DI/Definitions/MethodCallable.php @@ -0,0 +1,53 @@ +objectOrClass instanceof Expression) { + $this->objectOrClass->complete($resolver); + } + } + + + public function generateCode(PhpGenerator $generator): string + { + return is_string($this->objectOrClass) + ? $generator->formatPhp('?::?(...)', [new Php\Literal($this->objectOrClass), $this->method]) + : $generator->formatPhp('?->?(...)', [new Php\Literal($this->objectOrClass->generateCode($generator)), $this->method]); + } +} diff --git a/src/DI/Definitions/Statement.php b/src/DI/Definitions/Statement.php index ba65b1ee2..3c4b526a2 100644 --- a/src/DI/Definitions/Statement.php +++ b/src/DI/Definitions/Statement.php @@ -74,10 +74,7 @@ public function resolveType(Resolver $resolver): ?string { $entity = $this->normalizeEntity($resolver); - if ($this->arguments === Resolver::getFirstClassCallable()) { - return \Closure::class; - - } elseif (is_array($entity)) { + if (is_array($entity)) { if ($entity[0] instanceof Expression) { $entity[0] = $entity[0]->resolveType($resolver); if (!$entity[0]) { @@ -145,15 +142,6 @@ public function complete(Resolver $resolver): void $arguments = $this->arguments; switch (true) { - case $this->arguments === Resolver::getFirstClassCallable(): - if (!is_array($entity) || !Php\Helpers::isIdentifier($entity[1])) { - throw new ServiceCreationException(sprintf('Cannot create closure for %s(...)', $entity)); - } - if ($entity[0] instanceof self) { - $entity[0]->complete($resolver); - } - break; - case is_string($entity) && str_contains($entity, '?'): // PHP literal break; diff --git a/src/DI/Resolver.php b/src/DI/Resolver.php index 18a6e6fa0..e1ece774f 100644 --- a/src/DI/Resolver.php +++ b/src/DI/Resolver.php @@ -353,14 +353,6 @@ private static function isArrayOf(\ReflectionParameter $parameter, ?Nette\Utils\ } - /** @internal */ - public static function getFirstClassCallable(): array - { - static $x = [new Nette\PhpGenerator\Literal('...')]; - return $x; - } - - /** @deprecated */ public function resolveReferenceType(Reference $ref): ?string { diff --git a/tests/DI/Compiler.first-class-callable.phpt b/tests/DI/Compiler.first-class-callable.phpt index c2601dff0..bc8ab2966 100644 --- a/tests/DI/Compiler.first-class-callable.phpt +++ b/tests/DI/Compiler.first-class-callable.phpt @@ -28,7 +28,7 @@ class Service test('Valid callables', function () { $config = ' services: - - Service( Service::foo(...), @a::foo(...), ::trim(...) ) + - Service( Service::foo(...), @a::b()::foo(...), ::trim(...) ) a: stdClass '; $loader = new DI\Config\Loader; @@ -36,7 +36,7 @@ test('Valid callables', function () { $compiler->addConfig($loader->load(Tester\FileMock::create($config, 'neon'))); $code = $compiler->compile(); - Assert::contains('new Service(Service::foo(...), $this->getService(\'a\')->foo(...), trim(...));', $code); + Assert::contains('new Service(Service::foo(...), $this->getService(\'a\')->b()->foo(...), trim(...));', $code); }); @@ -50,7 +50,7 @@ Assert::exception(function () { $compiler = new DI\Compiler; $compiler->addConfig($loader->load(Tester\FileMock::create($config, 'neon'))); $compiler->compile(); -}, Nette\DI\ServiceCreationException::class, 'Service of type Closure: Cannot create closure for Service(...)'); +}, Nette\DI\InvalidConfigurationException::class, "Cannot create closure for 'Service' in config file (used in %a%)"); // Invalid callable 2 @@ -63,4 +63,4 @@ Assert::exception(function () { $compiler = new DI\Compiler; $compiler->addConfig($loader->load(Tester\FileMock::create($config, 'neon'))); $compiler->compile(); -}, Nette\DI\ServiceCreationException::class, 'Service of type Service: Cannot create closure for Service(...) (used in Service::__construct())'); +}, Nette\DI\InvalidConfigurationException::class, "Cannot create closure for 'Service' in config file (used in %a%)"); From f049f5bf0788f406baadba9d543555f720efdf07 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 25 Nov 2024 11:38:08 +0100 Subject: [PATCH 11/22] creating lazy services in PHP 8.4 WIP --- src/DI/Definitions/ServiceDefinition.php | 36 +++++++++++++++++------ src/DI/Extensions/DIExtension.php | 15 ++++++++++ src/DI/Extensions/DefinitionSchema.php | 1 + src/DI/Extensions/ServicesExtension.php | 4 +++ tests/DI/Compiler.loadConfig.include.phpt | 1 + 5 files changed, 48 insertions(+), 9 deletions(-) diff --git a/src/DI/Definitions/ServiceDefinition.php b/src/DI/Definitions/ServiceDefinition.php index 0546b54b3..9b26b2871 100644 --- a/src/DI/Definitions/ServiceDefinition.php +++ b/src/DI/Definitions/ServiceDefinition.php @@ -11,6 +11,7 @@ use Nette; use Nette\DI\ServiceCreationException; +use Nette\Utils\Strings; /** @@ -24,6 +25,8 @@ final class ServiceDefinition extends Definition { use Nette\SmartObject; + public ?bool $lazy = null; + private Statement $creator; /** @var Statement[] */ @@ -181,18 +184,33 @@ private function prependSelf(Statement $setup): Statement public function generateCode(Nette\DI\PhpGenerator $generator): string { - $code = $this->creator->generateCode($generator) . ";\n"; - if (!$this->setup) { - return 'return ' . $code; + $lines = []; + foreach ([$this->creator, ...$this->setup] as $stmt) { + $lines[] = $stmt->generateCode($generator) . ";\n"; } - $code = '$service = ' . $code; - foreach ($this->setup as $setup) { - $code .= $setup->generateCode($generator) . ";\n"; + $class = $this->creator->getEntity(); + $lazy = PHP_VERSION_ID >= 80400 + && $this->lazy + && is_string($class) + && ($ancestor = ($tmp = class_parents($class)) ? array_pop($tmp) : $class) + && !(new \ReflectionClass($ancestor))->isInternal() + && !preg_grep('#(?:func_get_arg|func_num_args)#i', $lines); // latteFactory workaround + + if ($lazy) { + $lines[0] = (new \ReflectionClass($class))->hasMethod('__construct') + ? $generator->formatPhp("\$service->__construct(...?:);\n", [$this->creator->arguments]) + : ''; + return "return new ReflectionClass($class::class)->newLazyGhost(function (\$service) {\n" + . Strings::indent(implode('', $lines)) + . '});'; + + } elseif (count($lines) === 1) { + return 'return ' . $lines[0]; + + } else { + return '$service = ' . implode('', $lines) . 'return $service;'; } - - $code .= 'return $service;'; - return $code; } diff --git a/src/DI/Extensions/DIExtension.php b/src/DI/Extensions/DIExtension.php index 5b789cffa..ce17727d4 100644 --- a/src/DI/Extensions/DIExtension.php +++ b/src/DI/Extensions/DIExtension.php @@ -10,6 +10,7 @@ namespace Nette\DI\Extensions; use Nette; +use Nette\DI\Definitions\ServiceDefinition; use Tracy; @@ -36,6 +37,7 @@ public function __construct(bool $debugMode = false) public array $excluded = []; public ?string $parentClass = null; public object $export; + public bool $lazy = false; }; $this->config->export = new class { public bool $parameters = true; @@ -56,6 +58,19 @@ public function loadConfiguration(): void } + public function beforeCompile(): void + { + if ($this->config->lazy) { + $builder = $this->getContainerBuilder(); + foreach ($builder->getDefinitions() as $def) { + if ($def instanceof ServiceDefinition) { + $def->lazy ??= true; + } + } + } + } + + public function afterCompile(Nette\PhpGenerator\ClassType $class): void { if ($this->config->parentClass) { diff --git a/src/DI/Extensions/DefinitionSchema.php b/src/DI/Extensions/DefinitionSchema.php index a49ab3faa..487ddecae 100644 --- a/src/DI/Extensions/DefinitionSchema.php +++ b/src/DI/Extensions/DefinitionSchema.php @@ -173,6 +173,7 @@ private static function getServiceSchema(): Schema 'tags' => Expect::array(), 'reset' => Expect::array(), 'alteration' => Expect::bool(), + 'lazy' => Expect::bool(), ]); } diff --git a/src/DI/Extensions/ServicesExtension.php b/src/DI/Extensions/ServicesExtension.php index 8bb50257d..075cf54a3 100644 --- a/src/DI/Extensions/ServicesExtension.php +++ b/src/DI/Extensions/ServicesExtension.php @@ -113,6 +113,10 @@ private function updateServiceDefinition(Definitions\ServiceDefinition $definiti if (isset($config->inject)) { $definition->addTag(InjectExtension::TagInject, $config->inject); } + + if (isset($config->lazy)) { + $definition->lazy = $config->lazy; + } } diff --git a/tests/DI/Compiler.loadConfig.include.phpt b/tests/DI/Compiler.loadConfig.include.phpt index 26d00f815..590afe064 100644 --- a/tests/DI/Compiler.loadConfig.include.phpt +++ b/tests/DI/Compiler.loadConfig.include.phpt @@ -49,6 +49,7 @@ Assert::equal([ 'tags' => [], 'reset' => [], 'alteration' => null, + 'lazy' => null, 'defType' => Nette\DI\Definitions\ServiceDefinition::class, ], ], From 880f28d29960531c721c46fb473535151ef50c36 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 Sep 2021 23:09:50 +0200 Subject: [PATCH 12/22] opened 4.0-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f40ee38e6..328f3f37a 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "4.0-dev" } } } From b98757e44cea9c923ec465f54561cac40b562092 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 2 Dec 2024 05:53:18 +0100 Subject: [PATCH 13/22] exception messages use [Service ...]\n format [WIP] added Definition::getDescriptor(), Helpers::entityToString() --- src/DI/Definitions/AccessorDefinition.php | 14 +-- src/DI/Definitions/Definition.php | 27 ++++- src/DI/Definitions/FactoryDefinition.php | 13 ++- src/DI/Definitions/LocatorDefinition.php | 13 ++- src/DI/Definitions/ServiceDefinition.php | 17 ++- src/DI/Definitions/Statement.php | 10 +- src/DI/Extensions/InjectExtension.php | 8 +- src/DI/Extensions/ServicesExtension.php | 9 +- src/DI/Helpers.php | 27 ++++- src/DI/PhpGenerator.php | 2 +- src/DI/Resolver.php | 53 ++------- tests/DI/Compiler.configOverride.phpt | 3 +- .../DI/Compiler.extensionOverride.errors.phpt | 3 +- tests/DI/Compiler.functions.phpt | 4 +- tests/DI/Compiler.generatedFactory.phpt | 9 +- .../ContainerBuilder.autowiring.novalue.phpt | 6 +- tests/DI/ContainerBuilder.create.error.phpt | 104 +++++++++++++----- tests/DI/ContainerBuilder.error.phpt | 34 +++++- tests/DI/ContainerBuilder.recursive.phpt | 3 +- .../ContainerBuilder.resolveTypes.next.phpt | 27 +++-- tests/DI/ContainerBuilder.resolveTypes.phpt | 27 +++-- tests/DI/ContainerBuilder.selfdependency.phpt | 4 +- .../Definitions.AccessorDefinition.api.phpt | 24 ++-- ...efinitions.AccessorDefinition.resolve.phpt | 9 +- .../DI/Definitions.FactoryDefinition.api.phpt | 21 ++-- ...Definitions.FactoryDefinition.resolve.phpt | 9 +- tests/DI/Definitions.ImportedDefinition.phpt | 6 +- .../DI/Definitions.LocatorDefinition.api.phpt | 26 +++-- ...Definitions.LocatorDefinition.resolve.phpt | 3 +- tests/DI/Definitions.ServiceDefinition.phpt | 3 +- tests/DI/InjectExtension.errors.phpt | 9 +- .../DI/Resolver.autowireArguments.errors.phpt | 9 +- 32 files changed, 360 insertions(+), 176 deletions(-) diff --git a/src/DI/Definitions/AccessorDefinition.php b/src/DI/Definitions/AccessorDefinition.php index c416ca9a7..fc5bc504c 100644 --- a/src/DI/Definitions/AccessorDefinition.php +++ b/src/DI/Definitions/AccessorDefinition.php @@ -28,8 +28,8 @@ public function setImplement(string $interface): static { if (!interface_exists($interface)) { throw new Nette\InvalidArgumentException(sprintf( - "Service '%s': Interface '%s' not found.", - $this->getName(), + "[%s]\nInterface '%s' not found.", + $this->getDescriptor(), $interface, )); } @@ -44,19 +44,19 @@ public function setImplement(string $interface): static || count($rc->getMethods()) > 1 ) { throw new Nette\InvalidArgumentException(sprintf( - "Service '%s': Interface %s must have just one non-static method get().", - $this->getName(), + "[%s]\nInterface %s must have just one non-static method get().", + $this->getDescriptor(), $interface, )); } elseif ($method->getNumberOfParameters()) { throw new Nette\InvalidArgumentException(sprintf( - "Service '%s': Method %s::get() must have no parameters.", - $this->getName(), + "[%s]\nMethod %s::get() must have no parameters.", + $this->getDescriptor(), $interface, )); } - Helpers::ensureClassType(Type::fromReflection($method), "return type of $interface::get()"); + Helpers::ensureClassType(Type::fromReflection($method), "return type of $interface::get()", $this->getDescriptor()); return parent::setType($interface); } diff --git a/src/DI/Definitions/Definition.php b/src/DI/Definitions/Definition.php index d8ac3a0d1..31651174c 100644 --- a/src/DI/Definitions/Definition.php +++ b/src/DI/Definitions/Definition.php @@ -46,6 +46,29 @@ final public function getName(): ?string } + final public function isAnonymous(): bool + { + return !$this->name || ctype_digit($this->name); + } + + + public function getDescriptor(): string + { + if (!$this->isAnonymous()) { + return "Service '$this->name'" . ($this->type ? " of type $this->type" : ''); + + } elseif ($this->type) { + return "Service of type $this->type"; + + } elseif ($this->name) { + return "Service '$this->name'"; + + } else { + return 'Service ?'; + } + } + + protected function setType(?string $type): static { if ($this->autowired && $this->notifier && $this->type !== $type) { @@ -56,8 +79,8 @@ protected function setType(?string $type): static $this->type = null; } elseif (!class_exists($type) && !interface_exists($type)) { throw new Nette\InvalidArgumentException(sprintf( - "Service '%s': Class or interface '%s' not found.", - $this->name, + "[%s]\nClass or interface '%s' not found.", + $this->getDescriptor(), $type, )); } else { diff --git a/src/DI/Definitions/FactoryDefinition.php b/src/DI/Definitions/FactoryDefinition.php index 2bcee4519..a79b77e95 100644 --- a/src/DI/Definitions/FactoryDefinition.php +++ b/src/DI/Definitions/FactoryDefinition.php @@ -36,8 +36,8 @@ public function setImplement(string $interface): static { if (!interface_exists($interface)) { throw new Nette\InvalidArgumentException(sprintf( - "Service '%s': Interface '%s' not found.", - $this->getName(), + "[%s]\nInterface '%s' not found.", + $this->getDescriptor(), $interface, )); } @@ -46,13 +46,13 @@ public function setImplement(string $interface): static $method = $rc->getMethods()[0] ?? null; if (!$method || $method->isStatic() || $method->name !== self::MethodCreate || count($rc->getMethods()) > 1) { throw new Nette\InvalidArgumentException(sprintf( - "Service '%s': Interface %s must have just one non-static method create().", - $this->getName(), + "[%s]\nInterface %s must have just one non-static method create().", + $this->getDescriptor(), $interface, )); } - Helpers::ensureClassType(Type::fromReflection($method), "return type of $interface::create()"); + Helpers::ensureClassType(Type::fromReflection($method), "return type of $interface::create()", $this->getDescriptor()); return parent::setType($interface); } @@ -105,7 +105,8 @@ public function resolveType(Nette\DI\Resolver $resolver): void if (!$type->allows($resultDef->getType())) { throw new ServiceCreationException(sprintf( - 'Factory for %s cannot create incompatible %s type.', + "[%s]\nFactory for %s cannot create incompatible %s type.", + $this->getDescriptor(), $type, $resultDef->getType(), )); diff --git a/src/DI/Definitions/LocatorDefinition.php b/src/DI/Definitions/LocatorDefinition.php index 3cdfb1615..52db3c522 100644 --- a/src/DI/Definitions/LocatorDefinition.php +++ b/src/DI/Definitions/LocatorDefinition.php @@ -25,12 +25,12 @@ final class LocatorDefinition extends Definition public function setImplement(string $interface): static { if (!interface_exists($interface)) { - throw new Nette\InvalidArgumentException(sprintf("Service '%s': Interface '%s' not found.", $this->getName(), $interface)); + throw new Nette\InvalidArgumentException(sprintf("[%s]\nInterface '%s' not found.", $this->getDescriptor(), $interface)); } $methods = (new \ReflectionClass($interface))->getMethods(); if (!$methods) { - throw new Nette\InvalidArgumentException(sprintf("Service '%s': Interface %s must have at least one method.", $this->getName(), $interface)); + throw new Nette\InvalidArgumentException(sprintf("[%s]\nInterface %s must have at least one method.", $this->getDescriptor(), $interface)); } foreach ($methods as $method) { @@ -39,8 +39,8 @@ public function setImplement(string $interface): static || (preg_match('#^(get|create)[A-Z]#', $method->name) && $method->getNumberOfParameters() === 0) )) { throw new Nette\InvalidArgumentException(sprintf( - "Service '%s': Method %s::%s() does not meet the requirements: is create(\$name), get(\$name), create*() or get*() and is non-static.", - $this->getName(), + "[%s]\nMethod %s::%s() does not meet the requirements: is create(\$name), get(\$name), create*() or get*() and is non-static.", + $this->getDescriptor(), $interface, $method->name, )); @@ -50,6 +50,7 @@ public function setImplement(string $interface): static Nette\DI\Helpers::ensureClassType( Nette\Utils\Type::fromReflection($method), "return type of $interface::$method->name()", + $this->getDescriptor(), allowNullable: true, ); } @@ -110,8 +111,8 @@ public function complete(Nette\DI\Resolver $resolver): void foreach ($resolver->getContainerBuilder()->findByTag($this->tagged) as $name => $tag) { if (isset($this->references[$tag])) { trigger_error(sprintf( - "Service '%s': duplicated tag '%s' with value '%s'.", - $this->getName(), + "[%s]\nDuplicated tag '%s' with value '%s'.", + $this->getDescriptor(), $this->tagged, $tag, )); diff --git a/src/DI/Definitions/ServiceDefinition.php b/src/DI/Definitions/ServiceDefinition.php index 9b26b2871..7cbdce179 100644 --- a/src/DI/Definitions/ServiceDefinition.php +++ b/src/DI/Definitions/ServiceDefinition.php @@ -39,6 +39,17 @@ public function __construct() } + public function getDescriptor(): string + { + $entity = $this->getEntity(); + if ($entity && $this->isAnonymous()) { + return 'Service ' . (is_string($entity) ? "of type $entity" : Nette\DI\Helpers::describeExpression($entity)); + } + + return parent::getDescriptor(); + } + + public function setType(?string $type): static { return parent::setType($type); @@ -169,7 +180,11 @@ public function complete(Nette\DI\Resolver $resolver): void $this->creator->complete($resolver); foreach ($this->setup as $setup) { - $setup->complete($resolver->withCurrentServiceAvailable()); + try { + $setup->complete($resolver->withCurrentServiceAvailable()); + } catch (ServiceCreationException $e) { + throw $e->setMessage($e->getMessage() . ' (in setup)'); + } } } diff --git a/src/DI/Definitions/Statement.php b/src/DI/Definitions/Statement.php index 3c4b526a2..3f3c6d7cd 100644 --- a/src/DI/Definitions/Statement.php +++ b/src/DI/Definitions/Statement.php @@ -242,8 +242,14 @@ public function complete(Resolver $resolver): void try { $this->arguments = $this->completeArguments($resolver, $arguments); } catch (ServiceCreationException $e) { - if (!str_contains($e->getMessage(), ' (used in')) { - $e->setMessage($e->getMessage() . " (used in {$resolver->entityToString($entity)})"); + if (!str_contains($e->getMessage(), "\nRelated to")) { + if (is_string($entity)) { + $desc = $entity . '::__construct()'; + } else { + $desc = DI\Helpers::describeExpression($entity); + $desc = preg_replace('~@self::~A', '', $desc); + } + $e->setMessage($e->getMessage() . "\nRelated to $desc."); } throw $e; diff --git a/src/DI/Extensions/InjectExtension.php b/src/DI/Extensions/InjectExtension.php index d0da1a927..c84c7ecc7 100644 --- a/src/DI/Extensions/InjectExtension.php +++ b/src/DI/Extensions/InjectExtension.php @@ -67,7 +67,7 @@ private function updateDefinition(Definitions\ServiceDefinition $def): void } if ($builder) { - self::checkType($class, $property, $type, $builder); + self::checkType($class, $property, $type, $builder, $def); } array_unshift($setups, $inject); } @@ -148,7 +148,7 @@ public static function callInjects(DI\Container $container, object $service): vo } foreach (self::getInjectProperties($service::class) as $property => $type) { - self::checkType($service, $property, $type, $container); + self::checkType($service, $property, $type, $container, null); $service->$property = $container->getByType($type); } } @@ -159,11 +159,13 @@ private static function checkType( string $name, ?string $type, DI\Container|DI\ContainerBuilder $container, + ?Definitions\Definition $def, ): void { if (!$container->getByType($type, throw: false)) { throw new Nette\DI\MissingServiceException(sprintf( - 'Service of type %s required by %s not found. Did you add it to configuration file?', + "%sService of type %s required by %s not found.\nDid you add it to configuration file?", + $def ? '[' . $def->getDescriptor() . "]\n" : '', $type, Reflection::toString(new \ReflectionProperty($class, $name)), )); diff --git a/src/DI/Extensions/ServicesExtension.php b/src/DI/Extensions/ServicesExtension.php index 075cf54a3..50efe7f38 100644 --- a/src/DI/Extensions/ServicesExtension.php +++ b/src/DI/Extensions/ServicesExtension.php @@ -53,7 +53,7 @@ private function loadDefinition(?string $name, \stdClass $config): void $this->getContainerBuilder()->removeDefinition($name); return; } elseif (!empty($config->alteration) && !$this->getContainerBuilder()->hasDefinition($name)) { - throw new Nette\DI\InvalidConfigurationException('missing original definition for alteration.'); + throw new Nette\DI\InvalidConfigurationException('Missing original definition for alteration.'); } $def = $this->retrieveDefinition($name, $config); @@ -68,7 +68,12 @@ private function loadDefinition(?string $name, \stdClass $config): void $this->{$methods[$config->defType]}($def, $config); $this->updateDefinition($def, $config); } catch (\Throwable $e) { - throw new Nette\DI\InvalidConfigurationException(($name ? "Service '$name': " : '') . $e->getMessage(), 0, $e); + $message = $e->getMessage(); + if ($name && !str_starts_with($message, '[Service ')) { + $message = "[Service '$name']\n$message"; + } + + throw new Nette\DI\InvalidConfigurationException($message, 0, $e); } } diff --git a/src/DI/Helpers.php b/src/DI/Helpers.php index 610239b8d..cf257f8ea 100644 --- a/src/DI/Helpers.php +++ b/src/DI/Helpers.php @@ -232,17 +232,23 @@ public static function getReturnTypeAnnotation(\ReflectionFunctionAbstract $func } - public static function ensureClassType(?Type $type, string $hint, bool $allowNullable = false): string + public static function ensureClassType( + ?Type $type, + string $hint, + string $descriptor = '', + bool $allowNullable = false, + ): string { + $descriptor = $descriptor ? "[$descriptor]\n" : ''; if (!$type) { - throw new ServiceCreationException(sprintf('%s is not declared.', ucfirst($hint))); + throw new ServiceCreationException(sprintf('%s%s is not declared.', $descriptor, ucfirst($hint))); } elseif (!$type->isClass() || (!$allowNullable && $type->allows('null'))) { - throw new ServiceCreationException(sprintf("%s is expected to not be %sbuilt-in/complex, '%s' given.", ucfirst($hint), $allowNullable ? '' : 'nullable/', $type)); + throw new ServiceCreationException(sprintf("%s%s is expected to not be %sbuilt-in/complex, '%s' given.", $descriptor, ucfirst($hint), $allowNullable ? '' : 'nullable/', $type)); } $class = $type->getSingleName(); if (!class_exists($class) && !interface_exists($class)) { - throw new ServiceCreationException(sprintf("Class '%s' not found.\nCheck the %s.", $class, $hint)); + throw new ServiceCreationException(sprintf("%sClass '%s' not found.\nCheck the %s.", $descriptor, $class, $hint)); } return $class; @@ -282,4 +288,17 @@ public static function convertType(mixed $value, string $type): mixed $type, )); } + + + public static function describeExpression(string|array|Reference $expr, bool $inner = false): string + { + return match (true) { + is_string($expr) => $expr . ($inner ? '()' : ''), + $expr instanceof Reference => '@' . $expr->getValue(), + default => self::describeExpression($expr[0] instanceof Statement ? $expr[0]->getEntity() : $expr[0], inner: true) + . '::' + . $expr[1] + . (str_contains($expr[1], '$') ? '' : '()'), + }; + } } diff --git a/src/DI/PhpGenerator.php b/src/DI/PhpGenerator.php index 8f16e2dc3..ea5563982 100644 --- a/src/DI/PhpGenerator.php +++ b/src/DI/PhpGenerator.php @@ -96,7 +96,7 @@ public function generateMethod(Definitions\Definition $def): Php\Method return $method; } catch (\Throwable $e) { - throw new ServiceCreationException("Service '$name': " . $e->getMessage(), 0, $e); + throw new ServiceCreationException(sprintf("[%s]\n%s", $def->getDescriptor(), $e->getMessage()), 0, $e); } } diff --git a/src/DI/Resolver.php b/src/DI/Resolver.php index e1ece774f..73b525847 100644 --- a/src/DI/Resolver.php +++ b/src/DI/Resolver.php @@ -147,25 +147,16 @@ public function addDependency(\ReflectionClass|\ReflectionFunctionAbstract|strin /** @internal */ public function completeException(\Throwable $e, Definition $def): ServiceCreationException { - if ($e instanceof ServiceCreationException && str_starts_with($e->getMessage(), "Service '")) { + $message = $e->getMessage(); + if ($e instanceof ServiceCreationException && str_starts_with($message, '[Service ')) { return $e; } - $name = $def->getName(); - $type = $def->getType(); - if ($name && !ctype_digit($name)) { - $message = "Service '$name'" . ($type ? " (type of $type)" : '') . ': '; - } elseif ($type) { - $message = "Service of type $type: "; - } elseif ($def instanceof Definitions\ServiceDefinition && $def->getEntity()) { - $message = 'Service (' . $this->entityToString($def->getEntity()) . '): '; - } else { - $message = ''; + if ($tmp = $def->getType()) { + $message = str_replace(" $tmp::", ' ' . preg_replace('~.*\\\\~', '', $tmp) . '::', $message); } - $message .= $type - ? str_replace("$type::", preg_replace('~.*\\\\~', '', $type) . '::', $e->getMessage()) - : $e->getMessage(); + $message = '[' . $def->getDescriptor() . "]\n" . $message; return $e instanceof ServiceCreationException ? $e->setMessage($message) @@ -173,34 +164,6 @@ public function completeException(\Throwable $e, Definition $def): ServiceCreati } - /** @internal */ - public function entityToString($entity): string - { - $referenceToText = fn(Reference $ref): string => $ref->isSelf() && $this->currentService - ? '@' . $this->currentService->getName() - : '@' . $ref->getValue(); - if (is_string($entity)) { - return $entity . '::__construct()'; - } elseif ($entity instanceof Reference) { - $entity = $referenceToText($entity); - } elseif (is_array($entity)) { - if (!str_contains($entity[1], '$')) { - $entity[1] .= '()'; - } - - if ($entity[0] instanceof Reference) { - $entity[0] = $referenceToText($entity[0]); - } elseif (!is_string($entity[0])) { - return $entity[1]; - } - - return implode('::', $entity); - } - - return (string) $entity; - } - - public function autowireServices(\ReflectionFunctionAbstract $method, array $arguments): array { $getter = fn(string $type, bool $single) => $single @@ -301,20 +264,20 @@ private static function autowireArgument(\ReflectionParameter $parameter, callab } catch (MissingServiceException) { $res = null; } catch (ServiceCreationException $e) { - throw new ServiceCreationException("{$e->getMessage()} (required by $desc)", 0, $e); + throw new ServiceCreationException(sprintf("%s\nRequired by %s.", $e->getMessage(), $desc), 0, $e); } if ($res !== null || $parameter->isOptional()) { return $res; } elseif (class_exists($class) || interface_exists($class)) { throw new ServiceCreationException(sprintf( - 'Service of type %s required by %s not found. Did you add it to configuration file?', + "Service of type %s required by %s not found.\nDid you add it to configuration file?", $class, $desc, )); } else { throw new ServiceCreationException(sprintf( - "Class '%s' required by %s not found. Check the parameter type and 'use' statements.", + "Class '%s' required by %s not found.\nCheck the parameter type and 'use' statements.", $class, $desc, )); diff --git a/tests/DI/Compiler.configOverride.phpt b/tests/DI/Compiler.configOverride.phpt index ed31e4996..e0f5d2680 100644 --- a/tests/DI/Compiler.configOverride.phpt +++ b/tests/DI/Compiler.configOverride.phpt @@ -59,5 +59,6 @@ $compiler->addConfig([ Assert::exception( fn() => $compiler->setClassName($class)->compile(), DI\InvalidConfigurationException::class, - "Service 's3': missing original definition for alteration.", + "[Service 's3'] +Missing original definition for alteration.", ); diff --git a/tests/DI/Compiler.extensionOverride.errors.phpt b/tests/DI/Compiler.extensionOverride.errors.phpt index a4b942814..945f0e5b5 100644 --- a/tests/DI/Compiler.extensionOverride.errors.phpt +++ b/tests/DI/Compiler.extensionOverride.errors.phpt @@ -21,4 +21,5 @@ services: bad: alteration: yes '); -}, Nette\DI\InvalidConfigurationException::class, "Service 'bad': missing original definition for alteration."); +}, Nette\DI\InvalidConfigurationException::class, "[Service 'bad'] +Missing original definition for alteration."); diff --git a/tests/DI/Compiler.functions.phpt b/tests/DI/Compiler.functions.phpt index 0afb97a42..08831b44c 100644 --- a/tests/DI/Compiler.functions.phpt +++ b/tests/DI/Compiler.functions.phpt @@ -93,5 +93,7 @@ Assert::exception( - Service(bool(123, 10)) '), Nette\InvalidStateException::class, - 'Service of type Service: Function bool() expects 1 parameter, 2 given. (used in Service::__construct())', + '[Service of type Service] +Function bool() expects 1 parameter, 2 given. +Related to Service::__construct().', ); diff --git a/tests/DI/Compiler.generatedFactory.phpt b/tests/DI/Compiler.generatedFactory.phpt index 81068a106..eac013e90 100644 --- a/tests/DI/Compiler.generatedFactory.phpt +++ b/tests/DI/Compiler.generatedFactory.phpt @@ -266,7 +266,8 @@ Assert::exception(function () { $builder->addFactoryDefinition('one') ->setImplement(Bad2::class); $builder->complete(); -}, Nette\InvalidStateException::class, "Service 'one' (type of Bad2): Type of \$bar in Bad2::create() doesn't match type in Bad1 constructor."); +}, Nette\InvalidStateException::class, "[Service 'one' of type Bad2] +Type of \$bar in Bad2::create() doesn't match type in Bad1 constructor."); @@ -287,7 +288,8 @@ Assert::exception(function () { $builder->addFactoryDefinition('one') ->setImplement(Bad4::class); $builder->complete(); -}, Nette\InvalidStateException::class, "Service 'one' (type of Bad4): Cannot implement Bad4::create(): factory method parameters (\$baz) are not matching Bad3::__construct() parameters (\$bar). Did you mean to use '\$bar' in factory method?"); +}, Nette\InvalidStateException::class, "[Service 'one' of type Bad4] +Cannot implement Bad4::create(): factory method parameters (\$baz) are not matching Bad3::__construct() parameters (\$bar). Did you mean to use '\$bar' in factory method?"); @@ -308,4 +310,5 @@ Assert::exception(function () { $builder->addFactoryDefinition('one') ->setImplement(Bad6::class); $builder->complete(); -}, Nette\InvalidStateException::class, "Service 'one' (type of Bad6): Cannot implement Bad6::create(): factory method parameters (\$baz) are not matching Bad5::__construct() parameters (\$xxx)."); +}, Nette\InvalidStateException::class, "[Service 'one' of type Bad6] +Cannot implement Bad6::create(): factory method parameters (\$baz) are not matching Bad5::__construct() parameters (\$xxx)."); diff --git a/tests/DI/ContainerBuilder.autowiring.novalue.phpt b/tests/DI/ContainerBuilder.autowiring.novalue.phpt index 1a9b7cca4..3aee26fda 100644 --- a/tests/DI/ContainerBuilder.autowiring.novalue.phpt +++ b/tests/DI/ContainerBuilder.autowiring.novalue.phpt @@ -24,7 +24,8 @@ Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('foo')->setType(Foo::class); $container = createContainer($builder); -}, Nette\DI\ServiceCreationException::class, "Service 'foo' (type of Foo): Parameter \$x in Foo::__construct() has no class type or default value, so its value must be specified."); +}, Nette\DI\ServiceCreationException::class, "[Service 'foo' of type Foo] +Parameter \$x in Foo::__construct() has no class type or default value, so its value must be specified."); class Bar @@ -38,7 +39,8 @@ Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('foo')->setType(Bar::class); $container = createContainer($builder); -}, Nette\DI\ServiceCreationException::class, "Service 'foo' (type of Bar): Parameter \$x in Bar::__construct() has no class type or default value, so its value must be specified."); +}, Nette\DI\ServiceCreationException::class, "[Service 'foo' of type Bar] +Parameter \$x in Bar::__construct() has no class type or default value, so its value must be specified."); class Bar2 diff --git a/tests/DI/ContainerBuilder.create.error.phpt b/tests/DI/ContainerBuilder.create.error.phpt index 0af01c12c..7fa6d9bfd 100644 --- a/tests/DI/ContainerBuilder.create.error.phpt +++ b/tests/DI/ContainerBuilder.create.error.phpt @@ -17,14 +17,16 @@ require __DIR__ . '/../bootstrap.php'; testException('', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('one')->setType('X')->setCreator('Unknown'); -}, Nette\InvalidArgumentException::class, "Service 'one': Class or interface 'X' not found."); +}, Nette\InvalidArgumentException::class, "[Service 'one'] +Class or interface 'X' not found."); testException('', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition(null)->setCreator('Unknown'); $builder->complete(); -}, Nette\DI\ServiceCreationException::class, "Service (Unknown::__construct()): Class 'Unknown' not found."); +}, Nette\DI\ServiceCreationException::class, "[Service of type Unknown] +Class 'Unknown' not found."); testException('', function () { @@ -32,7 +34,8 @@ testException('', function () { $builder->addDefinition('one')->setCreator('@two'); $builder->addDefinition('two')->setCreator('Unknown'); $builder->complete(); -}, Nette\InvalidStateException::class, "Service 'two': Class 'Unknown' not found."); +}, Nette\InvalidStateException::class, "[Service 'two'] +Class 'Unknown' not found."); testException('', function () { @@ -40,28 +43,32 @@ testException('', function () { $builder->addDefinition('one')->setCreator(new Reference('two')); $builder->addDefinition('two')->setCreator('Unknown'); $builder->complete(); -}, Nette\InvalidStateException::class, "Service 'two': Class 'Unknown' not found."); +}, Nette\InvalidStateException::class, "[Service 'two'] +Class 'Unknown' not found."); testException('', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('one')->setCreator('stdClass::foo'); $builder->complete(); -}, Nette\InvalidStateException::class, "Service 'one': Method stdClass::foo() is not callable."); +}, Nette\InvalidStateException::class, "[Service 'one'] +Method stdClass::foo() is not callable."); testException('', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('one')->setCreator('Nette\DI\Container::foo'); // has __magic $builder->complete(); -}, Nette\InvalidStateException::class, "Service 'one': Method Nette\\DI\\Container::foo() is not callable."); +}, Nette\InvalidStateException::class, "[Service 'one'] +Method Nette\\DI\\Container::foo() is not callable."); testException('', function () { $builder = new DI\ContainerBuilder; $builder->addFactoryDefinition('one') ->setImplement('Unknown'); -}, Nette\InvalidArgumentException::class, "Service 'one': Interface 'Unknown' not found."); +}, Nette\InvalidArgumentException::class, "[Service 'one'] +Interface 'Unknown' not found."); @@ -74,7 +81,8 @@ testException('', function () { $builder = new DI\ContainerBuilder; $builder->addFactoryDefinition('one') ->setImplement(Bad4::class); -}, Nette\InvalidStateException::class, 'Return type of Bad4::create() is not declared.'); +}, Nette\InvalidStateException::class, "[Service 'one'] +Return type of Bad4::create() is not declared."); interface Bad5 @@ -87,7 +95,8 @@ testException('', function () { $builder->addAccessorDefinition('one') ->setImplement(Bad5::class); $builder->complete(); -}, Nette\InvalidArgumentException::class, "Service 'one': Method Bad5::get() must have no parameters."); +}, Nette\InvalidArgumentException::class, "[Service 'one'] +Method Bad5::get() must have no parameters."); class Bad6 @@ -101,7 +110,8 @@ testException('', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('one')->setCreator('Bad6::create'); $builder->complete(); -}, Nette\DI\ServiceCreationException::class, "Service 'one': Method Bad6::create() is not callable."); +}, Nette\DI\ServiceCreationException::class, "[Service 'one'] +Method Bad6::create() is not callable."); class Bad7 @@ -115,7 +125,8 @@ testException('', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('one')->setCreator('Bad7::create'); $builder->complete(); -}, Nette\DI\ServiceCreationException::class, "Service 'one': Unknown service type, specify it or declare return type of factory method."); +}, Nette\DI\ServiceCreationException::class, "[Service 'one'] +Unknown service type, specify it or declare return type of factory method."); class Bad8 @@ -129,7 +140,8 @@ testException('', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('one')->setType(Bad8::class); $builder->complete(); -}, Nette\InvalidStateException::class, "Service 'one' (type of Bad8): Class Bad8 has private constructor."); +}, Nette\InvalidStateException::class, "[Service 'one' of type Bad8] +Class Bad8 has private constructor."); class Good @@ -143,13 +155,17 @@ testException('fail in argument', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('one')->setCreator(Good::class, [new Statement('Unknown')]); $builder->complete(); -}, Nette\InvalidStateException::class, "Service 'one' (type of Good): Class 'Unknown' not found. (used in Good::__construct())"); +}, Nette\InvalidStateException::class, "[Service 'one' of type Good] +Class 'Unknown' not found. +Related to Good::__construct()."); testException('fail in argument', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('one')->setCreator(Good::class, [new Statement(Bad8::class)]); $builder->complete(); -}, Nette\InvalidStateException::class, "Service 'one' (type of Good): Class Bad8 has private constructor. (used in Good::__construct())"); +}, Nette\InvalidStateException::class, "[Service 'one' of type Good] +Class Bad8 has private constructor. +Related to Good::__construct()."); abstract class Bad9 @@ -163,7 +179,8 @@ testException('abstract class cannot be instantiated', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('one')->setType(Bad9::class); $builder->complete(); -}, Nette\InvalidStateException::class, "Service 'one' (type of Bad9): Class Bad9 is abstract."); +}, Nette\InvalidStateException::class, "[Service 'one' of type Bad9] +Class Bad9 is abstract."); trait Bad10 @@ -177,7 +194,8 @@ testException('trait cannot be instantiated', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('one')->setCreator('Bad10::method'); $builder->complete(); -}, Nette\InvalidStateException::class, "Service 'one': Method Bad10::method() is not callable."); +}, Nette\InvalidStateException::class, "[Service 'one'] +Method Bad10::method() is not callable."); class ConstructorParam @@ -201,7 +219,9 @@ services: b: stdClass bad: ConstructorParam '); -}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of ConstructorParam): Multiple services of type stdClass found: a, b (required by \$x in ConstructorParam::__construct())"); +}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type ConstructorParam] +Multiple services of type stdClass found: a, b +Required by \$x in ConstructorParam::__construct()."); testException('forced autowiring fail', function () { @@ -211,7 +231,9 @@ services: b: stdClass bad: ConstructorParam(@\stdClass) '); -}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of ConstructorParam): Multiple services of type stdClass found: a, b (used in ConstructorParam::__construct())"); +}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type ConstructorParam] +Multiple services of type stdClass found: a, b +Related to ConstructorParam::__construct()."); testException('autowiring fail in chain', function () { @@ -221,7 +243,9 @@ services: b: stdClass bad: MethodParam()::foo() '); -}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of MethodParam): Multiple services of type stdClass found: a, b (required by \$x in MethodParam::foo())"); +}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type MethodParam] +Multiple services of type stdClass found: a, b +Required by \$x in MethodParam::foo()."); testException('forced autowiring fail in chain', function () { @@ -231,7 +255,9 @@ services: b: stdClass bad: MethodParam()::foo(@\stdClass) '); -}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of MethodParam): Multiple services of type stdClass found: a, b (used in foo())"); +}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type MethodParam] +Multiple services of type stdClass found: a, b +Related to MethodParam()::foo()."); testException('autowiring fail in argument', function () { @@ -241,7 +267,10 @@ services: b: stdClass bad: Good(ConstructorParam()) '); -}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of Good): Multiple services of type stdClass found: a, b (required by \$x in ConstructorParam::__construct()) (used in Good::__construct())"); +}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type Good] +Multiple services of type stdClass found: a, b +Required by \$x in ConstructorParam::__construct(). +Related to Good::__construct()."); testException('forced autowiring fail in argument', function () { @@ -251,7 +280,9 @@ services: b: stdClass bad: Good(ConstructorParam(@\stdClass)) '); -}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of Good): Multiple services of type stdClass found: a, b (used in ConstructorParam::__construct())"); +}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type Good] +Multiple services of type stdClass found: a, b +Related to ConstructorParam::__construct()."); testException('autowiring fail in chain in argument', function () { @@ -261,7 +292,10 @@ services: b: stdClass bad: Good(MethodParam()::foo()) '); -}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of Good): Multiple services of type stdClass found: a, b (required by \$x in MethodParam::foo()) (used in Good::__construct())"); +}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type Good] +Multiple services of type stdClass found: a, b +Required by \$x in MethodParam::foo(). +Related to Good::__construct()."); testException('forced autowiring fail in chain in argument', function () { @@ -271,7 +305,9 @@ services: b: stdClass bad: Good(MethodParam()::foo(@\stdClass)) '); -}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of Good): Multiple services of type stdClass found: a, b (used in foo())"); +}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type Good] +Multiple services of type stdClass found: a, b +Related to MethodParam()::foo()."); testException('forced autowiring fail in property passing', function () { @@ -284,7 +320,9 @@ services: setup: - $a = @\stdClass '); -}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of Good): Multiple services of type stdClass found: a, b (used in @bad::\$a)"); +}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type Good] +Multiple services of type stdClass found: a, b +Related to \$a. (in setup)"); testException('autowiring fail in rich property passing', function () { @@ -297,7 +335,9 @@ services: setup: - $a = MethodParam()::foo(@\stdClass) '); -}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of Good): Multiple services of type stdClass found: a, b (used in foo())"); +}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type Good] +Multiple services of type stdClass found: a, b +Related to MethodParam()::foo(). (in setup)"); testException('autowiring fail in method calling', function () { @@ -310,7 +350,9 @@ services: setup: - foo '); -}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of MethodParam): Multiple services of type stdClass found: a, b (required by \$x in MethodParam::foo())"); +}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type MethodParam] +Multiple services of type stdClass found: a, b +Required by \$x in MethodParam::foo(). (in setup)"); testException('forced autowiring fail in method calling', function () { @@ -323,7 +365,9 @@ services: setup: - bar(@\stdClass) '); -}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of Good): Multiple services of type stdClass found: a, b (used in @bad::bar())"); +}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type Good] +Multiple services of type stdClass found: a, b +Related to bar(). (in setup)"); testException('autowiring fail in rich method calling', function () { @@ -336,4 +380,6 @@ services: setup: - bar(MethodParam()::foo(@\stdClass)) '); -}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of Good): Multiple services of type stdClass found: a, b (used in foo())"); +}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type Good] +Multiple services of type stdClass found: a, b +Related to MethodParam()::foo(). (in setup)"); diff --git a/tests/DI/ContainerBuilder.error.phpt b/tests/DI/ContainerBuilder.error.phpt index 33c369aeb..f949188ee 100644 --- a/tests/DI/ContainerBuilder.error.phpt +++ b/tests/DI/ContainerBuilder.error.phpt @@ -7,6 +7,7 @@ declare(strict_types=1); use Nette\DI; +use Nette\DI\Definitions\Statement; use Tester\Assert; @@ -21,7 +22,8 @@ $builder->addDefinition('one') Assert::exception( fn() => $builder->complete(), Nette\InvalidStateException::class, - "Service 'one' (type of stdClass): Expected function, method or property name, '1234' given.", + "[Service 'one' of type stdClass] +Expected function, method or property name, '1234' given. (in setup)", ); @@ -48,5 +50,33 @@ $builder->addDefinition('one') Assert::exception( fn() => $builder->complete(), Nette\InvalidStateException::class, - "Service 'one' (type of stdClass): Missing argument for \$prop[].", + "[Service 'one' of type stdClass] +Missing argument for \$prop[]. (in setup)", +); + + + +$builder = new DI\ContainerBuilder; +$builder->addDefinition(null) + ->setFactory([new Statement('Unknown'), 'foo']); + +Assert::exception( + fn() => $builder->complete(), + Nette\DI\ServiceCreationException::class, + "[Service Unknown()::foo()] +Class 'Unknown' not found.", +); + + + +$builder = new DI\ContainerBuilder; +$builder->addDefinition(null) + ->setFactory('stdClass') + ->addSetup([new Statement('Unknown'), 'foo']); + +Assert::exception( + fn() => $builder->complete(), + Nette\DI\ServiceCreationException::class, + "[Service of type stdClass] +Class 'Unknown' not found. (in setup)", ); diff --git a/tests/DI/ContainerBuilder.recursive.phpt b/tests/DI/ContainerBuilder.recursive.phpt index 8ee4fdb7a..cbd0f08f2 100644 --- a/tests/DI/ContainerBuilder.recursive.phpt +++ b/tests/DI/ContainerBuilder.recursive.phpt @@ -30,5 +30,6 @@ $builder->addDefinition('two') Assert::exception( fn() => createContainer($builder), Nette\DI\ServiceCreationException::class, - "Service 'two': Circular reference detected for services: one, two.", + "[Service 'two'] +Circular reference detected for services: one, two.", ); diff --git a/tests/DI/ContainerBuilder.resolveTypes.next.phpt b/tests/DI/ContainerBuilder.resolveTypes.next.phpt index f19770352..8ce3c0761 100644 --- a/tests/DI/ContainerBuilder.resolveTypes.next.phpt +++ b/tests/DI/ContainerBuilder.resolveTypes.next.phpt @@ -148,56 +148,64 @@ Assert::exception(function () { $builder->addDefinition('a') ->setCreator([new Statement([Factory::class, 'createScalarPhpDoc']), 'next']); $container = @createContainer($builder); // @return is deprecated -}, Nette\DI\ServiceCreationException::class, "Service 'a': Return type of Factory::createScalarPhpDoc() is expected to not be built-in/complex, 'array' given."); +}, Nette\DI\ServiceCreationException::class, "[Service 'a'] +Return type of Factory::createScalarPhpDoc() is expected to not be built-in/complex, 'array' given."); Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') ->setCreator([new Statement([Factory::class, 'createScalar']), 'next']); $container = createContainer($builder); -}, Nette\DI\ServiceCreationException::class, "Service 'a': Return type of Factory::createScalar() is expected to not be built-in/complex, 'array' given."); +}, Nette\DI\ServiceCreationException::class, "[Service 'a'] +Return type of Factory::createScalar() is expected to not be built-in/complex, 'array' given."); Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') ->setCreator([new Statement([Factory::class, 'createObjectPhpDoc']), 'next']); $container = createContainer($builder); -}, Nette\DI\ServiceCreationException::class, "Service 'a': Unknown service type, specify it or declare return type of factory method."); +}, Nette\DI\ServiceCreationException::class, "[Service 'a'] +Unknown service type, specify it or declare return type of factory method."); Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') ->setCreator([new Statement([Factory::class, 'createObject']), 'next']); $container = createContainer($builder); -}, Nette\DI\ServiceCreationException::class, "Service 'a': Unknown service type, specify it or declare return type of factory method."); +}, Nette\DI\ServiceCreationException::class, "[Service 'a'] +Unknown service type, specify it or declare return type of factory method."); Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') ->setCreator([new Statement([Factory::class, 'createObjectNullable']), 'next']); $container = createContainer($builder); -}, Nette\DI\ServiceCreationException::class, "Service 'a': Unknown service type, specify it or declare return type of factory method."); +}, Nette\DI\ServiceCreationException::class, "[Service 'a'] +Unknown service type, specify it or declare return type of factory method."); Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') ->setCreator([new Statement([Factory::class, 'createMixedPhpDoc']), 'next']); $container = createContainer($builder); -}, Nette\DI\ServiceCreationException::class, "Service 'a': Unknown service type, specify it or declare return type of factory method."); +}, Nette\DI\ServiceCreationException::class, "[Service 'a'] +Unknown service type, specify it or declare return type of factory method."); Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') ->setCreator([new Statement([Factory::class, 'createMixed']), 'next']); $container = createContainer($builder); -}, Nette\DI\ServiceCreationException::class, "Service 'a': Unknown service type, specify it or declare return type of factory method."); +}, Nette\DI\ServiceCreationException::class, "[Service 'a'] +Unknown service type, specify it or declare return type of factory method."); Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') ->setCreator([new Statement([Factory::class, 'createGeneric']), 'next']); $container = @createContainer($builder); // @return is deprecated -}, Nette\DI\ServiceCreationException::class, "Service 'a': Class 'T' not found. +}, Nette\DI\ServiceCreationException::class, "[Service 'a'] +Class 'T' not found. Check the return type of Factory::createGeneric()."); Assert::exception(function () { @@ -205,4 +213,5 @@ Assert::exception(function () { $builder->addDefinition('a') ->setCreator([new Statement([Factory::class, 'createUnion']), 'next']); $container = createContainer($builder); -}, Nette\DI\ServiceCreationException::class, "Service 'a': Return type of Factory::createUnion() is expected to not be built-in/complex, 'stdClass|array' given."); +}, Nette\DI\ServiceCreationException::class, "[Service 'a'] +Return type of Factory::createUnion() is expected to not be built-in/complex, 'stdClass|array' given."); diff --git a/tests/DI/ContainerBuilder.resolveTypes.phpt b/tests/DI/ContainerBuilder.resolveTypes.phpt index 67cb3ea47..29b8b32d7 100644 --- a/tests/DI/ContainerBuilder.resolveTypes.phpt +++ b/tests/DI/ContainerBuilder.resolveTypes.phpt @@ -139,56 +139,64 @@ Assert::exception(function () { $builder->addDefinition('a') ->setCreator([Factory::class, 'createScalarPhpDoc']); $container = @createContainer($builder); // @return is deprecated -}, Nette\DI\ServiceCreationException::class, "Service 'a': Return type of Factory::createScalarPhpDoc() is expected to not be built-in/complex, 'array' given."); +}, Nette\DI\ServiceCreationException::class, "[Service 'a'] +Return type of Factory::createScalarPhpDoc() is expected to not be built-in/complex, 'array' given."); Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') ->setCreator([Factory::class, 'createScalar']); $container = createContainer($builder); -}, Nette\DI\ServiceCreationException::class, "Service 'a': Return type of Factory::createScalar() is expected to not be built-in/complex, 'array' given."); +}, Nette\DI\ServiceCreationException::class, "[Service 'a'] +Return type of Factory::createScalar() is expected to not be built-in/complex, 'array' given."); Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') ->setCreator([Factory::class, 'createObjectPhpDoc']); $container = @createContainer($builder); // @return is deprecated -}, Nette\DI\ServiceCreationException::class, "Service 'a': Unknown service type, specify it or declare return type of factory method."); +}, Nette\DI\ServiceCreationException::class, "[Service 'a'] +Unknown service type, specify it or declare return type of factory method."); Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') ->setCreator([Factory::class, 'createObject']); $container = createContainer($builder); -}, Nette\DI\ServiceCreationException::class, "Service 'a': Unknown service type, specify it or declare return type of factory method."); +}, Nette\DI\ServiceCreationException::class, "[Service 'a'] +Unknown service type, specify it or declare return type of factory method."); Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') ->setCreator([Factory::class, 'createObjectNullable']); $container = createContainer($builder); -}, Nette\DI\ServiceCreationException::class, "Service 'a': Unknown service type, specify it or declare return type of factory method."); +}, Nette\DI\ServiceCreationException::class, "[Service 'a'] +Unknown service type, specify it or declare return type of factory method."); Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') ->setCreator([Factory::class, 'createMixedPhpDoc']); $container = @createContainer($builder); // @return is deprecated -}, Nette\DI\ServiceCreationException::class, "Service 'a': Unknown service type, specify it or declare return type of factory method."); +}, Nette\DI\ServiceCreationException::class, "[Service 'a'] +Unknown service type, specify it or declare return type of factory method."); Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') ->setCreator([Factory::class, 'createMixed']); $container = createContainer($builder); -}, Nette\DI\ServiceCreationException::class, "Service 'a': Unknown service type, specify it or declare return type of factory method."); +}, Nette\DI\ServiceCreationException::class, "[Service 'a'] +Unknown service type, specify it or declare return type of factory method."); Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') ->setCreator([Factory::class, 'createGeneric']); $container = @createContainer($builder); // @return is deprecated -}, Nette\DI\ServiceCreationException::class, "Service 'a': Class 'T' not found. +}, Nette\DI\ServiceCreationException::class, "[Service 'a'] +Class 'T' not found. Check the return type of Factory::createGeneric()."); Assert::exception(function () { @@ -196,4 +204,5 @@ Assert::exception(function () { $builder->addDefinition('a') ->setCreator([Factory::class, 'createUnion']); $container = createContainer($builder); -}, Nette\DI\ServiceCreationException::class, "Service 'a': Return type of Factory::createUnion() is expected to not be built-in/complex, 'stdClass|array' given."); +}, Nette\DI\ServiceCreationException::class, "[Service 'a'] +Return type of Factory::createUnion() is expected to not be built-in/complex, 'stdClass|array' given."); diff --git a/tests/DI/ContainerBuilder.selfdependency.phpt b/tests/DI/ContainerBuilder.selfdependency.phpt index be8c5b77b..360871d0f 100644 --- a/tests/DI/ContainerBuilder.selfdependency.phpt +++ b/tests/DI/ContainerBuilder.selfdependency.phpt @@ -28,5 +28,7 @@ $builder->addDefinition(null) Assert::exception( fn() => createContainer($builder), Nette\DI\ServiceCreationException::class, - 'Service of type Foo: Service of type Foo required by $foo in Foo::__construct() not found. Did you add it to configuration file?', + '[Service of type Foo] +Service of type Foo required by $foo in Foo::__construct() not found. +Did you add it to configuration file?', ); diff --git a/tests/DI/Definitions.AccessorDefinition.api.phpt b/tests/DI/Definitions.AccessorDefinition.api.phpt index adb04bebf..23c3ec302 100644 --- a/tests/DI/Definitions.AccessorDefinition.api.phpt +++ b/tests/DI/Definitions.AccessorDefinition.api.phpt @@ -54,49 +54,57 @@ interface Good1 Assert::exception(function () { $def = new AccessorDefinition; $def->setImplement('Foo'); -}, Nette\InvalidArgumentException::class, "Service '': Interface 'Foo' not found."); +}, Nette\InvalidArgumentException::class, "[Service ?] +Interface 'Foo' not found."); Assert::exception(function () { $def = new AccessorDefinition; $def->setImplement(stdClass::class); -}, Nette\InvalidArgumentException::class, "Service '': Interface 'stdClass' not found."); +}, Nette\InvalidArgumentException::class, "[Service ?] +Interface 'stdClass' not found."); Assert::exception(function () { $def = new AccessorDefinition; $def->setImplement(Bad1::class); -}, Nette\InvalidArgumentException::class, "Service '': Interface Bad1 must have just one non-static method get()."); +}, Nette\InvalidArgumentException::class, '[Service ?] +Interface Bad1 must have just one non-static method get().'); Assert::exception(function () { $def = new AccessorDefinition; $def->setImplement(Bad2::class); -}, Nette\InvalidArgumentException::class, "Service '': Interface Bad2 must have just one non-static method get()."); +}, Nette\InvalidArgumentException::class, '[Service ?] +Interface Bad2 must have just one non-static method get().'); Assert::exception(function () { $def = new AccessorDefinition; $def->setImplement(Bad3::class); -}, Nette\InvalidArgumentException::class, "Service '': Interface Bad3 must have just one non-static method get()."); +}, Nette\InvalidArgumentException::class, '[Service ?] +Interface Bad3 must have just one non-static method get().'); Assert::exception(function () { $def = new AccessorDefinition; $def->setImplement(Bad4::class); -}, Nette\InvalidArgumentException::class, "Service '': Interface Bad4 must have just one non-static method get()."); +}, Nette\InvalidArgumentException::class, '[Service ?] +Interface Bad4 must have just one non-static method get().'); Assert::exception(function () { $def = new AccessorDefinition; $def->setImplement(Bad5::class); -}, Nette\InvalidArgumentException::class, "Service '': Method Bad5::get() must have no parameters."); +}, Nette\InvalidArgumentException::class, '[Service ?] +Method Bad5::get() must have no parameters.'); Assert::exception(function () { $def = new AccessorDefinition; $def->setImplement(Bad6::class); -}, Nette\DI\ServiceCreationException::class, 'Return type of Bad6::get() is not declared.'); +}, Nette\DI\ServiceCreationException::class, '[Service ?] +Return type of Bad6::get() is not declared.'); Assert::noError(function () { diff --git a/tests/DI/Definitions.AccessorDefinition.resolve.phpt b/tests/DI/Definitions.AccessorDefinition.resolve.phpt index 014e2caf0..d03e48290 100644 --- a/tests/DI/Definitions.AccessorDefinition.resolve.phpt +++ b/tests/DI/Definitions.AccessorDefinition.resolve.phpt @@ -28,13 +28,15 @@ Assert::exception(function () { $def = new AccessorDefinition; $resolver = new Nette\DI\Resolver(new Nette\DI\ContainerBuilder); $resolver->resolveDefinition($def); -}, Nette\DI\ServiceCreationException::class, 'Type of service is unknown.'); +}, Nette\DI\ServiceCreationException::class, '[Service ?] +Type of service is unknown.'); Assert::exception(function () { $def = new AccessorDefinition; $def->setImplement(Bad1::class); -}, Nette\DI\ServiceCreationException::class, 'Return type of Bad1::get() is not declared.'); +}, Nette\DI\ServiceCreationException::class, '[Service ?] +Return type of Bad1::get() is not declared.'); Assert::noError(function () { @@ -63,4 +65,5 @@ Assert::exception(function () { $resolver = new Nette\DI\Resolver(new Nette\DI\ContainerBuilder); $resolver->resolveDefinition($def); $resolver->completeDefinition($def); -}, Nette\DI\ServiceCreationException::class, 'Service of type Good1: Service of type stdClass not found. Did you add it to configuration file?'); +}, Nette\DI\ServiceCreationException::class, '[Service of type Good1] +Service of type stdClass not found. Did you add it to configuration file?'); diff --git a/tests/DI/Definitions.FactoryDefinition.api.phpt b/tests/DI/Definitions.FactoryDefinition.api.phpt index 787435a5c..6a7a6c370 100644 --- a/tests/DI/Definitions.FactoryDefinition.api.phpt +++ b/tests/DI/Definitions.FactoryDefinition.api.phpt @@ -48,43 +48,50 @@ interface Good1 Assert::exception(function () { $def = new FactoryDefinition; $def->setImplement('Foo'); -}, Nette\InvalidArgumentException::class, "Service '': Interface 'Foo' not found."); +}, Nette\InvalidArgumentException::class, "[Service ?] +Interface 'Foo' not found."); Assert::exception(function () { $def = new FactoryDefinition; $def->setImplement(stdClass::class); -}, Nette\InvalidArgumentException::class, "Service '': Interface 'stdClass' not found."); +}, Nette\InvalidArgumentException::class, "[Service ?] +Interface 'stdClass' not found."); Assert::exception(function () { $def = new FactoryDefinition; $def->setImplement(Bad1::class); -}, Nette\InvalidArgumentException::class, "Service '': Interface Bad1 must have just one non-static method create()."); +}, Nette\InvalidArgumentException::class, '[Service ?] +Interface Bad1 must have just one non-static method create().'); Assert::exception(function () { $def = new FactoryDefinition; $def->setImplement(Bad2::class); -}, Nette\InvalidArgumentException::class, "Service '': Interface Bad2 must have just one non-static method create()."); +}, Nette\InvalidArgumentException::class, '[Service ?] +Interface Bad2 must have just one non-static method create().'); Assert::exception(function () { $def = new FactoryDefinition; $def->setImplement(Bad3::class); -}, Nette\InvalidArgumentException::class, "Service '': Interface Bad3 must have just one non-static method create()."); +}, Nette\InvalidArgumentException::class, '[Service ?] +Interface Bad3 must have just one non-static method create().'); Assert::exception(function () { $def = new FactoryDefinition; $def->setImplement(Bad4::class); -}, Nette\InvalidArgumentException::class, "Service '': Interface Bad4 must have just one non-static method create()."); +}, Nette\InvalidArgumentException::class, '[Service ?] +Interface Bad4 must have just one non-static method create().'); Assert::exception(function () { $def = new FactoryDefinition; $def->setImplement(Bad5::class); -}, Nette\DI\ServiceCreationException::class, 'Return type of Bad5::create() is not declared.'); +}, Nette\DI\ServiceCreationException::class, '[Service ?] +Return type of Bad5::create() is not declared.'); Assert::noError(function () { diff --git a/tests/DI/Definitions.FactoryDefinition.resolve.phpt b/tests/DI/Definitions.FactoryDefinition.resolve.phpt index 97f280234..79410b22a 100644 --- a/tests/DI/Definitions.FactoryDefinition.resolve.phpt +++ b/tests/DI/Definitions.FactoryDefinition.resolve.phpt @@ -28,13 +28,15 @@ Assert::exception(function () { $def = new FactoryDefinition; $resolver = new Nette\DI\Resolver(new Nette\DI\ContainerBuilder); $resolver->resolveDefinition($def); -}, Nette\DI\ServiceCreationException::class, 'Type is missing in definition of service.'); +}, Nette\DI\ServiceCreationException::class, '[Service ?] +Type is missing in definition of service.'); Assert::exception(function () { $def = new FactoryDefinition; $def->setImplement(Bad1::class); -}, Nette\DI\ServiceCreationException::class, 'Return type of Bad1::create() is not declared.'); +}, Nette\DI\ServiceCreationException::class, '[Service ?] +Return type of Bad1::create() is not declared.'); Assert::noError(function () { @@ -75,4 +77,5 @@ Assert::exception(function () { $resolver = new Nette\DI\Resolver(new Nette\DI\ContainerBuilder); $resolver->resolveDefinition($def); -}, Nette\DI\ServiceCreationException::class, 'Service of type Good1: Factory for stdClass cannot create incompatible DateTime type.'); +}, Nette\DI\ServiceCreationException::class, '[Service of type Good1] +Factory for stdClass cannot create incompatible DateTime type.'); diff --git a/tests/DI/Definitions.ImportedDefinition.phpt b/tests/DI/Definitions.ImportedDefinition.phpt index c5dd8fc1f..6b5d6c122 100644 --- a/tests/DI/Definitions.ImportedDefinition.phpt +++ b/tests/DI/Definitions.ImportedDefinition.phpt @@ -16,14 +16,16 @@ require __DIR__ . '/../bootstrap.php'; testException('Unknown class', function () { $def = new ImportedDefinition; $def->setType('Foo'); -}, Nette\InvalidArgumentException::class, "Service '': Class or interface 'Foo' not found."); +}, Nette\InvalidArgumentException::class, "[Service ?] +Class or interface 'Foo' not found."); testException('Unknown type', function () { $def = new ImportedDefinition; $resolver = new Nette\DI\Resolver(new Nette\DI\ContainerBuilder); $resolver->resolveDefinition($def); -}, Nette\DI\ServiceCreationException::class, 'Type of service is unknown.'); +}, Nette\DI\ServiceCreationException::class, '[Service ?] +Type of service is unknown.'); test('', function () { diff --git a/tests/DI/Definitions.LocatorDefinition.api.phpt b/tests/DI/Definitions.LocatorDefinition.api.phpt index 50a771bd0..2136fa2b6 100644 --- a/tests/DI/Definitions.LocatorDefinition.api.phpt +++ b/tests/DI/Definitions.LocatorDefinition.api.phpt @@ -66,49 +66,57 @@ interface Good3 Assert::exception(function () { $def = new LocatorDefinition; $def->setImplement('Foo'); -}, Nette\InvalidArgumentException::class, "Service '': Interface 'Foo' not found."); +}, Nette\InvalidArgumentException::class, "[Service ?] +Interface 'Foo' not found."); Assert::exception(function () { $def = new LocatorDefinition; $def->setImplement(stdClass::class); -}, Nette\InvalidArgumentException::class, "Service '': Interface 'stdClass' not found."); +}, Nette\InvalidArgumentException::class, "[Service ?] +Interface 'stdClass' not found."); Assert::exception(function () { $def = new LocatorDefinition; $def->setImplement(Bad1::class); -}, Nette\InvalidArgumentException::class, "Service '': Interface Bad1 must have at least one method."); +}, Nette\InvalidArgumentException::class, '[Service ?] +Interface Bad1 must have at least one method.'); Assert::exception(function () { $def = new LocatorDefinition; $def->setImplement(Bad2::class); -}, Nette\InvalidArgumentException::class, "Service '': Method Bad2::create() does not meet the requirements: is create(\$name), get(\$name), create*() or get*() and is non-static."); +}, Nette\InvalidArgumentException::class, '[Service ?] +Method Bad2::create() does not meet the requirements: is create($name), get($name), create*() or get*() and is non-static.'); Assert::exception(function () { $def = new LocatorDefinition; $def->setImplement(Bad3::class); -}, Nette\InvalidArgumentException::class, "Service '': Method Bad3::get() does not meet the requirements: is create(\$name), get(\$name), create*() or get*() and is non-static."); +}, Nette\InvalidArgumentException::class, '[Service ?] +Method Bad3::get() does not meet the requirements: is create($name), get($name), create*() or get*() and is non-static.'); Assert::exception(function () { $def = new LocatorDefinition; $def->setImplement(Bad4::class); -}, Nette\InvalidArgumentException::class, "Service '': Method Bad4::foo() does not meet the requirements: is create(\$name), get(\$name), create*() or get*() and is non-static."); +}, Nette\InvalidArgumentException::class, '[Service ?] +Method Bad4::foo() does not meet the requirements: is create($name), get($name), create*() or get*() and is non-static.'); Assert::exception(function () { $def = new LocatorDefinition; $def->setImplement(Bad5::class); -}, Nette\InvalidArgumentException::class, "Service '': Method Bad5::get() does not meet the requirements: is create(\$name), get(\$name), create*() or get*() and is non-static."); +}, Nette\InvalidArgumentException::class, '[Service ?] +Method Bad5::get() does not meet the requirements: is create($name), get($name), create*() or get*() and is non-static.'); Assert::exception(function () { $def = new LocatorDefinition; $def->setImplement(Bad6::class); -}, Nette\InvalidArgumentException::class, "Service '': Method Bad6::get() does not meet the requirements: is create(\$name), get(\$name), create*() or get*() and is non-static."); +}, Nette\InvalidArgumentException::class, '[Service ?] +Method Bad6::get() does not meet the requirements: is create($name), get($name), create*() or get*() and is non-static.'); Assert::noError(function () { @@ -121,7 +129,7 @@ Assert::noError(function () { Assert::noError(function () { $def = new LocatorDefinition; - $def->setImplement(Good2::class); + @$def->setImplement(Good2::class); // create($name) is deprecated Assert::same(Good2::class, $def->getImplement()); Assert::same(Good2::class, $def->getType()); }); diff --git a/tests/DI/Definitions.LocatorDefinition.resolve.phpt b/tests/DI/Definitions.LocatorDefinition.resolve.phpt index 4d1383ed0..7696e71bd 100644 --- a/tests/DI/Definitions.LocatorDefinition.resolve.phpt +++ b/tests/DI/Definitions.LocatorDefinition.resolve.phpt @@ -33,7 +33,8 @@ testException('', function () { $def = new LocatorDefinition; $resolver = new Nette\DI\Resolver(new Nette\DI\ContainerBuilder); $resolver->resolveDefinition($def); -}, Nette\DI\ServiceCreationException::class, 'Type of service is unknown.'); +}, Nette\DI\ServiceCreationException::class, '[Service ?] +Type of service is unknown.'); test('', function () { diff --git a/tests/DI/Definitions.ServiceDefinition.phpt b/tests/DI/Definitions.ServiceDefinition.phpt index c433a6473..2a413dcb9 100644 --- a/tests/DI/Definitions.ServiceDefinition.phpt +++ b/tests/DI/Definitions.ServiceDefinition.phpt @@ -17,7 +17,8 @@ require __DIR__ . '/../bootstrap.php'; testException('Unknown type', function () { $def = new ServiceDefinition; $def->setType('Foo'); -}, Nette\InvalidArgumentException::class, "Service '': Class or interface 'Foo' not found."); +}, Nette\InvalidArgumentException::class, "[Service ?] +Class or interface 'Foo' not found."); test('type versus entity I.', function () { $def = new ServiceDefinition; diff --git a/tests/DI/InjectExtension.errors.phpt b/tests/DI/InjectExtension.errors.phpt index e2dc16c88..2d1d94172 100644 --- a/tests/DI/InjectExtension.errors.phpt +++ b/tests/DI/InjectExtension.errors.phpt @@ -58,7 +58,9 @@ services: create: ServiceA inject: yes '); -}, InvalidStateException::class, 'Service of type DateTimeImmutable required by ServiceA::$a not found. Did you add it to configuration file?'); +}, InvalidStateException::class, "[Service 'service' of type ServiceA] +Service of type DateTimeImmutable required by ServiceA::\$a not found. +Did you add it to configuration file?"); Assert::exception(function () { @@ -72,6 +74,9 @@ services: '); }, InvalidStateException::class, "Class 'Unknown' not found. Check the type of property ServiceB::\$a."); +// }, InvalidStateException::class, "[Service 'service' of type ServiceB] +// Class 'Unknown' required by ServiceB::\$a not found. +// Check the property type and 'use' statements."); Assert::exception(function () { @@ -84,6 +89,8 @@ services: inject: yes '); }, InvalidStateException::class, 'Type of property ServiceC::$a is not declared.'); +//}, InvalidStateException::class, "[Service 'service' of type ServiceC] +//Property ServiceC::\$a has no type."); Assert::exception(function () { diff --git a/tests/DI/Resolver.autowireArguments.errors.phpt b/tests/DI/Resolver.autowireArguments.errors.phpt index 700a48bfb..1cf867667 100644 --- a/tests/DI/Resolver.autowireArguments.errors.phpt +++ b/tests/DI/Resolver.autowireArguments.errors.phpt @@ -21,7 +21,8 @@ Assert::exception( function () {}, ), Nette\DI\ServiceCreationException::class, - 'Service of type stdClass required by $x in {closure}() not found. Did you add it to configuration file?', + 'Service of type stdClass required by $x in {closure}() not found. +Did you add it to configuration file?', ); @@ -33,7 +34,8 @@ Assert::exception( function () {}, ), Nette\DI\ServiceCreationException::class, - "Class 'Foo' required by \$x in {closure}() not found. Check the parameter type and 'use' statements.", + "Class 'Foo' required by \$x in {closure}() not found. +Check the parameter type and 'use' statements.", ); @@ -69,7 +71,8 @@ Assert::exception( fn($type) => $type === Test::class ? new Test : null, ), Nette\DI\ServiceCreationException::class, - 'Service of type stdClass required by $arg in {closure}() not found. Did you add it to configuration file?', + 'Service of type stdClass required by $arg in {closure}() not found. +Did you add it to configuration file?', ); From 766e9df541a6079c4a61debe041f9b268030ee3d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 2 Dec 2024 00:21:11 +0100 Subject: [PATCH 14/22] annotations @return are no longer supported (BC break) --- src/DI/Definitions/Statement.php | 18 +--- src/DI/Helpers.php | 14 ---- .../ContainerBuilder.resolveTypes.next.phpt | 82 ------------------- tests/DI/ContainerBuilder.resolveTypes.phpt | 82 ------------------- tests/DI/Helpers.getReturnType.phpt | 30 ------- tests/DI/fixtures/Helpers.getReturnType.php | 49 ----------- 6 files changed, 4 insertions(+), 271 deletions(-) delete mode 100644 tests/DI/Helpers.getReturnType.phpt delete mode 100644 tests/DI/fixtures/Helpers.getReturnType.php diff --git a/src/DI/Definitions/Statement.php b/src/DI/Definitions/Statement.php index 3f3c6d7cd..8d3286059 100644 --- a/src/DI/Definitions/Statement.php +++ b/src/DI/Definitions/Statement.php @@ -100,20 +100,10 @@ public function resolveType(Resolver $resolver): ?string $resolver->addDependency($reflection); - $type = Nette\Utils\Type::fromReflection($reflection) ?? ($annotation = DI\Helpers::getReturnTypeAnnotation($reflection)); - if ($type && !in_array($type->getSingleName(), ['object', 'mixed'], strict: true)) { - if (isset($annotation)) { - trigger_error('Annotation @return should be replaced with native return type at ' . Callback::toString($entity), E_USER_DEPRECATED); - } - - return DI\Helpers::ensureClassType( - $type, - sprintf('return type of %s()', Callback::toString($entity)), - allowNullable: true, - ); - } - - return null; + $type = Nette\Utils\Type::fromReflection($reflection); + return $type && !in_array($type->getSingleName(), ['object', 'mixed'], strict: true) + ? DI\Helpers::ensureClassType($type, sprintf('return type of %s()', Callback::toString($entity)), allowNullable: true) + : null; } elseif ($entity instanceof Expression) { return $entity->resolveType($resolver); diff --git a/src/DI/Helpers.php b/src/DI/Helpers.php index cf257f8ea..f29995382 100644 --- a/src/DI/Helpers.php +++ b/src/DI/Helpers.php @@ -218,20 +218,6 @@ public static function parseAnnotation(\Reflector $ref, string $name): ?string } - public static function getReturnTypeAnnotation(\ReflectionFunctionAbstract $func): ?Type - { - $type = preg_replace('#[|\s].*#', '', (string) self::parseAnnotation($func, 'return')); - if (!$type || $type === 'object' || $type === 'mixed') { - return null; - } elseif ($func instanceof \ReflectionMethod) { - $type = $type === '$this' ? 'static' : $type; - $type = Reflection::expandClassName($type, $func->getDeclaringClass()); - } - - return Type::fromString($type); - } - - public static function ensureClassType( ?Type $type, string $hint, diff --git a/tests/DI/ContainerBuilder.resolveTypes.next.phpt b/tests/DI/ContainerBuilder.resolveTypes.next.phpt index 8ce3c0761..7f078690c 100644 --- a/tests/DI/ContainerBuilder.resolveTypes.next.phpt +++ b/tests/DI/ContainerBuilder.resolveTypes.next.phpt @@ -24,52 +24,24 @@ class Lorem class Factory { - /** @return Lorem */ - public function createClassPhpDoc() - { - return []; - } - - public function createClass(): Lorem { return []; } - /** @return Lorem|null */ - public function createNullableClassPhpDoc() - { - return []; - } - - public function createNullableClass(): ?Lorem { return []; } - /** @return array */ - public function createScalarPhpDoc() - { - return []; - } - - public function createScalar(): array { return []; } - /** @return object */ - public function createObjectPhpDoc() - { - return (object) null; - } - - public function createObject(): object { return (object) null; @@ -82,13 +54,6 @@ class Factory } - /** @return mixed */ - public function createMixedPhpDoc() - { - return (object) null; - } - - public function createMixed(): mixed { return (object) null; @@ -115,13 +80,6 @@ class Factory require __DIR__ . '/../bootstrap.php'; -Assert::noError(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([new Statement([Factory::class, 'createClassPhpDoc']), 'next']); - $container = @createContainer($builder); // @return is deprecated -}); - Assert::noError(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') @@ -129,13 +87,6 @@ Assert::noError(function () { $container = createContainer($builder); }); -Assert::noError(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([new Statement([Factory::class, 'createNullableClassPhpDoc']), 'next']); - $container = @createContainer($builder); // @return is deprecated -}); - Assert::noError(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') @@ -143,14 +94,6 @@ Assert::noError(function () { $container = createContainer($builder); }); -Assert::exception(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([new Statement([Factory::class, 'createScalarPhpDoc']), 'next']); - $container = @createContainer($builder); // @return is deprecated -}, Nette\DI\ServiceCreationException::class, "[Service 'a'] -Return type of Factory::createScalarPhpDoc() is expected to not be built-in/complex, 'array' given."); - Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') @@ -159,14 +102,6 @@ Assert::exception(function () { }, Nette\DI\ServiceCreationException::class, "[Service 'a'] Return type of Factory::createScalar() is expected to not be built-in/complex, 'array' given."); -Assert::exception(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([new Statement([Factory::class, 'createObjectPhpDoc']), 'next']); - $container = createContainer($builder); -}, Nette\DI\ServiceCreationException::class, "[Service 'a'] -Unknown service type, specify it or declare return type of factory method."); - Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') @@ -183,14 +118,6 @@ Assert::exception(function () { }, Nette\DI\ServiceCreationException::class, "[Service 'a'] Unknown service type, specify it or declare return type of factory method."); -Assert::exception(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([new Statement([Factory::class, 'createMixedPhpDoc']), 'next']); - $container = createContainer($builder); -}, Nette\DI\ServiceCreationException::class, "[Service 'a'] -Unknown service type, specify it or declare return type of factory method."); - Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') @@ -199,15 +126,6 @@ Assert::exception(function () { }, Nette\DI\ServiceCreationException::class, "[Service 'a'] Unknown service type, specify it or declare return type of factory method."); -Assert::exception(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([new Statement([Factory::class, 'createGeneric']), 'next']); - $container = @createContainer($builder); // @return is deprecated -}, Nette\DI\ServiceCreationException::class, "[Service 'a'] -Class 'T' not found. -Check the return type of Factory::createGeneric()."); - Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') diff --git a/tests/DI/ContainerBuilder.resolveTypes.phpt b/tests/DI/ContainerBuilder.resolveTypes.phpt index 29b8b32d7..09c639bf5 100644 --- a/tests/DI/ContainerBuilder.resolveTypes.phpt +++ b/tests/DI/ContainerBuilder.resolveTypes.phpt @@ -15,52 +15,24 @@ use Tester\Assert; class Factory { - /** @return stdClass */ - public function createClassPhpDoc() - { - return []; - } - - public function createClass(): stdClass { return []; } - /** @return stdClass|null */ - public function createNullableClassPhpDoc() - { - return []; - } - - public function createNullableClass(): ?stdClass { return []; } - /** @return array */ - public function createScalarPhpDoc() - { - return []; - } - - public function createScalar(): array { return []; } - /** @return object */ - public function createObjectPhpDoc() - { - return (object) null; - } - - public function createObject(): object { return (object) null; @@ -73,13 +45,6 @@ class Factory } - /** @return mixed */ - public function createMixedPhpDoc() - { - return (object) null; - } - - public function createMixed(): mixed { return (object) null; @@ -106,13 +71,6 @@ class Factory require __DIR__ . '/../bootstrap.php'; -Assert::noError(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([Factory::class, 'createClassPhpDoc']); - $container = @createContainer($builder); // @return is deprecated -}); - Assert::noError(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') @@ -120,13 +78,6 @@ Assert::noError(function () { $container = createContainer($builder); }); -Assert::noError(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([Factory::class, 'createNullableClassPhpDoc']); - $container = @createContainer($builder); // @return is deprecated -}); - Assert::noError(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') @@ -134,14 +85,6 @@ Assert::noError(function () { $container = createContainer($builder); }); -Assert::exception(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([Factory::class, 'createScalarPhpDoc']); - $container = @createContainer($builder); // @return is deprecated -}, Nette\DI\ServiceCreationException::class, "[Service 'a'] -Return type of Factory::createScalarPhpDoc() is expected to not be built-in/complex, 'array' given."); - Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') @@ -150,14 +93,6 @@ Assert::exception(function () { }, Nette\DI\ServiceCreationException::class, "[Service 'a'] Return type of Factory::createScalar() is expected to not be built-in/complex, 'array' given."); -Assert::exception(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([Factory::class, 'createObjectPhpDoc']); - $container = @createContainer($builder); // @return is deprecated -}, Nette\DI\ServiceCreationException::class, "[Service 'a'] -Unknown service type, specify it or declare return type of factory method."); - Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') @@ -174,14 +109,6 @@ Assert::exception(function () { }, Nette\DI\ServiceCreationException::class, "[Service 'a'] Unknown service type, specify it or declare return type of factory method."); -Assert::exception(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([Factory::class, 'createMixedPhpDoc']); - $container = @createContainer($builder); // @return is deprecated -}, Nette\DI\ServiceCreationException::class, "[Service 'a'] -Unknown service type, specify it or declare return type of factory method."); - Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') @@ -190,15 +117,6 @@ Assert::exception(function () { }, Nette\DI\ServiceCreationException::class, "[Service 'a'] Unknown service type, specify it or declare return type of factory method."); -Assert::exception(function () { - $builder = new DI\ContainerBuilder; - $builder->addDefinition('a') - ->setCreator([Factory::class, 'createGeneric']); - $container = @createContainer($builder); // @return is deprecated -}, Nette\DI\ServiceCreationException::class, "[Service 'a'] -Class 'T' not found. -Check the return type of Factory::createGeneric()."); - Assert::exception(function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('a') diff --git a/tests/DI/Helpers.getReturnType.phpt b/tests/DI/Helpers.getReturnType.phpt deleted file mode 100644 index 9a0a47cbf..000000000 --- a/tests/DI/Helpers.getReturnType.phpt +++ /dev/null @@ -1,30 +0,0 @@ - Date: Thu, 14 Dec 2023 13:48:45 +0100 Subject: [PATCH 15/22] annotations @var are no longer supported (BC break) --- src/DI/Extensions/InjectExtension.php | 14 +-- tests/DI/Container.inject.properties.phpt | 16 +-- tests/DI/InjectExtension.basic.phpt | 12 +- tests/DI/InjectExtension.errors.phpt | 8 +- ...Extension.getInjectProperties().php74.phpt | 39 ------- ...Extension.getInjectProperties().php80.phpt | 38 ------- ...InjectExtension.getInjectProperties().phpt | 104 ++++++------------ ...xtension.getInjectProperties().traits.phpt | 4 +- 8 files changed, 54 insertions(+), 181 deletions(-) delete mode 100644 tests/DI/InjectExtension.getInjectProperties().php74.phpt delete mode 100644 tests/DI/InjectExtension.getInjectProperties().php80.phpt diff --git a/src/DI/Extensions/InjectExtension.php b/src/DI/Extensions/InjectExtension.php index c84c7ecc7..8ee8f55de 100644 --- a/src/DI/Extensions/InjectExtension.php +++ b/src/DI/Extensions/InjectExtension.php @@ -117,19 +117,15 @@ public static function getInjectProperties(string $class): array { $res = []; foreach ((new \ReflectionClass($class))->getProperties() as $rp) { - $hasAttr = $rp->getAttributes(DI\Attributes\Inject::class); - if ($hasAttr || DI\Helpers::parseAnnotation($rp, 'inject') !== null) { + if ( + $rp->getAttributes(DI\Attributes\Inject::class) + || DI\Helpers::parseAnnotation($rp, 'inject') !== null + ) { if (!$rp->isPublic() || $rp->isStatic() || $rp->isReadOnly()) { throw new Nette\InvalidStateException(sprintf('Property %s for injection must not be static, readonly and must be public.', Reflection::toString($rp))); } - $type = Nette\Utils\Type::fromReflection($rp); - if (!$type && !$hasAttr && ($annotation = DI\Helpers::parseAnnotation($rp, 'var'))) { - $annotation = Reflection::expandClassName($annotation, Reflection::getPropertyDeclaringClass($rp)); - $type = Nette\Utils\Type::fromString($annotation); - } - - $res[$rp->getName()] = DI\Helpers::ensureClassType($type, 'type of property ' . Reflection::toString($rp)); + $res[$rp->getName()] = DI\Helpers::ensureClassType(Nette\Utils\Type::fromReflection($rp), 'type of property ' . Reflection::toString($rp)); } } diff --git a/tests/DI/Container.inject.properties.phpt b/tests/DI/Container.inject.properties.phpt index b633c0f8b..8b0203539 100644 --- a/tests/DI/Container.inject.properties.phpt +++ b/tests/DI/Container.inject.properties.phpt @@ -23,20 +23,17 @@ class Foo implements IFoo class Test1 { - /** @inject @var stdClass */ - public $varA; - - /** @var stdClass @inject */ - public $varB; + /** @inject */ + public stdClass $varA; } class Test2 extends Test1 { - /** @var stdClass @inject */ - public $varC; + /** @inject */ + public stdClass $varC; - /** @var IFoo @inject */ - public $varD; + /** @inject */ + public IFoo $varD; } @@ -52,6 +49,5 @@ $container = createContainer($builder); $test = new Test2; $container->callInjects($test); Assert::type(stdClass::class, $test->varA); -Assert::type(stdClass::class, $test->varB); Assert::type(stdClass::class, $test->varC); Assert::type(Foo::class, $test->varD); diff --git a/tests/DI/InjectExtension.basic.phpt b/tests/DI/InjectExtension.basic.phpt index 1b6d3fb52..8cb99fc8b 100644 --- a/tests/DI/InjectExtension.basic.phpt +++ b/tests/DI/InjectExtension.basic.phpt @@ -31,8 +31,8 @@ class ConcreteDependencyB extends AbstractDependency class ParentClass { - /** @var stdClass @inject */ - public $a; + /** @inject */ + public stdClass $a; public function injectA() @@ -47,11 +47,11 @@ class ParentClass class Service extends ParentClass { - /** @var stdClass @inject */ - public $c; + /** @inject */ + public stdClass $c; - /** @var AbstractDependency @inject */ - public $e; + /** @inject */ + public AbstractDependency $e; public function injectC() diff --git a/tests/DI/InjectExtension.errors.phpt b/tests/DI/InjectExtension.errors.phpt index 2d1d94172..0bd382707 100644 --- a/tests/DI/InjectExtension.errors.phpt +++ b/tests/DI/InjectExtension.errors.phpt @@ -16,15 +16,15 @@ require __DIR__ . '/../bootstrap.php'; class ServiceA { - /** @var DateTimeImmutable @inject */ - public $a; + /** @inject */ + public DateTimeImmutable $a; } class ServiceB { - /** @var Unknown @inject */ - public $a; + /** @inject */ + public Unknown $a; } diff --git a/tests/DI/InjectExtension.getInjectProperties().php74.phpt b/tests/DI/InjectExtension.getInjectProperties().php74.phpt deleted file mode 100644 index 33a5cb95e..000000000 --- a/tests/DI/InjectExtension.getInjectProperties().php74.phpt +++ /dev/null @@ -1,39 +0,0 @@ - A\AInjected::class, - 'varC' => A\AInjected::class, - ], InjectExtension::getInjectProperties(A\AClass::class)); -} diff --git a/tests/DI/InjectExtension.getInjectProperties().php80.phpt b/tests/DI/InjectExtension.getInjectProperties().php80.phpt deleted file mode 100644 index e24155f65..000000000 --- a/tests/DI/InjectExtension.getInjectProperties().php80.phpt +++ /dev/null @@ -1,38 +0,0 @@ - InjectExtension::getInjectProperties(AClass::class), - Nette\InvalidStateException::class, - "Type of property AClass::\$var is expected to not be nullable/built-in/complex, 'AClass|stdClass' given.", -); - -Assert::same([ - 'varA' => 'stdClass', -], InjectExtension::getInjectProperties(EClass::class)); diff --git a/tests/DI/InjectExtension.getInjectProperties().phpt b/tests/DI/InjectExtension.getInjectProperties().phpt index 79c00b86e..65bf65a04 100644 --- a/tests/DI/InjectExtension.getInjectProperties().phpt +++ b/tests/DI/InjectExtension.getInjectProperties().phpt @@ -6,93 +6,51 @@ declare(strict_types=1); -namespace A -{ - class AClass - { - /** @var AInjected @inject */ - public $varA; +use Nette\DI\Attributes\Inject; +use Nette\DI\Extensions\InjectExtension; +use Tester\Assert; + - /** @var B\BInjected @inject */ - public $varB; +class AClass +{ + /** @inject */ + public AInjected $varA; - /** @var AInjected @inject */ - public $varC; + /** @inject */ + public BInjected $varB; - /** @var AInjected */ - public $varD; - } + public $varD; - class AInjected - { - } + #[Inject] + public stdClass $varF; } -namespace A\B +class BadClass { - use A; - - class BClass extends A\AClass - { - /** @var BInjected @inject */ - public $varF; - } - - class BInjected - { - } + /** @inject */ + public AClass|stdClass $var; } -namespace C +class AInjected { - use A\AInjected; - use A\B; - use C\CInjected as CAlias; - - class CClass - { - /** @var AInjected @inject */ - public $var1; - - /** @var B\BInjected @inject */ - public $var2; - - /** @var CAlias @inject */ - public $var3; - - /** @var CInjected @inject */ - public $var4; - } - - class CInjected - { - } } -namespace { - use Nette\DI\Extensions\InjectExtension; - use Tester\Assert; +class BInjected +{ +} - require __DIR__ . '/../bootstrap.php'; +require __DIR__ . '/../bootstrap.php'; - Assert::same([ - 'varA' => A\AInjected::class, - 'varB' => A\B\BInjected::class, - 'varC' => A\AInjected::class, - ], InjectExtension::getInjectProperties(A\AClass::class)); - Assert::same([ - 'varA' => A\AInjected::class, - 'varB' => A\B\BInjected::class, - 'varC' => A\AInjected::class, - 'varF' => A\B\BInjected::class, - ], InjectExtension::getInjectProperties(A\B\BClass::class)); +Assert::same([ + 'varA' => AInjected::class, + 'varB' => BInjected::class, + 'varF' => stdClass::class, +], InjectExtension::getInjectProperties(AClass::class)); - Assert::same([ - 'var1' => A\AInjected::class, - 'var2' => A\B\BInjected::class, - 'var3' => C\CInjected::class, - 'var4' => C\CInjected::class, - ], InjectExtension::getInjectProperties(C\CClass::class)); -} +Assert::exception( + fn() => InjectExtension::getInjectProperties(BadClass::class), + Nette\InvalidStateException::class, + "Type of property BadClass::\$var is expected to not be nullable/built-in/complex, 'AClass|stdClass' given.", +); diff --git a/tests/DI/InjectExtension.getInjectProperties().traits.phpt b/tests/DI/InjectExtension.getInjectProperties().traits.phpt index 1fc6c211e..42b625067 100644 --- a/tests/DI/InjectExtension.getInjectProperties().traits.phpt +++ b/tests/DI/InjectExtension.getInjectProperties().traits.phpt @@ -19,8 +19,8 @@ namespace B trait BTrait { - /** @var AInjected @inject */ - public $varA; + /** @inject */ + public AInjected $varA; } } From d9068543e74a331ccedf40a41586dd74c3ede092 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 1 Dec 2024 22:15:47 +0100 Subject: [PATCH 16/22] removed Definition::generateMethod() (BC break) --- src/DI/Definitions/Definition.php | 9 +-------- src/DI/Definitions/FactoryDefinition.php | 3 +-- src/DI/PhpGenerator.php | 2 +- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/DI/Definitions/Definition.php b/src/DI/Definitions/Definition.php index 31651174c..bf0e9f418 100644 --- a/src/DI/Definitions/Definition.php +++ b/src/DI/Definitions/Definition.php @@ -170,7 +170,7 @@ abstract public function resolveType(Nette\DI\Resolver $resolver): void; abstract public function complete(Nette\DI\Resolver $resolver): void; - //abstract public function generateCode(Nette\DI\PhpGenerator $generator): string; + abstract public function generateCode(Nette\DI\PhpGenerator $generator): string; final public function setNotifier(?\Closure $notifier): void @@ -182,13 +182,6 @@ final public function setNotifier(?\Closure $notifier): void /********************* deprecated stuff from former ServiceDefinition ****************d*g**/ - /** @deprecated */ - public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void - { - $method->setBody($this->generateCode($generator)); - } - - /** @deprecated Use setType() */ public function setClass(?string $type) { diff --git a/src/DI/Definitions/FactoryDefinition.php b/src/DI/Definitions/FactoryDefinition.php index a79b77e95..6e5c19eb0 100644 --- a/src/DI/Definitions/FactoryDefinition.php +++ b/src/DI/Definitions/FactoryDefinition.php @@ -208,8 +208,7 @@ public function generateCode(Nette\DI\PhpGenerator $generator): string ->setType($generator->getClassName()); $methodCreate = $class->addMethod(self::MethodCreate); - $this->resultDefinition->generateMethod($methodCreate, $generator); - $body = $methodCreate->getBody(); + $body = $this->resultDefinition->generateCode($generator); $body = str_replace('$this', '$this->container', $body); $body = str_replace('$this->container->container', '$this->container', $body); diff --git a/src/DI/PhpGenerator.php b/src/DI/PhpGenerator.php index ea5563982..62f5366e7 100644 --- a/src/DI/PhpGenerator.php +++ b/src/DI/PhpGenerator.php @@ -92,7 +92,7 @@ public function generateMethod(Definitions\Definition $def): Php\Method $method = new Php\Method(Container::getMethodName($name)); $method->setPublic(); $method->setReturnType($def->getType()); - $def->generateMethod($method, $this); + $method->setBody($def->generateCode($this)); return $method; } catch (\Throwable $e) { From 722be3701213f1558a18db044e79446e03ae2d18 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 11 Dec 2023 17:12:33 +0100 Subject: [PATCH 17/22] removed support for three ... dots --- src/DI/Config/Adapters/NeonAdapter.php | 9 --------- tests/DI/NeonAdapter.preprocess.phpt | 11 ----------- 2 files changed, 20 deletions(-) diff --git a/src/DI/Config/Adapters/NeonAdapter.php b/src/DI/Config/Adapters/NeonAdapter.php index fa08c27a7..92487ba5a 100644 --- a/src/DI/Config/Adapters/NeonAdapter.php +++ b/src/DI/Config/Adapters/NeonAdapter.php @@ -216,15 +216,6 @@ private function removeUnderscoreVisitor(Node $node): void if ($attr->value instanceof Node\LiteralNode && $attr->value->value === '_') { unset($node->attributes[$i]); $index = true; - - } elseif ( - $attr->value instanceof Node\LiteralNode - && $attr->value->value === '...' - && !$this->isFirstClassCallable($node) - ) { - trigger_error("Replace ... with _ in configuration file '$this->file'.", E_USER_DEPRECATED); - unset($node->attributes[$i]); - $index = true; } } } diff --git a/tests/DI/NeonAdapter.preprocess.phpt b/tests/DI/NeonAdapter.preprocess.phpt index 7b59be016..350dbd259 100644 --- a/tests/DI/NeonAdapter.preprocess.phpt +++ b/tests/DI/NeonAdapter.preprocess.phpt @@ -70,17 +70,6 @@ Assert::equal( ); -// ... deprecated -$data = @$adapter->load(Tester\FileMock::create(' -- Class(arg1, ..., [...]) -', 'neon')); - -Assert::equal( - [new Statement('Class', ['arg1', 2 => ['...']])], - $data, -); - - // @ escaping $data = @$adapter->load(Tester\FileMock::create(' - @@double From abd0a99a054591c2c35faa31287118864dc805eb Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 20 Dec 2022 00:31:03 +0100 Subject: [PATCH 18/22] removed compatibility for old class names --- src/DI/Config/Adapter.php | 3 -- src/DI/Definitions/ServiceDefinition.php | 3 -- src/DI/Definitions/Statement.php | 3 -- src/compatibility.php | 39 ------------------------ 4 files changed, 48 deletions(-) delete mode 100644 src/compatibility.php diff --git a/src/DI/Config/Adapter.php b/src/DI/Config/Adapter.php index 25e9cecd8..a9b7004b6 100644 --- a/src/DI/Config/Adapter.php +++ b/src/DI/Config/Adapter.php @@ -20,6 +20,3 @@ interface Adapter */ function load(string $file): array; } - - -class_exists(IAdapter::class); diff --git a/src/DI/Definitions/ServiceDefinition.php b/src/DI/Definitions/ServiceDefinition.php index 7cbdce179..2bbb50775 100644 --- a/src/DI/Definitions/ServiceDefinition.php +++ b/src/DI/Definitions/ServiceDefinition.php @@ -236,6 +236,3 @@ public function __clone() $this->setup = unserialize(serialize($this->setup)); } } - - -class_exists(Nette\DI\ServiceDefinition::class); diff --git a/src/DI/Definitions/Statement.php b/src/DI/Definitions/Statement.php index 8d3286059..bd6de0ab8 100644 --- a/src/DI/Definitions/Statement.php +++ b/src/DI/Definitions/Statement.php @@ -372,6 +372,3 @@ public function generateCode(DI\PhpGenerator $generator): string throw new Nette\InvalidStateException; } } - - -class_exists(Nette\DI\Statement::class); diff --git a/src/compatibility.php b/src/compatibility.php deleted file mode 100644 index f0a0e53fe..000000000 --- a/src/compatibility.php +++ /dev/null @@ -1,39 +0,0 @@ - Date: Fri, 24 Sep 2021 14:07:27 +0200 Subject: [PATCH 19/22] deprecated magic properties (BC break) --- src/DI/Definitions/ServiceDefinition.php | 6 +++--- src/DI/Definitions/Statement.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/DI/Definitions/ServiceDefinition.php b/src/DI/Definitions/ServiceDefinition.php index 2bbb50775..5f7a1a98e 100644 --- a/src/DI/Definitions/ServiceDefinition.php +++ b/src/DI/Definitions/ServiceDefinition.php @@ -17,9 +17,9 @@ /** * Definition of standard service. * - * @property string|null $class - * @property Statement $factory - * @property Statement[] $setup + * @property-deprecated string|null $class + * @property-deprecated Statement $factory + * @property-deprecated Statement[] $setup */ final class ServiceDefinition extends Definition { diff --git a/src/DI/Definitions/Statement.php b/src/DI/Definitions/Statement.php index bd6de0ab8..d2b9a0a6d 100644 --- a/src/DI/Definitions/Statement.php +++ b/src/DI/Definitions/Statement.php @@ -21,7 +21,7 @@ /** * Assignment or calling statement. * - * @property string|array|Definition|Reference|null $entity + * @property-deprecated string|array|Definition|Reference|null $entity */ final class Statement extends Expression implements Nette\Schema\DynamicParameter { From 6e75d6454d3b685620c19244abd32e1111b51e39 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 6 Apr 2024 21:02:45 +0200 Subject: [PATCH 20/22] annotations @inject is deprecated (BC break) --- src/DI/Extensions/InjectExtension.php | 3 +++ tests/DI/Container.inject.properties.phpt | 7 ++++--- tests/DI/InjectExtension.basic.phpt | 7 ++++--- tests/DI/InjectExtension.errors.phpt | 11 ++++++----- tests/DI/InjectExtension.getInjectProperties().phpt | 6 +++--- .../InjectExtension.getInjectProperties().traits.phpt | 3 ++- 6 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/DI/Extensions/InjectExtension.php b/src/DI/Extensions/InjectExtension.php index 8ee8f55de..f72e35a03 100644 --- a/src/DI/Extensions/InjectExtension.php +++ b/src/DI/Extensions/InjectExtension.php @@ -121,6 +121,9 @@ public static function getInjectProperties(string $class): array $rp->getAttributes(DI\Attributes\Inject::class) || DI\Helpers::parseAnnotation($rp, 'inject') !== null ) { + if (!$rp->getAttributes(DI\Attributes\Inject::class)) { + trigger_error('Annotation @inject is deprecated, use #[Nette\\DI\\Attributes\\Inject] (used in ' . Reflection::toString($rp) . ')', E_USER_DEPRECATED); + } if (!$rp->isPublic() || $rp->isStatic() || $rp->isReadOnly()) { throw new Nette\InvalidStateException(sprintf('Property %s for injection must not be static, readonly and must be public.', Reflection::toString($rp))); } diff --git a/tests/DI/Container.inject.properties.phpt b/tests/DI/Container.inject.properties.phpt index 8b0203539..73324665c 100644 --- a/tests/DI/Container.inject.properties.phpt +++ b/tests/DI/Container.inject.properties.phpt @@ -7,6 +7,7 @@ declare(strict_types=1); use Nette\DI; +use Nette\DI\Attributes\Inject; use Tester\Assert; @@ -23,16 +24,16 @@ class Foo implements IFoo class Test1 { - /** @inject */ + #[Inject] public stdClass $varA; } class Test2 extends Test1 { - /** @inject */ + #[Inject] public stdClass $varC; - /** @inject */ + #[Inject] public IFoo $varD; } diff --git a/tests/DI/InjectExtension.basic.phpt b/tests/DI/InjectExtension.basic.phpt index 8cb99fc8b..2ae5b8eff 100644 --- a/tests/DI/InjectExtension.basic.phpt +++ b/tests/DI/InjectExtension.basic.phpt @@ -7,6 +7,7 @@ declare(strict_types=1); use Nette\DI; +use Nette\DI\Attributes\Inject; use Nette\DI\Definitions\Reference; use Nette\DI\Definitions\Statement; use Tester\Assert; @@ -31,7 +32,7 @@ class ConcreteDependencyB extends AbstractDependency class ParentClass { - /** @inject */ + #[Inject] public stdClass $a; @@ -47,10 +48,10 @@ class ParentClass class Service extends ParentClass { - /** @inject */ + #[Inject] public stdClass $c; - /** @inject */ + #[Inject] public AbstractDependency $e; diff --git a/tests/DI/InjectExtension.errors.phpt b/tests/DI/InjectExtension.errors.phpt index 0bd382707..8d9e6cdb4 100644 --- a/tests/DI/InjectExtension.errors.phpt +++ b/tests/DI/InjectExtension.errors.phpt @@ -7,6 +7,7 @@ declare(strict_types=1); use Nette\DI; +use Nette\DI\Attributes\Inject; use Nette\InvalidStateException; use Tester\Assert; @@ -16,35 +17,35 @@ require __DIR__ . '/../bootstrap.php'; class ServiceA { - /** @inject */ + #[Inject] public DateTimeImmutable $a; } class ServiceB { - /** @inject */ + #[Inject] public Unknown $a; } class ServiceC { - /** @inject */ + #[Inject] public $a; } class ServiceD { - /** @inject */ + #[Inject] protected $a; } class ServiceE { - /** @inject */ + #[Inject] public static $a; } diff --git a/tests/DI/InjectExtension.getInjectProperties().phpt b/tests/DI/InjectExtension.getInjectProperties().phpt index 65bf65a04..c7c997a1b 100644 --- a/tests/DI/InjectExtension.getInjectProperties().phpt +++ b/tests/DI/InjectExtension.getInjectProperties().phpt @@ -13,10 +13,10 @@ use Tester\Assert; class AClass { - /** @inject */ + #[Inject] public AInjected $varA; - /** @inject */ + #[Inject] public BInjected $varB; public $varD; @@ -27,7 +27,7 @@ class AClass class BadClass { - /** @inject */ + #[Inject] public AClass|stdClass $var; } diff --git a/tests/DI/InjectExtension.getInjectProperties().traits.phpt b/tests/DI/InjectExtension.getInjectProperties().traits.phpt index 42b625067..ba4022d49 100644 --- a/tests/DI/InjectExtension.getInjectProperties().traits.phpt +++ b/tests/DI/InjectExtension.getInjectProperties().traits.phpt @@ -16,10 +16,11 @@ namespace A namespace B { use A\AInjected; + use Nette\DI\Attributes\Inject; trait BTrait { - /** @inject */ + #[Inject] public AInjected $varA; } } From 59cf6994c71ac077bb7848beb80031edf29dfef4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 29 Nov 2024 19:25:33 +0100 Subject: [PATCH 21/22] used attribute Deprecated --- src/DI/ContainerBuilder.php | 4 ++-- src/DI/Definitions/Reference.php | 2 +- src/DI/DependencyChecker.php | 2 +- src/DI/Extensions/InjectExtension.php | 4 ++-- src/DI/Helpers.php | 4 +--- tests/DI/Helpers.parseAnnotation().phpt | 22 +++++++++------------- 6 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/DI/ContainerBuilder.php b/src/DI/ContainerBuilder.php index 089ef301a..d8aff9737 100644 --- a/src/DI/ContainerBuilder.php +++ b/src/DI/ContainerBuilder.php @@ -22,10 +22,10 @@ class ContainerBuilder ThisService = 'self', ThisContainer = 'container'; - /** @deprecated use ContainerBuilder::ThisService */ + #[\Deprecated('use ContainerBuilder::ThisService')] public const THIS_SERVICE = self::ThisService; - /** @deprecated use ContainerBuilder::ThisContainer */ + #[\Deprecated('use ContainerBuilder::ThisContainer')] public const THIS_CONTAINER = self::ThisContainer; public array $parameters = []; diff --git a/src/DI/Definitions/Reference.php b/src/DI/Definitions/Reference.php index ed6db4305..b10dd6745 100644 --- a/src/DI/Definitions/Reference.php +++ b/src/DI/Definitions/Reference.php @@ -20,7 +20,7 @@ final class Reference extends Expression { public const Self = 'self'; - /** @deprecated use Reference::Self */ + #[\Deprecated('use Reference::Self')] public const SELF = self::Self; private string $value; diff --git a/src/DI/DependencyChecker.php b/src/DI/DependencyChecker.php index 94eb9fdc8..cab489c63 100644 --- a/src/DI/DependencyChecker.php +++ b/src/DI/DependencyChecker.php @@ -22,7 +22,7 @@ class DependencyChecker { public const Version = 1; - /** @deprecated use DependencyChecker::Version */ + #[\Deprecated('use DependencyChecker::Version')] public const VERSION = self::Version; /** @var array */ diff --git a/src/DI/Extensions/InjectExtension.php b/src/DI/Extensions/InjectExtension.php index f72e35a03..3d32dece4 100644 --- a/src/DI/Extensions/InjectExtension.php +++ b/src/DI/Extensions/InjectExtension.php @@ -22,7 +22,7 @@ final class InjectExtension extends DI\CompilerExtension { public const TagInject = 'nette.inject'; - /** @deprecated use InjectExtension::TagInject */ + #[\Deprecated('use InjectExtension::TagInject')] public const TAG_INJECT = self::TagInject; @@ -119,7 +119,7 @@ public static function getInjectProperties(string $class): array foreach ((new \ReflectionClass($class))->getProperties() as $rp) { if ( $rp->getAttributes(DI\Attributes\Inject::class) - || DI\Helpers::parseAnnotation($rp, 'inject') !== null + || @DI\Helpers::parseAnnotation($rp, 'inject') !== null // @deprecated ) { if (!$rp->getAttributes(DI\Attributes\Inject::class)) { trigger_error('Annotation @inject is deprecated, use #[Nette\\DI\\Attributes\\Inject] (used in ' . Reflection::toString($rp) . ')', E_USER_DEPRECATED); diff --git a/src/DI/Helpers.php b/src/DI/Helpers.php index f29995382..a656d8a36 100644 --- a/src/DI/Helpers.php +++ b/src/DI/Helpers.php @@ -200,9 +200,7 @@ public static function prefixServiceName(mixed $config, string $namespace): mixe } - /** - * Returns an annotation value. - */ + #[\Deprecated] public static function parseAnnotation(\Reflector $ref, string $name): ?string { if (!Reflection::areCommentsAvailable()) { diff --git a/tests/DI/Helpers.parseAnnotation().phpt b/tests/DI/Helpers.parseAnnotation().phpt index 3406024bd..44115be43 100644 --- a/tests/DI/Helpers.parseAnnotation().phpt +++ b/tests/DI/Helpers.parseAnnotation().phpt @@ -1,9 +1,5 @@ Date: Fri, 11 Apr 2025 14:36:52 +0200 Subject: [PATCH 22/22] Enable to suppress Suspicious dumping error --- src/DI/Attributes/IgnoreDumping.php | 17 +++++++++++++++++ src/DI/PhpGenerator.php | 2 ++ 2 files changed, 19 insertions(+) create mode 100644 src/DI/Attributes/IgnoreDumping.php diff --git a/src/DI/Attributes/IgnoreDumping.php b/src/DI/Attributes/IgnoreDumping.php new file mode 100644 index 000000000..3f492dc78 --- /dev/null +++ b/src/DI/Attributes/IgnoreDumping.php @@ -0,0 +1,17 @@ +getAttributes(IgnoreDumping::class) && (new \ReflectionObject($val))->getProperties(\ReflectionProperty::IS_PRIVATE | \ReflectionProperty::IS_PROTECTED) ) { trigger_error(sprintf('Nette DI: suspicious dumping of objects %s when generating the container', $val::class));