From 158996d6bc9d20cb83c8fc3c3f51b60b61f145db Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 28 Apr 2024 13:23:23 +0200 Subject: [PATCH 01/56] test: fixes --- tests/DI/Container.dynamic.phpt | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/tests/DI/Container.dynamic.phpt b/tests/DI/Container.dynamic.phpt index 0a5e09ee0..567642176 100644 --- a/tests/DI/Container.dynamic.phpt +++ b/tests/DI/Container.dynamic.phpt @@ -18,9 +18,8 @@ class Service } -$container = new Container; - -test('basic', function () use ($container) { +test('basic', function () { + $container = new Container; $one = new Service; $two = new Service; $container->addService('one', $one); @@ -39,8 +38,9 @@ test('basic', function () use ($container) { }); -test('closure', function () use ($container) { - @$container->addService('four', fn() => new Service); +test('closure', function () { + $container = new Container; + $container->addService('four', fn() => new Service); Assert::true($container->hasService('four')); Assert::false($container->isCreated('four')); @@ -52,20 +52,23 @@ test('closure', function () use ($container) { }); -test('closure with typehint', function () use ($container) { - @$container->addService('five', fn(): Service => new Service); +test('closure with typehint', function () { + $container = new Container; + $container->addService('five', fn(): Service => new Service); Assert::same(Service::class, $container->getServiceType('five')); }); -testException('bad closure', function () use ($container) { - @$container->addService('six', function () {}); // @ triggers service should be defined as "imported" +testException('bad closure', function () { + $container = new Container; + $container->addService('six', function () {}); $container->getService('six'); }, Nette\UnexpectedValueException::class, "Unable to create service 'six', value returned by closure is not object."); -testException('union type', function () use ($container) { - @$container->addService('six', function (): stdClass|Closure {}); // @ triggers service should be defined as "imported" +testException('union type', function () { + $container = new Container; + $container->addService('six', function (): stdClass|Closure {}); $container->getService('six'); }, Nette\InvalidStateException::class, "Return type of closure is expected to not be nullable/built-in/complex, 'stdClass|Closure' given."); From e9f3ea895f00cc6db96ea321fb6c4bcb3f83a1b8 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 27 Apr 2024 17:44:00 +0200 Subject: [PATCH 02/56] improved phpDoc --- src/DI/Container.php | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/DI/Container.php b/src/DI/Container.php index 6189e424b..3d1462e55 100644 --- a/src/DI/Container.php +++ b/src/DI/Container.php @@ -23,7 +23,7 @@ class Container */ public $parameters = []; - /** @var string[] services name => type (complete list of available services) */ + /** @var string[] service name => type */ protected array $types = []; /** @var string[] alias => service name */ @@ -32,16 +32,16 @@ class Container /** @var array[] tag name => service name => tag value */ protected array $tags = []; - /** @var array[] type => level => services */ + /** @var array[] type => (high, low, no) => services */ protected array $wiring = []; /** @var object[] service name => instance */ private array $instances = []; - /** @var array circular reference detector */ + /** @var array circular reference detector */ private array $creating; - /** @var array */ + /** @var array */ private array $methods; @@ -83,7 +83,7 @@ protected function getDynamicParameter(string|int $key): mixed /** - * Adds the service to the container. + * Adds the service or its factory to the container. * @param object $service service or its factory */ public function addService(string $name, object $service): static @@ -124,7 +124,7 @@ public function addService(string $name, object $service): static /** - * Removes the service from the container. + * Removes a service instance from the container. */ public function removeService(string $name): void { @@ -134,7 +134,7 @@ public function removeService(string $name): void /** - * Gets the service object by name. + * Returns the service instance. If it has not been created yet, it creates it. * @throws MissingServiceException */ public function getService(string $name): object @@ -152,7 +152,8 @@ public function getService(string $name): object /** - * Gets the service object by name. + * Returns the service instance. If it has not been created yet, it creates it. + * Alias for getService(). * @throws MissingServiceException */ public function getByName(string $name): object @@ -162,7 +163,7 @@ public function getByName(string $name): object /** - * Gets the service type by name. + * Returns type of the service. * @throws MissingServiceException */ public function getServiceType(string $name): string @@ -194,7 +195,7 @@ public function hasService(string $name): bool /** - * Is the service created? + * Has a service instance been created? */ public function isCreated(string $name): bool { @@ -236,7 +237,7 @@ public function createService(string $name): object /** - * Resolves service by type. + * Returns an instance of the autowired service of the given type. If it has not been created yet, it creates it. * @template T of object * @param class-string $type * @return ($throw is true ? T : ?T) @@ -279,7 +280,7 @@ public function getByType(string $type, bool $throw = true): ?object /** - * Gets the autowired service names of the specified type. + * Returns the names of autowired services of the given type. * @return string[] * @internal */ @@ -291,7 +292,7 @@ public function findAutowired(string $type): array /** - * Gets the service names of the specified type. + * Returns the names of all services of the given type. * @return string[] */ public function findByType(string $type): array @@ -304,7 +305,7 @@ public function findByType(string $type): array /** - * Gets the service names of the specified tag. + * Returns the names of services with the given tag. * @return array of [service name => tag attributes] */ public function findByTag(string $tag): array @@ -313,6 +314,9 @@ public function findByTag(string $tag): array } + /** + * Prevents circular references during service creation by checking if the service is already being created. + */ private function preventDeadLock(string $key, \Closure $callback): mixed { if (isset($this->creating[$key])) { @@ -331,7 +335,7 @@ private function preventDeadLock(string $key, \Closure $callback): mixed /** - * Creates new instance using autowiring. + * Creates an instance of the class and passes dependencies to the constructor using autowiring. */ public function createInstance(string $class, array $args = []): object { @@ -351,7 +355,7 @@ public function createInstance(string $class, array $args = []): object /** - * Calls all methods starting with "inject" using autowiring. + * Calls all methods starting with 'inject' and passes dependencies to them via autowiring. */ public function callInjects(object $service): void { @@ -360,7 +364,7 @@ public function callInjects(object $service): void /** - * Calls method using autowiring. + * Calls the method and passes dependencies to it via autowiring. */ public function callMethod(callable $function, array $args = []): mixed { @@ -376,6 +380,9 @@ private function autowireArguments(\ReflectionFunctionAbstract $function, array } + /** + * Returns the method name for creating a service. + */ public static function getMethodName(string $name): string { if ($name === '') { From 1ed8edd203be787a75c4747d6a3c0e692057f5ee Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 28 Apr 2024 16:09:52 +0200 Subject: [PATCH 03/56] PhpGenerator: removed property's phpDoc --- src/DI/PhpGenerator.php | 4 +++- tests/DI/expected/compiler.code.php | 5 ----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/DI/PhpGenerator.php b/src/DI/PhpGenerator.php index 961f96d8f..e5ff43b60 100644 --- a/src/DI/PhpGenerator.php +++ b/src/DI/PhpGenerator.php @@ -42,7 +42,9 @@ public function generate(string $className): Php\ClassType ->addBody('parent::__construct($params);'); foreach ($this->builder->exportMeta() as $key => $value) { - $class->inheritProperty($key)->setValue($value); + $class->inheritProperty($key) + ->setComment(null) + ->setValue($value); } $definitions = $this->builder->getDefinitions(); diff --git a/tests/DI/expected/compiler.code.php b/tests/DI/expected/compiler.code.php index eb6cbee22..6f2d90b54 100644 --- a/tests/DI/expected/compiler.code.php +++ b/tests/DI/expected/compiler.code.php @@ -6,13 +6,8 @@ class Container extends Nette\DI\Container { - /** @var string[] services name => type (complete list of available services) */ protected array $types = ['container' => 'Nette\DI\Container']; - - /** @var string[] alias => service name */ protected array $aliases = []; - - /** @var array[] type => level => services */ protected array $wiring = ['Nette\DI\Container' => [['container']], 'stdClass' => [['01', 'name']]]; From fe3ed08235191728deeb3b9783d9252add45a4a7 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 27 Apr 2024 17:44:00 +0200 Subject: [PATCH 04/56] Container::getByType() fixed cooperation with dynamic factory [Closes #314] --- src/DI/Container.php | 19 ++++--------------- tests/DI/Container.dynamic.phpt | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/DI/Container.php b/src/DI/Container.php index 3d1462e55..20f99302a 100644 --- a/src/DI/Container.php +++ b/src/DI/Container.php @@ -257,22 +257,11 @@ public function getByType(string $type, bool $throw = true): ?object } elseif ($throw) { if (!class_exists($type) && !interface_exists($type)) { throw new MissingServiceException(sprintf("Service of type '%s' not found. Check the class name because it cannot be found.", $type)); + } elseif ($this->findByType($type)) { + throw new MissingServiceException(sprintf("Service of type %s is not autowired or is missing in di\u{a0}›\u{a0}export\u{a0}›\u{a0}types.", $type)); + } else { + throw new MissingServiceException(sprintf('Service of type %s not found. Did you add it to configuration file?', $type)); } - - foreach ($this->methods as $method => $foo) { - $methodType = (new \ReflectionMethod(static::class, $method))->getReturnType()->getName(); - if (is_a($methodType, $type, allow_string: true)) { - throw new MissingServiceException(sprintf( - "Service of type %s is not autowired or is missing in di\u{a0}›\u{a0}export\u{a0}›\u{a0}types.", - $type, - )); - } - } - - throw new MissingServiceException(sprintf( - 'Service of type %s not found. Did you add it to configuration file?', - $type, - )); } return null; diff --git a/tests/DI/Container.dynamic.phpt b/tests/DI/Container.dynamic.phpt index 567642176..f6f66ffad 100644 --- a/tests/DI/Container.dynamic.phpt +++ b/tests/DI/Container.dynamic.phpt @@ -60,6 +60,20 @@ test('closure with typehint', function () { }); +testException('getByType', function () { + $container = new Container; + $container->addService('one', fn() => new Service); + $container->getByType(Service::class); +}, Nette\DI\MissingServiceException::class, 'Service of type Service not found. Did you add it to configuration file?'); + + +testException('getByType with typehint', function () { + $container = new Container; + $container->addService('one', fn(): Service => new Service); + $container->getByType(Service::class); +}, Nette\DI\MissingServiceException::class, 'Service of type Service not found. Did you add it to configuration file?'); + + testException('bad closure', function () { $container = new Container; $container->addService('six', function () {}); From 1fa0bc4394c29219a310782da37af11d902b0242 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 27 Apr 2024 17:44:00 +0200 Subject: [PATCH 05/56] Container: $methods are not filtered For speed up --- src/Bridges/DITracy/ContainerPanel.php | 13 +++++++------ src/DI/Container.php | 7 ++----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Bridges/DITracy/ContainerPanel.php b/src/Bridges/DITracy/ContainerPanel.php index f8301b062..9a0f361d0 100644 --- a/src/Bridges/DITracy/ContainerPanel.php +++ b/src/Bridges/DITracy/ContainerPanel.php @@ -50,11 +50,13 @@ public function getTab(): string */ public function getPanel(): string { - $methods = (fn() => $this->methods)->bindTo($this->container, Container::class)(); + $rc = (new \ReflectionClass($this->container)); $services = []; - foreach ($methods as $name => $foo) { - $name = lcfirst(str_replace('__', '.', substr($name, 13))); - $services[$name] = $this->container->getServiceType($name); + foreach ($rc->getMethods() as $method) { + if (preg_match('#^createService.#', $method->getName())) { + $name = lcfirst(str_replace('__', '.', substr($method->getName(), 13))); + $services[$name] = $this->container->getServiceType($name); + } } ksort($services, SORT_NATURAL); @@ -66,9 +68,8 @@ public function getPanel(): string } } - return Nette\Utils\Helpers::capture(function () use ($tags, $services) { + return Nette\Utils\Helpers::capture(function () use ($rc, $tags, $services) { $container = $this->container; - $rc = (new \ReflectionClass($this->container)); $file = $rc->getFileName(); $instances = (fn() => $this->instances)->bindTo($this->container, Container::class)(); $wiring = (fn() => $this->wiring)->bindTo($this->container, $this->container)(); diff --git a/src/DI/Container.php b/src/DI/Container.php index 20f99302a..b726587ca 100644 --- a/src/DI/Container.php +++ b/src/DI/Container.php @@ -48,10 +48,7 @@ class Container public function __construct(array $params = []) { $this->parameters = $params + $this->getStaticParameters(); - $this->methods = array_flip(array_filter( - get_class_methods($this), - fn($s) => preg_match('#^createService.#', $s), - )); + $this->methods = array_flip(get_class_methods($this)); } @@ -372,7 +369,7 @@ private function autowireArguments(\ReflectionFunctionAbstract $function, array /** * Returns the method name for creating a service. */ - public static function getMethodName(string $name): string + final public static function getMethodName(string $name): string { if ($name === '') { throw new Nette\InvalidArgumentException('Service name must be a non-empty string.'); From ed53906d11c1b6184b6ff0c35edc8ad7ebe992c2 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 27 Apr 2024 17:44:00 +0200 Subject: [PATCH 06/56] Container: dynamic factories as stored in $factories --- src/DI/Container.php | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/DI/Container.php b/src/DI/Container.php index b726587ca..6d2a871ad 100644 --- a/src/DI/Container.php +++ b/src/DI/Container.php @@ -41,9 +41,12 @@ class Container /** @var array circular reference detector */ private array $creating; - /** @var array */ + /** @var array */ private array $methods; + /** @var array service name => \Closure */ + private array $factories = []; + public function __construct(array $params = []) { @@ -110,7 +113,7 @@ public function addService(string $name, object $service): static } if ($service instanceof \Closure) { - $this->methods[self::getMethodName($name)] = $service; + $this->factories[$name] = $service; $this->types[$name] = $type; } else { $this->instances[$name] = $service; @@ -175,6 +178,9 @@ public function getServiceType(string $name): string } elseif (isset($this->methods[$method])) { return (string) (new \ReflectionMethod($this, $method))->getReturnType(); + } elseif ($cb = $this->factories[$name] ?? null) { + return (string) (new \ReflectionFunction($cb))->getReturnType(); + } else { throw new MissingServiceException(sprintf("Service '%s' not found.", $name)); } @@ -187,7 +193,7 @@ public function getServiceType(string $name): string public function hasService(string $name): bool { $name = $this->aliases[$name] ?? $name; - return isset($this->methods[self::getMethodName($name)]) || isset($this->instances[$name]); + return isset($this->methods[self::getMethodName($name)]) || isset($this->instances[$name]) || isset($this->factories[$name]); } @@ -213,15 +219,14 @@ public function createService(string $name): object { $name = $this->aliases[$name] ?? $name; $method = self::getMethodName($name); - $callback = $this->methods[$method] ?? null; - if ($callback === null) { + if ($callback = ($this->factories[$name] ?? null)) { + $service = $this->preventDeadLock($name, fn() => $callback()); + } elseif (isset($this->methods[$method])) { + $service = $this->preventDeadLock($name, fn() => $this->$method()); + } else { throw new MissingServiceException(sprintf("Service '%s' not found.", $name)); } - $service = $this->preventDeadLock($name, fn() => $callback instanceof \Closure - ? $callback() - : $this->$method()); - if (!is_object($service)) { throw new Nette\UnexpectedValueException(sprintf( "Unable to create service '$name', value returned by %s is not object.", From 067cdf5f2ea8c6167a5015c6e95163d0ece45972 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 27 Apr 2024 17:44:00 +0200 Subject: [PATCH 07/56] Container::getServiceType() ignores dynamically added services (BC break) --- src/DI/Container.php | 11 +++++------ tests/DI/Container.dynamic.phpt | 10 +++++++--- tests/DI/Container.getServiceType.phpt | 2 +- tests/DI/Container.static-dynamic.phpt | 4 ++-- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/DI/Container.php b/src/DI/Container.php index 6d2a871ad..2a453094e 100644 --- a/src/DI/Container.php +++ b/src/DI/Container.php @@ -100,10 +100,10 @@ public function addService(string $name, object $service): static $type = $service::class; } - if (!isset($this->methods[self::getMethodName($name)])) { - $this->types[$name] = $type; - - } elseif (($expectedType = $this->getServiceType($name)) && !is_a($type, $expectedType, allow_string: true)) { + if (isset($this->methods[self::getMethodName($name)]) + && ($expectedType = $this->getServiceType($name)) + && !is_a($type, $expectedType, allow_string: true) + ) { throw new Nette\InvalidArgumentException(sprintf( "Service '%s' must be instance of %s, %s.", $name, @@ -114,7 +114,6 @@ public function addService(string $name, object $service): static if ($service instanceof \Closure) { $this->factories[$name] = $service; - $this->types[$name] = $type; } else { $this->instances[$name] = $service; } @@ -182,7 +181,7 @@ public function getServiceType(string $name): string return (string) (new \ReflectionFunction($cb))->getReturnType(); } else { - throw new MissingServiceException(sprintf("Service '%s' not found.", $name)); + throw new MissingServiceException(sprintf("Type of service '%s' not known.", $name)); } } diff --git a/tests/DI/Container.dynamic.phpt b/tests/DI/Container.dynamic.phpt index f6f66ffad..8f148ed5c 100644 --- a/tests/DI/Container.dynamic.phpt +++ b/tests/DI/Container.dynamic.phpt @@ -32,12 +32,16 @@ test('basic', function () { Assert::same($one, $container->getService('one')); Assert::same($two, $container->getService('two')); - - Assert::same(Service::class, $container->getServiceType('one')); - Assert::same(Service::class, $container->getServiceType('two')); }); +testException('type not known', function () { + $container = new Container; + $container->addService('one', new Service); + $container->getServiceType('one'); +}, Nette\DI\MissingServiceException::class, "Type of service 'one' not known."); + + test('closure', function () { $container = new Container; $container->addService('four', fn() => new Service); diff --git a/tests/DI/Container.getServiceType.phpt b/tests/DI/Container.getServiceType.phpt index d333516cc..8b1fe045b 100644 --- a/tests/DI/Container.getServiceType.phpt +++ b/tests/DI/Container.getServiceType.phpt @@ -40,5 +40,5 @@ Assert::same('One', $container->getServiceType('three')); Assert::exception( fn() => $container->getServiceType('four'), Nette\DI\MissingServiceException::class, - "Service 'four' not found.", + "Type of service 'four' not known.", ); diff --git a/tests/DI/Container.static-dynamic.phpt b/tests/DI/Container.static-dynamic.phpt index 42c05c71a..b12976ccc 100644 --- a/tests/DI/Container.static-dynamic.phpt +++ b/tests/DI/Container.static-dynamic.phpt @@ -60,7 +60,7 @@ test('closure & typehint', function () { $container->addService('one', fn(): stdClass => new stdClass); - Assert::same(stdClass::class, $container->getServiceType('one')); + Assert::same('', $container->getServiceType('one')); Assert::true($container->hasService('one')); Assert::type(stdClass::class, $container->getService('one')); }); @@ -75,7 +75,7 @@ test('closure & matching typehint', function () { $container->addService('typehint', fn(): MyClass => new MyClass); - Assert::same(MyClass::class, $container->getServiceType('typehint')); + Assert::same(stdClass::class, $container->getServiceType('typehint')); Assert::true($container->hasService('typehint')); Assert::type(MyClass::class, $container->getService('typehint')); }); From 79fd5be59c315098d60df388657761f3010a66c9 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 27 Apr 2024 17:44:00 +0200 Subject: [PATCH 08/56] Container: removed $types It was used only for imported services, which is now solved by typehint --- src/Bridges/DITracy/ContainerPanel.php | 2 +- src/DI/Container.php | 6 ------ src/DI/ContainerBuilder.php | 4 ---- src/DI/Definitions/ImportedDefinition.php | 9 ++++----- src/DI/PhpGenerator.php | 1 - tests/DI/ContainerBuilder.metadata.phpt | 5 ----- tests/DI/Definitions.ImportedDefinition.phpt | 2 +- tests/DI/expected/compiler.code.php | 3 +-- 8 files changed, 7 insertions(+), 25 deletions(-) diff --git a/src/Bridges/DITracy/ContainerPanel.php b/src/Bridges/DITracy/ContainerPanel.php index 9a0f361d0..82051f931 100644 --- a/src/Bridges/DITracy/ContainerPanel.php +++ b/src/Bridges/DITracy/ContainerPanel.php @@ -55,7 +55,7 @@ public function getPanel(): string foreach ($rc->getMethods() as $method) { if (preg_match('#^createService.#', $method->getName())) { $name = lcfirst(str_replace('__', '.', substr($method->getName(), 13))); - $services[$name] = $this->container->getServiceType($name); + $services[$name] = (string) $method->getReturnType(); } } ksort($services, SORT_NATURAL); diff --git a/src/DI/Container.php b/src/DI/Container.php index 2a453094e..d72639a63 100644 --- a/src/DI/Container.php +++ b/src/DI/Container.php @@ -23,9 +23,6 @@ class Container */ public $parameters = []; - /** @var string[] service name => type */ - protected array $types = []; - /** @var string[] alias => service name */ protected array $aliases = []; @@ -171,9 +168,6 @@ public function getServiceType(string $name): string if (isset($this->aliases[$name])) { return $this->getServiceType($this->aliases[$name]); - } elseif (isset($this->types[$name])) { - return $this->types[$name]; - } elseif (isset($this->methods[$method])) { return (string) (new \ReflectionMethod($this, $method))->getReturnType(); diff --git a/src/DI/ContainerBuilder.php b/src/DI/ContainerBuilder.php index 92c60c134..b70a2028e 100644 --- a/src/DI/ContainerBuilder.php +++ b/src/DI/ContainerBuilder.php @@ -353,10 +353,6 @@ public function exportMeta(): array $defs = $this->definitions; ksort($defs); foreach ($defs as $name => $def) { - if ($def instanceof Definitions\ImportedDefinition) { - $meta['types'][$name] = $def->getType(); - } - foreach ($def->getTags() as $tag => $value) { $meta['tags'][$tag][$name] = $value; } diff --git a/src/DI/Definitions/ImportedDefinition.php b/src/DI/Definitions/ImportedDefinition.php index eb97fbcbe..501e558cc 100644 --- a/src/DI/Definitions/ImportedDefinition.php +++ b/src/DI/Definitions/ImportedDefinition.php @@ -36,10 +36,9 @@ public function complete(Nette\DI\Resolver $resolver): void public function generateMethod(Nette\PhpGenerator\Method $method, PhpGenerator $generator): void { - $method->setReturnType('void') - ->setBody( - 'throw new Nette\\DI\\ServiceCreationException(?);', - ["Unable to create imported service '{$this->getName()}', it must be added using addService()"], - ); + $method->setBody( + 'throw new Nette\\DI\\ServiceCreationException(?);', + ["Unable to create imported service '{$this->getName()}', it must be added using addService()"], + ); } } diff --git a/src/DI/PhpGenerator.php b/src/DI/PhpGenerator.php index e5ff43b60..da0abf59a 100644 --- a/src/DI/PhpGenerator.php +++ b/src/DI/PhpGenerator.php @@ -55,7 +55,6 @@ public function generate(string $className): Php\ClassType } $class->getMethod(Container::getMethodName(ContainerBuilder::ThisContainer)) - ->setReturnType($className) ->setBody('return $this;'); $class->inheritMethod('initialize'); diff --git a/tests/DI/ContainerBuilder.metadata.phpt b/tests/DI/ContainerBuilder.metadata.phpt index 76f8be6bd..539a43858 100644 --- a/tests/DI/ContainerBuilder.metadata.phpt +++ b/tests/DI/ContainerBuilder.metadata.phpt @@ -41,11 +41,6 @@ Assert::same( getPropertyValue($container, 'wiring'), ); -Assert::same( - ['container' => Nette\DI\Container::class], - getPropertyValue($container, 'types'), -); - Assert::same( [ 'a' => ['lorem' => true], diff --git a/tests/DI/Definitions.ImportedDefinition.phpt b/tests/DI/Definitions.ImportedDefinition.phpt index 14b734c65..c5dd8fc1f 100644 --- a/tests/DI/Definitions.ImportedDefinition.phpt +++ b/tests/DI/Definitions.ImportedDefinition.phpt @@ -42,7 +42,7 @@ test('', function () { Assert::match( <<<'XX' - public function createServiceAbc(): void + public function createServiceAbc(): stdClass { throw new Nette\DI\ServiceCreationException('Unable to create imported service \'abc\', it must be added using addService()'); } diff --git a/tests/DI/expected/compiler.code.php b/tests/DI/expected/compiler.code.php index 6f2d90b54..c74c6d60f 100644 --- a/tests/DI/expected/compiler.code.php +++ b/tests/DI/expected/compiler.code.php @@ -6,7 +6,6 @@ class Container extends Nette\DI\Container { - protected array $types = ['container' => 'Nette\DI\Container']; protected array $aliases = []; protected array $wiring = ['Nette\DI\Container' => [['container']], 'stdClass' => [['01', 'name']]]; @@ -23,7 +22,7 @@ public function createService01(): stdClass } - public function createServiceContainer(): Container + public function createServiceContainer(): Nette\DI\Container { return $this; } From 1c247c0ad3a71a9df2d9e7866334afe8dec7619b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 28 Apr 2024 18:20:58 +0200 Subject: [PATCH 09/56] typos --- composer.json | 1 + src/DI/Config/Adapters/NeonAdapter.php | 8 ++++---- src/DI/ContainerBuilder.php | 3 +-- src/DI/ContainerLoader.php | 4 ++-- src/DI/DependencyChecker.php | 2 +- src/DI/Extensions/DecoratorExtension.php | 2 +- src/DI/Extensions/InjectExtension.php | 2 +- src/DI/Resolver.php | 4 ++-- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/composer.json b/composer.json index f4782e7b2..28498797b 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ "require": { "php": "8.1 - 8.3", "ext-tokenizer": "*", + "ext-ctype": "*", "nette/neon": "^3.3 || ^4.0", "nette/php-generator": "^4.1.3", "nette/robot-loader": "^4.0", diff --git a/src/DI/Config/Adapters/NeonAdapter.php b/src/DI/Config/Adapters/NeonAdapter.php index b71823b6f..d78a5e191 100644 --- a/src/DI/Config/Adapters/NeonAdapter.php +++ b/src/DI/Config/Adapters/NeonAdapter.php @@ -149,7 +149,7 @@ function (&$val): void { } - private function firstClassCallableVisitor(Neon\Node $node) + private function firstClassCallableVisitor(Neon\Node $node): void { if ($node instanceof Neon\Node\EntityNode && count($node->attributes) === 1 @@ -162,7 +162,7 @@ private function firstClassCallableVisitor(Neon\Node $node) } - private function removeUnderscoreVisitor(Neon\Node $node) + private function removeUnderscoreVisitor(Neon\Node $node): void { if (!$node instanceof Neon\Node\EntityNode) { return; @@ -189,7 +189,7 @@ private function removeUnderscoreVisitor(Neon\Node $node) } - private function convertAtSignVisitor(Neon\Node $node) + private function convertAtSignVisitor(Neon\Node $node): void { if ($node instanceof Neon\Node\StringNode) { if (str_starts_with($node->value, '@@')) { @@ -208,7 +208,7 @@ private function convertAtSignVisitor(Neon\Node $node) } - private function deprecatedParametersVisitor(Neon\Node $node) + private function deprecatedParametersVisitor(Neon\Node $node): void { if (($node instanceof Neon\Node\StringNode || $node instanceof Neon\Node\LiteralNode) && is_string($node->value) diff --git a/src/DI/ContainerBuilder.php b/src/DI/ContainerBuilder.php index b70a2028e..f2844d508 100644 --- a/src/DI/ContainerBuilder.php +++ b/src/DI/ContainerBuilder.php @@ -28,8 +28,7 @@ class ContainerBuilder /** @deprecated use ContainerBuilder::ThisContainer */ public const THIS_CONTAINER = self::ThisContainer; - /** @var array */ - public $parameters = []; + public array $parameters = []; /** @var Definition[] */ private array $definitions = []; diff --git a/src/DI/ContainerLoader.php b/src/DI/ContainerLoader.php index b1a2015a7..e6241a75b 100644 --- a/src/DI/ContainerLoader.php +++ b/src/DI/ContainerLoader.php @@ -18,8 +18,8 @@ class ContainerLoader { public function __construct( - private string $tempDirectory, - private bool $autoRebuild = false, + private readonly string $tempDirectory, + private readonly bool $autoRebuild = false, ) { } diff --git a/src/DI/DependencyChecker.php b/src/DI/DependencyChecker.php index 4b143af19..b9bcb305b 100644 --- a/src/DI/DependencyChecker.php +++ b/src/DI/DependencyChecker.php @@ -179,7 +179,7 @@ private static function hashParameters(\ReflectionFunctionAbstract $method): arr (string) $param->getType(), $param->isVariadic(), $param->isDefaultValueAvailable() - ? is_object($tmp = Reflection::getParameterDefaultValue($param)) ? ['object' => $tmp::class] : ['value' => $tmp] + ? is_object($tmp = $param->getDefaultValue()) ? ['object' => $tmp::class] : ['value' => $tmp] : null, ]; } diff --git a/src/DI/Extensions/DecoratorExtension.php b/src/DI/Extensions/DecoratorExtension.php index 7afa70339..e5a4cc129 100644 --- a/src/DI/Extensions/DecoratorExtension.php +++ b/src/DI/Extensions/DecoratorExtension.php @@ -30,7 +30,7 @@ public function getConfigSchema(): Nette\Schema\Schema } - public function beforeCompile() + public function beforeCompile(): void { $this->getContainerBuilder()->resolve(); foreach ($this->config as $type => $info) { diff --git a/src/DI/Extensions/InjectExtension.php b/src/DI/Extensions/InjectExtension.php index 91948615f..4c40b30c0 100644 --- a/src/DI/Extensions/InjectExtension.php +++ b/src/DI/Extensions/InjectExtension.php @@ -32,7 +32,7 @@ public function getConfigSchema(): Nette\Schema\Schema } - public function beforeCompile() + public function beforeCompile(): void { foreach ($this->getContainerBuilder()->getDefinitions() as $def) { if ($def->getTag(self::TagInject)) { diff --git a/src/DI/Resolver.php b/src/DI/Resolver.php index 5848d73fe..dd51c490c 100644 --- a/src/DI/Resolver.php +++ b/src/DI/Resolver.php @@ -518,7 +518,7 @@ private function convertReferences(array $arguments): array /** * Add missing arguments using autowiring. - * @param (callable(string $type, bool $single): (object|object[]|null)) $getter + * @param (callable(string, bool): (object|object[]|null)) $getter * @throws ServiceCreationException */ public static function autowireArguments( @@ -592,7 +592,7 @@ public static function autowireArguments( /** * Resolves missing argument using autowiring. - * @param (callable(string $type, bool $single): (object|object[]|null)) $getter + * @param (callable(string, bool): (object|object[]|null)) $getter * @throws ServiceCreationException */ private static function autowireArgument(\ReflectionParameter $parameter, callable $getter): mixed From a22fd3a171c7fcf3106ea589df2de5cde602b679 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 29 Apr 2024 00:13:51 +0200 Subject: [PATCH 10/56] DefinitionSchema: 'create' can be Reference --- src/DI/Extensions/DefinitionSchema.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DI/Extensions/DefinitionSchema.php b/src/DI/Extensions/DefinitionSchema.php index 94e566134..a49ab3faa 100644 --- a/src/DI/Extensions/DefinitionSchema.php +++ b/src/DI/Extensions/DefinitionSchema.php @@ -165,7 +165,7 @@ private static function getServiceSchema(): Schema { return Expect::structure([ 'type' => Expect::type('string'), - 'create' => Expect::type('callable|Nette\DI\Definitions\Statement'), + 'create' => Expect::type('callable|Nette\DI\Definitions\Statement|Nette\DI\Definitions\Reference'), 'arguments' => Expect::array(), 'setup' => Expect::listOf('callable|Nette\DI\Definitions\Statement|array:1'), 'inject' => Expect::bool(), From 2a2dc5ab0296655fcb8ecade70eaf61a52742fb3 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 29 Apr 2024 00:20:49 +0200 Subject: [PATCH 11/56] NeonAdapter: resolving of constants and enums moved here from filterArguments() (BC break) --- src/DI/Config/Adapters/NeonAdapter.php | 22 ++++++++++++++++++++++ src/DI/Helpers.php | 10 +--------- tests/DI/Compiler.parameters.phpt | 2 +- tests/DI/DecoratorExtension.basic.phpt | 6 +++--- tests/DI/Helpers.filterArguments.phpt | 10 ++-------- tests/DI/NeonAdapter.preprocess.phpt | 21 +++++++++++++++++++++ 6 files changed, 50 insertions(+), 21 deletions(-) diff --git a/src/DI/Config/Adapters/NeonAdapter.php b/src/DI/Config/Adapters/NeonAdapter.php index d78a5e191..d806cd319 100644 --- a/src/DI/Config/Adapters/NeonAdapter.php +++ b/src/DI/Config/Adapters/NeonAdapter.php @@ -43,6 +43,7 @@ public function load(string $file): array $node = $traverser->traverse($node, $this->removeUnderscoreVisitor(...)); $node = $traverser->traverse($node, $this->convertAtSignVisitor(...)); $node = $traverser->traverse($node, $this->deprecatedParametersVisitor(...)); + $node = $traverser->traverse($node, $this->resolveConstants(...)); return $this->process((array) $node->toValue()); } @@ -217,4 +218,25 @@ private function deprecatedParametersVisitor(Neon\Node $node): void trigger_error('%parameters% is deprecated, use @container::getParameters() (in ' . $this->file . ')', E_USER_DEPRECATED); } } + + + private function resolveConstants(Neon\Node $node): void + { + $items = match (true) { + $node instanceof Neon\Node\ArrayNode => $node->items, + $node instanceof Neon\Node\EntityNode => $node->attributes, + default => null, + }; + if ($items) { + foreach ($items as $item) { + if ($item->value instanceof Neon\Node\LiteralNode + && is_string($item->value->value) + && preg_match('#^([\w\\\\]*)::[A-Z]\w+$#D', $item->value->value) + && defined(ltrim($item->value->value, ':')) + ) { + $item->value->value = constant(ltrim($item->value->value, ':')); + } + } + } + } } diff --git a/src/DI/Helpers.php b/src/DI/Helpers.php index c662e2afe..610239b8d 100644 --- a/src/DI/Helpers.php +++ b/src/DI/Helpers.php @@ -158,15 +158,7 @@ public static function escape(mixed $value): mixed public static function filterArguments(array $args): array { foreach ($args as $k => $v) { - if ( - is_string($v) - && preg_match('#^([\w\\\\]+)::\w+$#D', $v, $m) - && enum_exists($m[1]) - ) { - $args[$k] = new Nette\PhpGenerator\Literal($v); - } elseif (is_string($v) && preg_match('#^[\w\\\\]*::[A-Z][a-zA-Z0-9_]*$#D', $v)) { - $args[$k] = new Nette\PhpGenerator\Literal(ltrim($v, ':')); - } elseif (is_string($v) && preg_match('#^@[\w\\\\]+$#D', $v)) { + if (is_string($v) && preg_match('#^@[\w\\\\]+$#D', $v)) { $args[$k] = new Reference(substr($v, 1)); } elseif (is_array($v)) { $args[$k] = self::filterArguments($v); diff --git a/tests/DI/Compiler.parameters.phpt b/tests/DI/Compiler.parameters.phpt index e971ee4d6..3df08a3dd 100644 --- a/tests/DI/Compiler.parameters.phpt +++ b/tests/DI/Compiler.parameters.phpt @@ -108,7 +108,7 @@ test('NOT class constant as parameter', function () { one: Service(%bar%) '); - Assert::same(['bar' => 'Service::Name'], $container->getParameters()); // not resolved + Assert::same(['bar' => 'hello'], $container->getParameters()); Assert::same('hello', $container->getService('one')->arg); }); diff --git a/tests/DI/DecoratorExtension.basic.phpt b/tests/DI/DecoratorExtension.basic.phpt index 8c6d8cad4..067fd4c5e 100644 --- a/tests/DI/DecoratorExtension.basic.phpt +++ b/tests/DI/DecoratorExtension.basic.phpt @@ -17,7 +17,7 @@ require __DIR__ . '/../bootstrap.php'; interface Iface { - public const Name = self::class; + public const Name = 'hello'; } @@ -77,7 +77,7 @@ services: $builder = $compiler->getContainerBuilder(); Assert::same( - ['a' => true, 'tag' => 2, DI\Extensions\InjectExtension::TagInject => true, 'Iface::Name' => true], + ['a' => true, 'tag' => 2, DI\Extensions\InjectExtension::TagInject => true, 'hello' => true], $builder->getDefinition('one')->getTags(), ); @@ -86,7 +86,7 @@ Assert::true($builder->getDefinition('one')->getTag(DI\Extensions\InjectExtensio Assert::equal([ new Statement([new Reference('self'), 'setup'], ['Service']), new Statement([new Reference('self'), 'setup'], ['Object']), - new Statement([new Reference('self'), 'setup'], [new Nette\PhpGenerator\Literal('Iface::Name')]), + new Statement([new Reference('self'), 'setup'], ['hello']), new Statement([new Reference('self'), 'setup']), new Statement([new Reference('self'), '$a'], [10]), ], $builder->getDefinition('one')->getSetup()); diff --git a/tests/DI/Helpers.filterArguments.phpt b/tests/DI/Helpers.filterArguments.phpt index 38224efa9..c91559b8b 100644 --- a/tests/DI/Helpers.filterArguments.phpt +++ b/tests/DI/Helpers.filterArguments.phpt @@ -6,7 +6,6 @@ declare(strict_types=1); -use Nette\DI\ContainerBuilder; use Nette\DI\Definitions\Statement; use Nette\DI\Helpers; use Tester\Assert; @@ -17,17 +16,12 @@ require __DIR__ . '/../bootstrap.php'; Assert::same([], Helpers::filterArguments([])); -Assert::equal( - ['a', 'b', ContainerBuilder::literal('Nette\DI\ContainerBuilder::ThisContainer')], - Helpers::filterArguments(['a', 'b', 'Nette\DI\ContainerBuilder::ThisContainer']), -); - Assert::equal( ['a', 'b', new Nette\DI\Definitions\Reference('service')], Helpers::filterArguments(['a', 'b', '@service']), ); Assert::equal( - [new Statement('class', ['a', ContainerBuilder::literal('Nette\DI\ContainerBuilder::ThisContainer')])], - Helpers::filterArguments([new Statement('class', ['a', 'Nette\DI\ContainerBuilder::ThisContainer'])]), + [new Statement('class', ['a', new Nette\DI\Definitions\Reference('service')])], + Helpers::filterArguments([new Statement('class', ['a', '@service'])]), ); diff --git a/tests/DI/NeonAdapter.preprocess.phpt b/tests/DI/NeonAdapter.preprocess.phpt index 89552d5bc..7b59be016 100644 --- a/tests/DI/NeonAdapter.preprocess.phpt +++ b/tests/DI/NeonAdapter.preprocess.phpt @@ -98,3 +98,24 @@ Assert::equal( ], $data, ); + + +// constants +$data = @$adapter->load(Tester\FileMock::create(' +- Foo::Bar +- ArrayIterator::STD_PROP_LIST +- "ArrayIterator::STD_PROP_LIST" +- ::PHP_INT_MAX +- ArrayIterator::STD_PROP_LIST() +', 'neon')); + +Assert::equal( + [ + 'Foo::Bar', + ArrayIterator::STD_PROP_LIST, + 'ArrayIterator::STD_PROP_LIST', + PHP_INT_MAX, + new Statement('ArrayIterator::STD_PROP_LIST'), + ], + $data, +); From 718b0be9e094215b7e00f510a8aa4d187862f321 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 2 May 2024 12:54:43 +0200 Subject: [PATCH 12/56] github actions updated --- .github/workflows/coding-style.yml | 4 ++-- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index d46fa627c..96d05c498 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -7,7 +7,7 @@ jobs: name: Nette Code Checker runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: 8.2 @@ -21,7 +21,7 @@ jobs: name: Nette Coding Standard runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: 8.2 diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index c2e4c9a92..6f22b2fbf 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -10,7 +10,7 @@ jobs: name: PHPStan runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: 8.2 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3b571cea3..1bee6e40e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ jobs: name: PHP ${{ matrix.php }} tests steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} @@ -32,7 +32,7 @@ jobs: name: Lowest Dependencies runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: 8.1 @@ -46,7 +46,7 @@ jobs: name: Code Coverage runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: 8.2 From c1d4186ca51c8cf2f000608b360fc716b8c9711e Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 3 May 2024 20:53:09 +0200 Subject: [PATCH 13/56] Revert "LocatorDefinition: deprecated support for create($name) method (BC break)" This reverts commit 8140289e32e1b2bf2d8b5986bd92124b87fe6b2f. https://forum.nette.org/cs/36351-vysla-nova-verze-nette-di-3-2#p226913 --- src/DI/Definitions/LocatorDefinition.php | 4 +--- tests/DI/Compiler.generatedLocator.phpt | 3 +-- tests/DI/Definitions.LocatorDefinition.api.phpt | 12 ++++++------ .../Definitions.LocatorDefinition.render.create.phpt | 2 +- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/DI/Definitions/LocatorDefinition.php b/src/DI/Definitions/LocatorDefinition.php index f4fee5d66..fe8b3aecd 100644 --- a/src/DI/Definitions/LocatorDefinition.php +++ b/src/DI/Definitions/LocatorDefinition.php @@ -39,7 +39,7 @@ 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*(), get*() or get(\$name) and is non-static.", + "Service '%s': Method %s::%s() does not meet the requirements: is create(\$name), get(\$name), create*() or get*() and is non-static.", $this->getName(), $interface, $method->name, @@ -52,8 +52,6 @@ public function setImplement(string $interface): static "return type of $interface::$method->name()", allowNullable: true, ); - } elseif (str_starts_with($method->name, 'create')) { - trigger_error(sprintf("Service '%s': Method %s::create(\$name) is deprecated, use createFoo().", $this->getName(), $interface), E_USER_DEPRECATED); } } diff --git a/tests/DI/Compiler.generatedLocator.phpt b/tests/DI/Compiler.generatedLocator.phpt index 940f25432..34816729e 100644 --- a/tests/DI/Compiler.generatedLocator.phpt +++ b/tests/DI/Compiler.generatedLocator.phpt @@ -42,8 +42,7 @@ interface LocatorFactoryN } -// create($name) is deprecated -$container = @createContainer(new DI\Compiler, ' +$container = createContainer(new DI\Compiler, ' services: - LoremChild diff --git a/tests/DI/Definitions.LocatorDefinition.api.phpt b/tests/DI/Definitions.LocatorDefinition.api.phpt index aaef81002..50a771bd0 100644 --- a/tests/DI/Definitions.LocatorDefinition.api.phpt +++ b/tests/DI/Definitions.LocatorDefinition.api.phpt @@ -84,31 +84,31 @@ Assert::exception(function () { Assert::exception(function () { $def = new LocatorDefinition; $def->setImplement(Bad2::class); -}, Nette\InvalidArgumentException::class, "Service '': Method Bad2::create() does not meet the requirements: is create*(), get*() or get(\$name) 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*(), get*() or get(\$name) 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*(), get*() or get(\$name) 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*(), get*() or get(\$name) 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*(), get*() or get(\$name) 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 +121,7 @@ Assert::noError(function () { Assert::noError(function () { $def = new LocatorDefinition; - @$def->setImplement(Good2::class); // create($name) is deprecated + $def->setImplement(Good2::class); Assert::same(Good2::class, $def->getImplement()); Assert::same(Good2::class, $def->getType()); }); diff --git a/tests/DI/Definitions.LocatorDefinition.render.create.phpt b/tests/DI/Definitions.LocatorDefinition.render.create.phpt index 833fdbf75..4c649129f 100644 --- a/tests/DI/Definitions.LocatorDefinition.render.create.phpt +++ b/tests/DI/Definitions.LocatorDefinition.render.create.phpt @@ -22,7 +22,7 @@ interface Good test('', function () { $def = new LocatorDefinition; $def->setName('abc'); - @$def->setImplement(Good::class); // create($name) is deprecated + $def->setImplement(Good::class); $def->setReferences(['first' => '@a', 'second' => 'stdClass']); $builder = new Nette\DI\ContainerBuilder; From 50beb3271322a7c9a7b9f76d991476c9ae5c82d6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 7 May 2024 15:43:17 +0200 Subject: [PATCH 14/56] Resolver: better exception when normalizeEntity() fails due to service replacement https://forum.nette.org/en/36500-nette-di-servicecreationexception-service-cache-storage-not-found-in-definitions-used-in-nette-bridges-ca --- src/DI/Resolver.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/DI/Resolver.php b/src/DI/Resolver.php index dd51c490c..3e6afc3ff 100644 --- a/src/DI/Resolver.php +++ b/src/DI/Resolver.php @@ -354,12 +354,11 @@ private function normalizeEntity(Statement $statement): string|array|Reference|n } if ($item instanceof Definition) { - $name = current(array_keys($this->builder->getDefinitions(), $item, strict: true)); - if ($name === false) { - throw new ServiceCreationException(sprintf("Service '%s' not found in definitions.", $item->getName())); - } + if ($this->builder->getDefinition($item->getName()) !== $item) { + throw new ServiceCreationException(sprintf("Service '%s' does not match the expected service.", $item->getName())); - $item = new Reference($name); + } + $item = new Reference($item->getName()); } if ($item instanceof Reference) { From 83d9a4183b17c7d57ec058dcffc29dd22a5fa802 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 19 May 2024 22:33:44 +0200 Subject: [PATCH 15/56] fixed ReflectionParameter::getDefaultValue() error [Closes #315] This reverts commit 1c247c0ad3a71a9df2d9e7866334afe8dec7619b. --- src/DI/DependencyChecker.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DI/DependencyChecker.php b/src/DI/DependencyChecker.php index b9bcb305b..4b143af19 100644 --- a/src/DI/DependencyChecker.php +++ b/src/DI/DependencyChecker.php @@ -179,7 +179,7 @@ private static function hashParameters(\ReflectionFunctionAbstract $method): arr (string) $param->getType(), $param->isVariadic(), $param->isDefaultValueAvailable() - ? is_object($tmp = $param->getDefaultValue()) ? ['object' => $tmp::class] : ['value' => $tmp] + ? is_object($tmp = Reflection::getParameterDefaultValue($param)) ? ['object' => $tmp::class] : ['value' => $tmp] : null, ]; } From a4c42ffb9e33adff35496692f42589d17cf95eb2 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 11 Jun 2024 14:31:11 +0200 Subject: [PATCH 16/56] md5 replaced with xxHash --- src/DI/ContainerLoader.php | 2 +- src/DI/DependencyChecker.php | 2 +- tests/DI/Compiler.dependencies.phpt | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/DI/ContainerLoader.php b/src/DI/ContainerLoader.php index e6241a75b..8162013d7 100644 --- a/src/DI/ContainerLoader.php +++ b/src/DI/ContainerLoader.php @@ -40,7 +40,7 @@ public function load(callable $generator, mixed $key = null): string public function getClassName(mixed $key): string { - return 'Container_' . substr(md5(serialize($key)), 0, 10); + return 'Container_' . substr(hash('xxh128', serialize($key)), 0, 10); } diff --git a/src/DI/DependencyChecker.php b/src/DI/DependencyChecker.php index 4b143af19..94eb9fdc8 100644 --- a/src/DI/DependencyChecker.php +++ b/src/DI/DependencyChecker.php @@ -166,7 +166,7 @@ class_uses($name), ]; } - return md5(serialize($hash)); + return hash('xxh128', serialize($hash)); } diff --git a/tests/DI/Compiler.dependencies.phpt b/tests/DI/Compiler.dependencies.phpt index e5c98962d..59f831ac7 100644 --- a/tests/DI/Compiler.dependencies.phpt +++ b/tests/DI/Compiler.dependencies.phpt @@ -22,7 +22,7 @@ Assert::same( [], [], [], - '40cd750bba9870f18aada2478b24840a', + '91a436edb651f369cac5551729145e16', ], $compiler->exportDependencies(), ); @@ -37,7 +37,7 @@ Assert::same( [], [], [], - '40cd750bba9870f18aada2478b24840a', + '91a436edb651f369cac5551729145e16', ], $compiler->exportDependencies(), ); @@ -52,7 +52,7 @@ Assert::same( [], [], [], - '40cd750bba9870f18aada2478b24840a', + '91a436edb651f369cac5551729145e16', ], $compiler->exportDependencies(), ); @@ -87,7 +87,7 @@ Assert::same( [__FILE__ => filemtime(__FILE__)], ['Dep1'], [], - '1cde52df4926c96b79eaea6570f591d6', + '54d3c04c0d3d52db5d38d1f0f63b9f5d', ], $compiler->exportDependencies(), ); From 0833a0b2c17f7445bbc202363e73a4dcb07583cf Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 16 May 2024 23:26:10 +0200 Subject: [PATCH 17/56] readme: added jumbo --- readme.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 24d81e254..97f74641d 100644 --- a/readme.md +++ b/readme.md @@ -1,5 +1,4 @@ -Nette Dependency Injection (DI) -=============================== +[![Nette Dependency Injection](https://github.com/nette/di/assets/194960/d368a458-bac1-48b1-9b4b-7929f4bb2f98)](https://doc.nette.org/dependency-injection) [![Downloads this Month](https://img.shields.io/packagist/dm/nette/di.svg)](https://packagist.org/packages/nette/di) [![Tests](https://github.com/nette/di/workflows/Tests/badge.svg?branch=master)](https://github.com/nette/di/actions) @@ -7,6 +6,7 @@ Nette Dependency Injection (DI) [![Latest Stable Version](https://poser.pugx.org/nette/di/v/stable)](https://github.com/nette/di/releases) [![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/di/blob/master/license.md) +  Introduction ------------ @@ -17,6 +17,7 @@ Nette DI is one of the most interesting part of framework. It is compiled DI con Documentation can be found on the [website](https://doc.nette.org/dependency-injection). +  [Support Me](https://github.com/sponsors/dg) -------------------------------------------- @@ -27,6 +28,7 @@ Do you like Nette DI? Are you looking forward to the new features? Thank you! +  Installation ------------ @@ -39,6 +41,7 @@ composer require nette/di It requires PHP version 8.1 and supports PHP up to 8.3. +  Usage ----- @@ -177,6 +180,8 @@ $manager->distribute(...); Significant to Dependency Injection is that no class depends on the container. Thus it can be easily replaced with another one. For example with the container generated by Nette DI. +  + Nette DI ---------- @@ -218,6 +223,7 @@ During development it is useful to activate auto-refresh mode which automaticall $loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', autoRebuild: true); ``` +  Services -------- @@ -291,6 +297,7 @@ class FooClass However, this method is not ideal, because the variable must be declared as public and there is no way how you can ensure that the passed object will be of the given type. We also lose the ability to handle the assigned dependency in our code and we violate the principles of encapsulation. +  Factories --------- From 4b5c716c2728be425c1c0bcfb608769c0d2e4e6c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 18 Jun 2024 23:08:23 +0200 Subject: [PATCH 18/56] support for PHP 8.4 --- .github/workflows/tests.yml | 2 +- composer.json | 2 +- readme.md | 2 +- tests/DI/Compiler.configOverride.phpt | 2 +- tests/bootstrap.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1bee6e40e..b908378f2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['8.1', '8.2', '8.3'] + php: ['8.1', '8.2', '8.3', '8.4'] fail-fast: false diff --git a/composer.json b/composer.json index 28498797b..9811dc52b 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": "8.1 - 8.3", + "php": "8.1 - 8.4", "ext-tokenizer": "*", "ext-ctype": "*", "nette/neon": "^3.3 || ^4.0", diff --git a/readme.md b/readme.md index 97f74641d..25acf6454 100644 --- a/readme.md +++ b/readme.md @@ -39,7 +39,7 @@ The recommended way to install is via Composer: composer require nette/di ``` -It requires PHP version 8.1 and supports PHP up to 8.3. +It requires PHP version 8.1 and supports PHP up to 8.4.   diff --git a/tests/DI/Compiler.configOverride.phpt b/tests/DI/Compiler.configOverride.phpt index ab810683a..ed31e4996 100644 --- a/tests/DI/Compiler.configOverride.phpt +++ b/tests/DI/Compiler.configOverride.phpt @@ -21,7 +21,7 @@ class Ipsum } } -$class = 'Container' . md5((string) lcg_value()); +$class = 'Container'; $compiler = new DI\Compiler; $compiler->addConfig([ 'services' => [ diff --git a/tests/bootstrap.php b/tests/bootstrap.php index bf517ce72..9969632d5 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -59,7 +59,7 @@ public static function fetch(): array function createContainer($source, $config = null, array $params = []): ?Nette\DI\Container { - $class = 'Container' . md5((string) lcg_value()); + $class = 'Container' . @++$GLOBALS['counter']; if ($source instanceof Nette\DI\ContainerBuilder) { $source->complete(); $code = (new Nette\DI\PhpGenerator($source))->generate($class); From 9b9bfb43dac31c7804b2c8900217046cc0ca3307 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 10 Sep 2024 11:11:35 +0200 Subject: [PATCH 19/56] uses nette/php-generator 4.1.6 --- composer.json | 2 +- src/DI/Extensions/ParametersExtension.php | 7 ++++--- src/DI/PhpGenerator.php | 7 ++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 9811dc52b..f8342787f 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "ext-tokenizer": "*", "ext-ctype": "*", "nette/neon": "^3.3 || ^4.0", - "nette/php-generator": "^4.1.3", + "nette/php-generator": "^4.1.6", "nette/robot-loader": "^4.0", "nette/schema": "^1.2.5", "nette/utils": "^4.0" diff --git a/src/DI/Extensions/ParametersExtension.php b/src/DI/Extensions/ParametersExtension.php index 3de4d1c46..144554d5f 100644 --- a/src/DI/Extensions/ParametersExtension.php +++ b/src/DI/Extensions/ParametersExtension.php @@ -61,7 +61,8 @@ public function afterCompile(Nette\PhpGenerator\ClassType $class): void }); } - $class->inheritMethod('getStaticParameters') + $manipulator = new Nette\PhpGenerator\ClassManipulator($class); + $manipulator->inheritMethod('getStaticParameters') ->addBody('return ?;', [array_diff_key($builder->parameters, $dynamicParams)]); if (!$dynamicParams) { @@ -70,7 +71,7 @@ public function afterCompile(Nette\PhpGenerator\ClassType $class): void $resolver = new Nette\DI\Resolver($builder); $generator = new Nette\DI\PhpGenerator($builder); - $method = $class->inheritMethod('getDynamicParameter'); + $method = $manipulator->inheritMethod('getDynamicParameter'); $method->addBody('return match($key) {'); foreach ($dynamicParams as $key => $foo) { $value = Helpers::expand($this->config[$key] ?? null, $builder->parameters); @@ -84,7 +85,7 @@ public function afterCompile(Nette\PhpGenerator\ClassType $class): void $method->addBody("\tdefault => parent::getDynamicParameter(\$key),\n};"); if ($preload = array_keys($dynamicParams, true, true)) { - $method = $class->inheritMethod('getParameters'); + $method = $manipulator->inheritMethod('getParameters'); $method->addBody('array_map($this->getParameter(...), ?);', [$preload]); $method->addBody('return parent::getParameters();'); } diff --git a/src/DI/PhpGenerator.php b/src/DI/PhpGenerator.php index da0abf59a..bbb4b574c 100644 --- a/src/DI/PhpGenerator.php +++ b/src/DI/PhpGenerator.php @@ -38,11 +38,12 @@ public function generate(string $className): Php\ClassType $this->className = $className; $class = new Php\ClassType($this->className); $class->setExtends(Container::class); - $class->inheritMethod('__construct') + $manipulator = new Php\ClassManipulator($class); + $manipulator->inheritMethod('__construct') ->addBody('parent::__construct($params);'); foreach ($this->builder->exportMeta() as $key => $value) { - $class->inheritProperty($key) + $manipulator->inheritProperty($key) ->setComment(null) ->setValue($value); } @@ -57,7 +58,7 @@ public function generate(string $className): Php\ClassType $class->getMethod(Container::getMethodName(ContainerBuilder::ThisContainer)) ->setBody('return $this;'); - $class->inheritMethod('initialize'); + $manipulator->inheritMethod('initialize'); return $class; } From df0375f56c4851a675a68721c621359565e6b80b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 2 Dec 2024 05:33:59 +0100 Subject: [PATCH 20/56] cs --- src/DI/Config/Adapters/NeonAdapter.php | 41 ++++++++++++------------ src/DI/Definitions/LocatorDefinition.php | 12 ++++--- src/DI/Extensions/DecoratorExtension.php | 1 + src/DI/PhpGenerator.php | 8 +++-- 4 files changed, 35 insertions(+), 27 deletions(-) diff --git a/src/DI/Config/Adapters/NeonAdapter.php b/src/DI/Config/Adapters/NeonAdapter.php index d806cd319..7de1eee1e 100644 --- a/src/DI/Config/Adapters/NeonAdapter.php +++ b/src/DI/Config/Adapters/NeonAdapter.php @@ -10,10 +10,11 @@ namespace Nette\DI\Config\Adapters; use Nette; -use Nette\DI\Config\Helpers; +use Nette\DI; use Nette\DI\Definitions\Reference; use Nette\DI\Definitions\Statement; use Nette\Neon; +use Nette\Neon\Node; /** @@ -43,7 +44,7 @@ public function load(string $file): array $node = $traverser->traverse($node, $this->removeUnderscoreVisitor(...)); $node = $traverser->traverse($node, $this->convertAtSignVisitor(...)); $node = $traverser->traverse($node, $this->deprecatedParametersVisitor(...)); - $node = $traverser->traverse($node, $this->resolveConstants(...)); + $node = $traverser->traverse($node, $this->resolveConstantsVisitor(...)); return $this->process((array) $node->toValue()); } @@ -63,7 +64,7 @@ public function process(array $arr): array } $key = substr($key, 0, -1); - $val[Helpers::PREVENT_MERGING] = true; + $val[DI\Config\Helpers::PREVENT_MERGING] = true; } if (is_array($val)) { @@ -150,12 +151,12 @@ function (&$val): void { } - private function firstClassCallableVisitor(Neon\Node $node): void + private function firstClassCallableVisitor(Node $node): void { - if ($node instanceof Neon\Node\EntityNode + if ($node instanceof Node\EntityNode && count($node->attributes) === 1 && $node->attributes[0]->key === null - && $node->attributes[0]->value instanceof Neon\Node\LiteralNode + && $node->attributes[0]->value instanceof Node\LiteralNode && $node->attributes[0]->value->value === '...' ) { $node->attributes[0]->value->value = Nette\DI\Resolver::getFirstClassCallable()[0]; @@ -163,9 +164,9 @@ private function firstClassCallableVisitor(Neon\Node $node): void } - private function removeUnderscoreVisitor(Neon\Node $node): void + private function removeUnderscoreVisitor(Node $node): void { - if (!$node instanceof Neon\Node\EntityNode) { + if (!$node instanceof Node\EntityNode) { return; } @@ -175,13 +176,13 @@ private function removeUnderscoreVisitor(Neon\Node $node): void continue; } - $attr->key = $index ? new Neon\Node\LiteralNode((string) $i) : null; + $attr->key = $index ? new Node\LiteralNode((string) $i) : null; - if ($attr->value instanceof Neon\Node\LiteralNode && $attr->value->value === '_') { + if ($attr->value instanceof Node\LiteralNode && $attr->value->value === '_') { unset($node->attributes[$i]); $index = true; - } elseif ($attr->value instanceof Neon\Node\LiteralNode && $attr->value->value === '...') { + } elseif ($attr->value instanceof Node\LiteralNode && $attr->value->value === '...') { trigger_error("Replace ... with _ in configuration file '$this->file'.", E_USER_DEPRECATED); unset($node->attributes[$i]); $index = true; @@ -190,9 +191,9 @@ private function removeUnderscoreVisitor(Neon\Node $node): void } - private function convertAtSignVisitor(Neon\Node $node): void + private function convertAtSignVisitor(Node $node): void { - if ($node instanceof Neon\Node\StringNode) { + if ($node instanceof Node\StringNode) { if (str_starts_with($node->value, '@@')) { trigger_error("There is no need to escape @ anymore, replace @@ with @ in: '$node->value' (used in $this->file)", E_USER_DEPRECATED); } else { @@ -200,7 +201,7 @@ private function convertAtSignVisitor(Neon\Node $node): void } } elseif ( - $node instanceof Neon\Node\LiteralNode + $node instanceof Node\LiteralNode && is_string($node->value) && str_starts_with($node->value, '@@') ) { @@ -209,9 +210,9 @@ private function convertAtSignVisitor(Neon\Node $node): void } - private function deprecatedParametersVisitor(Neon\Node $node): void + private function deprecatedParametersVisitor(Node $node): void { - if (($node instanceof Neon\Node\StringNode || $node instanceof Neon\Node\LiteralNode) + if (($node instanceof Node\StringNode || $node instanceof Node\LiteralNode) && is_string($node->value) && str_contains($node->value, '%parameters%') ) { @@ -220,16 +221,16 @@ private function deprecatedParametersVisitor(Neon\Node $node): void } - private function resolveConstants(Neon\Node $node): void + private function resolveConstantsVisitor(Node $node): void { $items = match (true) { - $node instanceof Neon\Node\ArrayNode => $node->items, - $node instanceof Neon\Node\EntityNode => $node->attributes, + $node instanceof Node\ArrayNode => $node->items, + $node instanceof Node\EntityNode => $node->attributes, default => null, }; if ($items) { foreach ($items as $item) { - if ($item->value instanceof Neon\Node\LiteralNode + if ($item->value instanceof Node\LiteralNode && is_string($item->value->value) && preg_match('#^([\w\\\\]*)::[A-Z]\w+$#D', $item->value->value) && defined(ltrim($item->value->value, ':')) diff --git a/src/DI/Definitions/LocatorDefinition.php b/src/DI/Definitions/LocatorDefinition.php index fe8b3aecd..a9e3f735a 100644 --- a/src/DI/Definitions/LocatorDefinition.php +++ b/src/DI/Definitions/LocatorDefinition.php @@ -149,10 +149,14 @@ public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGe $class->addProperty('mapping', array_map(fn($item) => $item->getValue(), $this->references)) ->setPrivate(); - $methodInner->setBody('if (!isset($this->mapping[$name])) { - ' . ($nullable ? 'return null;' : 'throw new Nette\DI\MissingServiceException("Service \'$name\' is not defined.");') . ' -} -return $this->container->' . $m[1] . 'Service($this->mapping[$name]);') + $methodInner->setBody(<<<'XX' + if (!isset($this->mapping[$name])) { + + XX . "\t" . ($nullable ? 'return null;' : 'throw new Nette\DI\MissingServiceException("Service \'$name\' is not defined.");') . <<<'XX' + + } + return $this->container-> + XX . $m[1] . 'Service($this->mapping[$name]);') ->addParameter('name'); } elseif (isset($this->references[$name])) { diff --git a/src/DI/Extensions/DecoratorExtension.php b/src/DI/Extensions/DecoratorExtension.php index e5a4cc129..0725d8086 100644 --- a/src/DI/Extensions/DecoratorExtension.php +++ b/src/DI/Extensions/DecoratorExtension.php @@ -13,6 +13,7 @@ use Nette\DI\Definitions; use Nette\Schema\Expect; + /** * Decorators for services. */ diff --git a/src/DI/PhpGenerator.php b/src/DI/PhpGenerator.php index bbb4b574c..b5176c40d 100644 --- a/src/DI/PhpGenerator.php +++ b/src/DI/PhpGenerator.php @@ -66,11 +66,13 @@ public function generate(string $className): Php\ClassType public function toString(Php\ClassType $class): string { - return '/** @noinspection PhpParamsInspection,PhpMethodMayBeStaticInspection */ + return <<<'XX' + /** @noinspection PhpParamsInspection,PhpMethodMayBeStaticInspection */ + + declare(strict_types=1); -declare(strict_types=1); -' . $class->__toString(); + XX . $class->__toString(); } From b1cf83bf1cd5718ab65e806994c24d3558010d07 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 29 Nov 2024 21:16:07 +0100 Subject: [PATCH 21/56] 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 39edde747c81349d922afe837bd1065137c59186 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 29 Nov 2024 22:23:10 +0100 Subject: [PATCH 22/56] 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 57f923a7af32435b6e4921c0adbc70c619625a17 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 25 Nov 2024 11:38:08 +0100 Subject: [PATCH 23/56] implemented support for lazy services in PHP 8.4 --- src/DI/Definitions/ServiceDefinition.php | 37 +++++++--- 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 + tests/DI/DIExtension.lazy.phpt | 82 +++++++++++++++++++++++ 6 files changed, 131 insertions(+), 9 deletions(-) create mode 100644 tests/DI/DIExtension.lazy.phpt diff --git a/src/DI/Definitions/ServiceDefinition.php b/src/DI/Definitions/ServiceDefinition.php index 6cec76a86..ff934c26a 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,7 @@ final class ServiceDefinition extends Definition { use Nette\SmartObject; + public ?bool $lazy = null; private Statement $creator; /** @var Statement[] */ @@ -181,19 +183,36 @@ private function prependSelf(Statement $setup): Statement public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void { - $code = $generator->formatStatement($this->creator) . ";\n"; - if (!$this->setup) { - $method->setBody('return ' . $code); - return; + $lines = []; + foreach ([$this->creator, ...$this->setup] as $stmt) { + $lines[] = $generator->formatStatement($stmt) . ";\n"; } - $code = '$service = ' . $code; - foreach ($this->setup as $setup) { - $code .= $generator->formatStatement($setup) . ";\n"; + if ($this->canBeLazy() && !preg_grep('#(?:func_get_arg|func_num_args)#i', $lines)) { // latteFactory workaround + $class = $this->creator->getEntity(); + $lines[0] = (new \ReflectionClass($class))->hasMethod('__construct') + ? $generator->formatPhp("\$service->__construct(...?:);\n", [$this->creator->arguments]) + : ''; + $method->setBody("return new ReflectionClass($class::class)->newLazyGhost(function (\$service) {\n" + . Strings::indent(implode('', $lines)) + . '});'); + + } elseif (count($lines) === 1) { + $method->setBody('return ' . $lines[0]); + + } else { + $method->setBody('$service = ' . implode('', $lines) . 'return $service;'); } + } - $code .= 'return $service;'; - $method->setBody($code); + + private function canBeLazy(): bool + { + return $this->lazy + && is_string($class = $this->creator->getEntity()) + && ($this->creator->arguments || $this->setup) + && ($ancestor = ($tmp = class_parents($class)) ? array_pop($tmp) : $class) + && !(new \ReflectionClass($ancestor))->isInternal(); } diff --git a/src/DI/Extensions/DIExtension.php b/src/DI/Extensions/DIExtension.php index 5b789cffa..99e9de0f1 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 && PHP_VERSION_ID >= 80400) { + $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, ], ], diff --git a/tests/DI/DIExtension.lazy.phpt b/tests/DI/DIExtension.lazy.phpt new file mode 100644 index 000000000..de3ed60de --- /dev/null +++ b/tests/DI/DIExtension.lazy.phpt @@ -0,0 +1,82 @@ +isUninitializedLazyObject($obj); +} + + +test('Eager is default', function () { + $compiler = new DI\Compiler; + $compiler->addExtension('di', new DIExtension); + $container = createContainer($compiler, ' + services: + internal: stdClass + trivial: stdClass + default: Service(10) + eager: + create: Service(10) + lazy: false + lazy: + create: Service(10) + lazy: true + '); + + Assert::false(isLazy($container->getByName('internal'))); + Assert::false(isLazy($container->getByName('trivial'))); + Assert::false(isLazy($container->getByName('default'))); + Assert::false(isLazy($container->getByName('eager'))); + Assert::true(isLazy($container->getByName('lazy'))); +}); + + +test('Lazy is default', function () { + $compiler = new DI\Compiler; + $compiler->addExtension('di', new DIExtension); + $container = createContainer($compiler, ' + di: + lazy: true + + services: + internal: stdClass + trivial: stdClass + default: Service(10) + eager: + create: Service(10) + lazy: false + lazy: + create: Service(10) + lazy: true + '); + + Assert::false(isLazy($container->getByName('internal'))); + Assert::false(isLazy($container->getByName('trivial'))); + Assert::true(isLazy($container->getByName('default'))); + Assert::false(isLazy($container->getByName('eager'))); + Assert::true(isLazy($container->getByName('lazy'))); +}); From 61b7a414ebd4f27dc35ebc9ec79bdfd7af7a25b8 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 16 Jan 2025 05:29:29 +0100 Subject: [PATCH 24/56] cs --- src/DI/Config/Adapters/NeonAdapter.php | 6 +++--- src/DI/Config/Loader.php | 2 +- src/DI/Definitions/ImportedDefinition.php | 2 +- src/DI/Extensions/DefinitionSchema.php | 2 +- src/DI/Extensions/SearchExtension.php | 4 ++-- src/DI/Extensions/ServicesExtension.php | 2 +- src/DI/Helpers.php | 2 +- src/DI/Resolver.php | 6 +++--- tests/SearchExtension/all.phpt | 4 ++-- tests/SearchExtension/batches.phpt | 4 ++-- tests/SearchExtension/classes.phpt | 14 +++++++------- tests/SearchExtension/combined.phpt | 2 +- tests/SearchExtension/duplicates.phpt | 4 ++-- tests/SearchExtension/files.phpt | 8 ++++---- tests/SearchExtension/parents.phpt | 12 ++++++------ tests/bootstrap.php | 4 ++-- 16 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/DI/Config/Adapters/NeonAdapter.php b/src/DI/Config/Adapters/NeonAdapter.php index 7de1eee1e..fcc2d5459 100644 --- a/src/DI/Config/Adapters/NeonAdapter.php +++ b/src/DI/Config/Adapters/NeonAdapter.php @@ -71,7 +71,7 @@ public function process(array $arr): array $val = $this->process($val); } elseif ($val instanceof Neon\Entity) { - if ($val->value === Neon\Neon::CHAIN) { + if ($val->value === Neon\Neon::Chain) { $tmp = null; foreach ($this->process($val->attributes) as $st) { $tmp = new Statement( @@ -134,7 +134,7 @@ function (&$val): void { } elseif (is_array($entity)) { if ($entity[0] instanceof Statement) { return new Neon\Entity( - Neon\Neon::CHAIN, + Neon\Neon::Chain, [ self::statementToEntity($entity[0]), new Neon\Entity('::' . $entity[1], $val->arguments), @@ -232,7 +232,7 @@ private function resolveConstantsVisitor(Node $node): void foreach ($items as $item) { if ($item->value instanceof Node\LiteralNode && is_string($item->value->value) - && preg_match('#^([\w\\\\]*)::[A-Z]\w+$#D', $item->value->value) + && preg_match('#^([\w\\\]*)::[A-Z]\w+$#D', $item->value->value) && defined(ltrim($item->value->value, ':')) ) { $item->value->value = constant(ltrim($item->value->value, ':')); diff --git a/src/DI/Config/Loader.php b/src/DI/Config/Loader.php index 8da694676..cd845f019 100644 --- a/src/DI/Config/Loader.php +++ b/src/DI/Config/Loader.php @@ -83,7 +83,7 @@ public function getDependencies(): array */ public function expandIncludedFile(string $includedFile, string $mainFile): string { - return preg_match('#([a-z]+:)?[/\\\\]#Ai', $includedFile) // is absolute + return preg_match('#([a-z]+:)?[/\\\]#Ai', $includedFile) // is absolute ? $includedFile : dirname($mainFile) . '/' . $includedFile; } diff --git a/src/DI/Definitions/ImportedDefinition.php b/src/DI/Definitions/ImportedDefinition.php index 501e558cc..e9116653b 100644 --- a/src/DI/Definitions/ImportedDefinition.php +++ b/src/DI/Definitions/ImportedDefinition.php @@ -37,7 +37,7 @@ public function complete(Nette\DI\Resolver $resolver): void public function generateMethod(Nette\PhpGenerator\Method $method, PhpGenerator $generator): void { $method->setBody( - 'throw new Nette\\DI\\ServiceCreationException(?);', + 'throw new Nette\DI\ServiceCreationException(?);', ["Unable to create imported service '{$this->getName()}', it must be added using addService()"], ); } diff --git a/src/DI/Extensions/DefinitionSchema.php b/src/DI/Extensions/DefinitionSchema.php index 487ddecae..7935324e8 100644 --- a/src/DI/Extensions/DefinitionSchema.php +++ b/src/DI/Extensions/DefinitionSchema.php @@ -118,7 +118,7 @@ public function completeDefault(Context $context) private function sniffType($key, array $def): string { if (is_string($key)) { - $name = preg_match('#^@[\w\\\\]+$#D', $key) + $name = preg_match('#^@[\w\\\]+$#D', $key) ? $this->builder->getByType(substr($key, 1)) : $key; diff --git a/src/DI/Extensions/SearchExtension.php b/src/DI/Extensions/SearchExtension.php index eb872534d..0be9a8b25 100644 --- a/src/DI/Extensions/SearchExtension.php +++ b/src/DI/Extensions/SearchExtension.php @@ -148,8 +148,8 @@ private static function buildNameRegexp(array $masks): ?string foreach ($masks as $mask) { $mask = (str_contains($mask, '\\') ? '' : '**\\') . $mask; $mask = preg_quote($mask, '#'); - $mask = str_replace('\*\*\\\\', '(.*\\\\)?', $mask); - $mask = str_replace('\\\\\*\*', '(\\\\.*)?', $mask); + $mask = str_replace('\*\*\\\\', '(.*\\\)?', $mask); + $mask = str_replace('\\\\\*\*', '(\\\.*)?', $mask); $mask = str_replace('\*', '\w*', $mask); $res[] = $mask; } diff --git a/src/DI/Extensions/ServicesExtension.php b/src/DI/Extensions/ServicesExtension.php index 075cf54a3..38d7d47af 100644 --- a/src/DI/Extensions/ServicesExtension.php +++ b/src/DI/Extensions/ServicesExtension.php @@ -238,7 +238,7 @@ private function convertKeyToName($key): ?string { if (is_int($key)) { return null; - } elseif (preg_match('#^@[\w\\\\]+$#D', $key)) { + } elseif (preg_match('#^@[\w\\\]+$#D', $key)) { return $this->getContainerBuilder()->getByType(substr($key, 1), throw: true); } diff --git a/src/DI/Helpers.php b/src/DI/Helpers.php index 610239b8d..48e421fbf 100644 --- a/src/DI/Helpers.php +++ b/src/DI/Helpers.php @@ -158,7 +158,7 @@ public static function escape(mixed $value): mixed public static function filterArguments(array $args): array { foreach ($args as $k => $v) { - if (is_string($v) && preg_match('#^@[\w\\\\]+$#D', $v)) { + if (is_string($v) && preg_match('#^@[\w\\\]+$#D', $v)) { $args[$k] = new Reference(substr($v, 1)); } elseif (is_array($v)) { $args[$k] = self::filterArguments($v); diff --git a/src/DI/Resolver.php b/src/DI/Resolver.php index a1e594495..ceab3d135 100644 --- a/src/DI/Resolver.php +++ b/src/DI/Resolver.php @@ -249,7 +249,7 @@ public function completeStatement(Statement $statement, bool $currentServiceAllo break; case is_array($entity): - if (!preg_match('#^\$?(\\\\?' . PhpHelpers::ReIdentifier . ')+(\[\])?$#D', $entity[1])) { + if (!preg_match('#^\$?(\\\?' . PhpHelpers::ReIdentifier . ')+(\[\])?$#D', $entity[1])) { throw new ServiceCreationException(sprintf( "Expected function, method or property name, '%s' given.", $entity[1], @@ -455,7 +455,7 @@ private function completeException(\Throwable $e, Definition $def): ServiceCreat } $message .= $type - ? str_replace("$type::", preg_replace('~.*\\\\~', '', $type) . '::', $e->getMessage()) + ? str_replace("$type::", preg_replace('~.*\\\~', '', $type) . '::', $e->getMessage()) : $e->getMessage(); return $e instanceof ServiceCreationException @@ -643,7 +643,7 @@ private static function isArrayOf(\ReflectionParameter $parameter, ?Nette\Utils\ return $method instanceof \ReflectionMethod && $type?->getSingleName() === 'array' && preg_match( - '#@param[ \t]+(?|([\w\\\\]+)\[\]|list<([\w\\\\]+)>|array)[ \t]+\$' . $parameter->name . '#', + '#@param[ \t]+(?|([\w\\\]+)\[\]|list<([\w\\\]+)>|array)[ \t]+\$' . $parameter->name . '#', (string) $method->getDocComment(), $m, ) diff --git a/tests/SearchExtension/all.phpt b/tests/SearchExtension/all.phpt index f2d066d9f..01b0201bf 100644 --- a/tests/SearchExtension/all.phpt +++ b/tests/SearchExtension/all.phpt @@ -18,8 +18,8 @@ Assert::same([ 'ClassOk3', 'CountableClass', 'ExtendsStdClass', - 'Foo\\Bar\\ClassBar', - 'Foo\\ClassBar', + 'Foo\Bar\ClassBar', + 'Foo\ClassBar', 'InterfaceOk1', 'InterfaceOk2', ], array_keys($services)); diff --git a/tests/SearchExtension/batches.phpt b/tests/SearchExtension/batches.phpt index 26fda917b..453c6c668 100644 --- a/tests/SearchExtension/batches.phpt +++ b/tests/SearchExtension/batches.phpt @@ -33,8 +33,8 @@ Assert::same([ 'ClassOk3' => ['ok' => true, 'subdir' => true], 'CountableClass' => ['ok' => true], 'ExtendsStdClass' => ['ok' => true], - 'Foo\\Bar\\ClassBar' => ['foo' => true], - 'Foo\\ClassBar' => ['foo' => true], + 'Foo\Bar\ClassBar' => ['foo' => true], + 'Foo\ClassBar' => ['foo' => true], 'InterfaceOk1' => ['ok' => true], 'InterfaceOk2' => ['ok' => true], ], $services); diff --git a/tests/SearchExtension/classes.phpt b/tests/SearchExtension/classes.phpt index cea9cfac7..f8126fd71 100644 --- a/tests/SearchExtension/classes.phpt +++ b/tests/SearchExtension/classes.phpt @@ -19,8 +19,8 @@ Assert::same([ 'ClassOk3', 'CountableClass', 'ExtendsStdClass', - 'Foo\\Bar\\ClassBar', - 'Foo\\ClassBar', + 'Foo\Bar\ClassBar', + 'Foo\ClassBar', 'InterfaceOk1', 'InterfaceOk2', ], array_keys($services)); @@ -49,8 +49,8 @@ Assert::same([ 'ClassOk1', 'ClassOk2', 'ClassOk3', - 'Foo\\Bar\\ClassBar', - 'Foo\\ClassBar', + 'Foo\Bar\ClassBar', + 'Foo\ClassBar', 'InterfaceOk1', 'InterfaceOk2', ], array_keys($services)); @@ -64,7 +64,7 @@ search: - *\*Bar '); -Assert::same(['Foo\\ClassBar'], array_keys($services)); +Assert::same(['Foo\ClassBar'], array_keys($services)); @@ -81,8 +81,8 @@ Assert::same([ 'ClassOk3', 'CountableClass', 'ExtendsStdClass', - 'Foo\\Bar\\ClassBar', - 'Foo\\ClassBar', + 'Foo\Bar\ClassBar', + 'Foo\ClassBar', ], array_keys($services)); diff --git a/tests/SearchExtension/combined.phpt b/tests/SearchExtension/combined.phpt index 21ea3a2a2..b9f9ed523 100644 --- a/tests/SearchExtension/combined.phpt +++ b/tests/SearchExtension/combined.phpt @@ -40,4 +40,4 @@ search: n: 123 '); -Assert::same(['Foo\\Bar\\ClassBar'], array_keys($services)); +Assert::same(['Foo\Bar\ClassBar'], array_keys($services)); diff --git a/tests/SearchExtension/duplicates.phpt b/tests/SearchExtension/duplicates.phpt index d8b2eec98..83198c750 100644 --- a/tests/SearchExtension/duplicates.phpt +++ b/tests/SearchExtension/duplicates.phpt @@ -24,8 +24,8 @@ Assert::same([ 'ClassOk3', 'CountableClass', 'ExtendsStdClass', - 'Foo\\Bar\\ClassBar', - 'Foo\\ClassBar', + 'Foo\Bar\ClassBar', + 'Foo\ClassBar', 'InterfaceOk1', 'InterfaceOk2', ], array_keys($services)); diff --git a/tests/SearchExtension/files.phpt b/tests/SearchExtension/files.phpt index b72a55147..f84fd2833 100644 --- a/tests/SearchExtension/files.phpt +++ b/tests/SearchExtension/files.phpt @@ -19,8 +19,8 @@ Assert::same([ 'ClassOk3', 'CountableClass', 'ExtendsStdClass', - 'Foo\\Bar\\ClassBar', - 'Foo\\ClassBar', + 'Foo\Bar\ClassBar', + 'Foo\ClassBar', 'InterfaceOk1', 'InterfaceOk2', ], array_keys($services)); @@ -59,8 +59,8 @@ Assert::same([ 'ClassOk3', 'CountableClass', 'ExtendsStdClass', - 'Foo\\Bar\\ClassBar', - 'Foo\\ClassBar', + 'Foo\Bar\ClassBar', + 'Foo\ClassBar', 'InterfaceOk1', 'InterfaceOk2', ], array_keys($services)); diff --git a/tests/SearchExtension/parents.phpt b/tests/SearchExtension/parents.phpt index bf8bf1254..7a7772a10 100644 --- a/tests/SearchExtension/parents.phpt +++ b/tests/SearchExtension/parents.phpt @@ -19,8 +19,8 @@ Assert::same([ 'ClassOk3', 'CountableClass', 'ExtendsStdClass', - 'Foo\\Bar\\ClassBar', - 'Foo\\ClassBar', + 'Foo\Bar\ClassBar', + 'Foo\ClassBar', 'InterfaceOk1', 'InterfaceOk2', ], array_keys($services)); @@ -33,7 +33,7 @@ search: extends: stdClass '); -Assert::same(['ExtendsStdClass', 'Foo\\Bar\\ClassBar'], array_keys($services)); +Assert::same(['ExtendsStdClass', 'Foo\Bar\ClassBar'], array_keys($services)); @@ -45,7 +45,7 @@ search: - Countable '); -Assert::same(['CountableClass', 'ExtendsStdClass', 'Foo\\Bar\\ClassBar'], array_keys($services)); +Assert::same(['CountableClass', 'ExtendsStdClass', 'Foo\Bar\ClassBar'], array_keys($services)); @@ -58,7 +58,7 @@ search: - Countable '); -Assert::same(['CountableClass', 'ExtendsStdClass', 'Foo\\Bar\\ClassBar'], array_keys($services)); +Assert::same(['CountableClass', 'ExtendsStdClass', 'Foo\Bar\ClassBar'], array_keys($services)); @@ -96,7 +96,7 @@ Assert::same([ 'ClassOk2', 'ClassOk3', 'CountableClass', - 'Foo\\ClassBar', + 'Foo\ClassBar', 'InterfaceOk1', 'InterfaceOk2', ], array_keys($services)); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 9969632d5..7f5dc10dd 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -20,9 +20,9 @@ function getTempDir(): string { $dir = __DIR__ . '/tmp/' . getmypid(); - if (empty($GLOBALS['\\lock'])) { + if (empty($GLOBALS['\lock'])) { // garbage collector - $GLOBALS['\\lock'] = $lock = fopen(__DIR__ . '/lock', 'w'); + $GLOBALS['\lock'] = $lock = fopen(__DIR__ . '/lock', 'w'); if (rand(0, 100)) { flock($lock, LOCK_SH); @mkdir(dirname($dir)); From 8afd87f71c63621b3529bd88cc79d727fc0e1046 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 18 Jun 2025 21:33:39 +0200 Subject: [PATCH 25/56] composer: stricter constraint --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f8342787f..c3bec0059 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", "nette/php-generator": "^4.1.6", "nette/robot-loader": "^4.0", "nette/schema": "^1.2.5", From 007bc0fb18385211ad2307ea8218b5178e7a157c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 3 Feb 2025 03:31:24 +0100 Subject: [PATCH 26/56] github actions updated --- .github/workflows/coding-style.yml | 4 ++-- .github/workflows/tests.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index 96d05c498..995988d49 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: 8.2 + php-version: 8.3 coverage: none - run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: 8.2 + php-version: 8.3 coverage: none - run: composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b908378f2..189614db5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,9 +22,9 @@ jobs: - run: composer install --no-progress --prefer-dist - run: vendor/bin/tester tests -s -C - if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: output + name: output-${{ matrix.php }} path: tests/**/output From b3776b1c0f7af4810ad8898f6ecff2ebaa351043 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 6 Jun 2025 00:56:10 +0200 Subject: [PATCH 27/56] composer: require stable packages outside of nette --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c3bec0059..78e4439e4 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "require-dev": { "nette/tester": "^2.5.2", "tracy/tracy": "^2.9", - "phpstan/phpstan": "^1.0" + "phpstan/phpstan-nette": "^2.0@stable" }, "autoload": { "classmap": ["src/"] From 9ab27c6decbc0db80afcbf5b6cd245567b501d4f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 6 Jun 2025 01:20:03 +0200 Subject: [PATCH 28/56] composer: added psr-4 loader --- composer.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 78e4439e4..cbf4588ac 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,10 @@ "phpstan/phpstan-nette": "^2.0@stable" }, "autoload": { - "classmap": ["src/"] + "classmap": ["src/"], + "psr-4": { + "Nette\\": "src" + } }, "minimum-stability": "dev", "scripts": { From 908e6f5f1d5cb66d6a25a8010d005e441432a5df Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 3 Feb 2025 01:42:36 +0100 Subject: [PATCH 29/56] tests: improved descriptions --- tests/DI/Compiler.addExtension.phpt | 6 +- .../DI/ContainerBuilder.autowiring.types.phpt | 28 +++++----- tests/DI/ContainerBuilder.create.error.phpt | 56 +++++++++---------- 3 files changed, 45 insertions(+), 45 deletions(-) diff --git a/tests/DI/Compiler.addExtension.phpt b/tests/DI/Compiler.addExtension.phpt index e27c1d1f7..071642abf 100644 --- a/tests/DI/Compiler.addExtension.phpt +++ b/tests/DI/Compiler.addExtension.phpt @@ -21,21 +21,21 @@ class FooExtension extends DI\CompilerExtension } -testException('', function () { +testException('adding extension during loadConfiguration triggers deprecation', function () { $compiler = new DI\Compiler; $compiler->addExtension('foo', new FooExtension); $container = createContainer($compiler); }, Nette\DeprecatedException::class, "Extensions 'bar' were added while container was being compiled."); -testException('', function () { +testException('duplicate extension name throws error', function () { $compiler = new DI\Compiler; $compiler->addExtension('foo', new FooExtension); $compiler->addExtension('foo', new FooExtension); }, Nette\InvalidArgumentException::class, "Name 'foo' is already used or reserved."); -testException('', function () { +testException('extension name conflict due to case-insensitivity', function () { $compiler = new DI\Compiler; $compiler->addExtension('foo', new FooExtension); $compiler->addExtension('Foo', new FooExtension); diff --git a/tests/DI/ContainerBuilder.autowiring.types.phpt b/tests/DI/ContainerBuilder.autowiring.types.phpt index f14f11531..66e3b1f3e 100644 --- a/tests/DI/ContainerBuilder.autowiring.types.phpt +++ b/tests/DI/ContainerBuilder.autowiring.types.phpt @@ -30,7 +30,7 @@ class Bar extends Foo implements IBar } -test('Autowiring limited to Bar class and its subclasses', function () { +test('autowire using self type only', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('bar') ->setType(Bar::class) @@ -43,7 +43,7 @@ test('Autowiring limited to Bar class and its subclasses', function () { }); -test('Autowiring limited to Bar class via self', function () { +test('autowire with "self" keyword works correctly', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('bar') ->setType(Bar::class) @@ -56,7 +56,7 @@ test('Autowiring limited to Bar class via self', function () { }); -test('Autowiring limited to IBar interface and its implementations', function () { +test('autowire via interface returns service', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('bar') ->setType(Bar::class) @@ -69,7 +69,7 @@ test('Autowiring limited to IBar interface and its implementations', function () }); -test('Autowiring limited to Foo class and its subclasses', function () { +test('autowire via parent class returns service', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('bar') ->setType(Bar::class) @@ -82,7 +82,7 @@ test('Autowiring limited to Foo class and its subclasses', function () { }); -test('Autowiring limited to IFoo interface and its implementations', function () { +test('autowire using implemented interface returns service', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('bar') ->setType(Bar::class) @@ -95,7 +95,7 @@ test('Autowiring limited to IFoo interface and its implementations', function () }); -test('Autowiring limited to two interfaces', function () { +test('autowire with multiple types registers for all', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('bar') ->setType(Bar::class) @@ -108,7 +108,7 @@ test('Autowiring limited to two interfaces', function () { }); -test('Autowiring limited to two classes', function () { +test('autowire with redundant types excludes mismatches', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('bar') ->setType(Bar::class) @@ -121,7 +121,7 @@ test('Autowiring limited to two classes', function () { }); -test('Autowiring limited to class and interface', function () { +test('autowire with parent and interface returns service', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('bar') ->setType(Bar::class) @@ -134,7 +134,7 @@ test('Autowiring limited to class and interface', function () { }); -test('Autowiring limited to class and interface', function () { +test('autowire with self and interface returns service', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('bar') ->setType(Bar::class) @@ -147,7 +147,7 @@ test('Autowiring limited to class and interface', function () { }); -test('Distribution between two services with parent-child relation', function () { +test('separate definitions for parent and self types', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('bar') ->setType(Bar::class) @@ -164,7 +164,7 @@ test('Distribution between two services with parent-child relation', function () }); -test('Distribution between two services of same type', function () { +test('prefer autowired service when multiple exist', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('one') ->setType(stdClass::class); @@ -177,7 +177,7 @@ test('Distribution between two services of same type', function () { }); -test('', function () { +test('autowire with override of secondary definition', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('bar') ->setType(Bar::class) @@ -194,7 +194,7 @@ test('', function () { }); -test('', function () { +test('ambiguous autowiring throws exception for multiple services', function () { $builder = new DI\ContainerBuilder; $bar = $builder->addDefinition('bar') ->setType(Bar::class) @@ -221,7 +221,7 @@ test('', function () { }); -test('', function () { +test('incompatible autowired type triggers exception', function () { $builder = new DI\ContainerBuilder; $bar = $builder->addDefinition('bar') ->setType(Foo::class) diff --git a/tests/DI/ContainerBuilder.create.error.phpt b/tests/DI/ContainerBuilder.create.error.phpt index 0af01c12c..6d4ba9a5a 100644 --- a/tests/DI/ContainerBuilder.create.error.phpt +++ b/tests/DI/ContainerBuilder.create.error.phpt @@ -14,20 +14,20 @@ use Nette\DI\Definitions\Statement; require __DIR__ . '/../bootstrap.php'; -testException('', function () { +testException('non-existent class in type causes error', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('one')->setType('X')->setCreator('Unknown'); }, Nette\InvalidArgumentException::class, "Service 'one': Class or interface 'X' not found."); -testException('', function () { +testException('missing class in creator triggers service creation error', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition(null)->setCreator('Unknown'); $builder->complete(); }, Nette\DI\ServiceCreationException::class, "Service (Unknown::__construct()): Class 'Unknown' not found."); -testException('', function () { +testException('undefined class in dependency throws error', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('one')->setCreator('@two'); $builder->addDefinition('two')->setCreator('Unknown'); @@ -35,7 +35,7 @@ testException('', function () { }, Nette\InvalidStateException::class, "Service 'two': Class 'Unknown' not found."); -testException('', function () { +testException('reference to undefined class in dependency causes error', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('one')->setCreator(new Reference('two')); $builder->addDefinition('two')->setCreator('Unknown'); @@ -43,21 +43,21 @@ testException('', function () { }, Nette\InvalidStateException::class, "Service 'two': Class 'Unknown' not found."); -testException('', function () { +testException('non-callable method in creator causes error', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('one')->setCreator('stdClass::foo'); $builder->complete(); }, Nette\InvalidStateException::class, "Service 'one': Method stdClass::foo() is not callable."); -testException('', function () { +testException('uncallable magic method in creator triggers error', 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."); -testException('', function () { +testException('non-existent interface in factory definition causes error', function () { $builder = new DI\ContainerBuilder; $builder->addFactoryDefinition('one') ->setImplement('Unknown'); @@ -70,7 +70,7 @@ interface Bad4 public function create(); } -testException('', function () { +testException('undeclared return type in factory interface triggers error', function () { $builder = new DI\ContainerBuilder; $builder->addFactoryDefinition('one') ->setImplement(Bad4::class); @@ -82,7 +82,7 @@ interface Bad5 public function get($arg); } -testException('', function () { +testException('method with parameters in accessor interface causes error', function () { $builder = new DI\ContainerBuilder; $builder->addAccessorDefinition('one') ->setImplement(Bad5::class); @@ -97,7 +97,7 @@ class Bad6 } } -testException('', function () { +testException('non-callable factory method due to protection level', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('one')->setCreator('Bad6::create'); $builder->complete(); @@ -111,7 +111,7 @@ class Bad7 } } -testException('', function () { +testException('factory method without return type causes unknown service type error', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('one')->setCreator('Bad7::create'); $builder->complete(); @@ -125,7 +125,7 @@ class Bad8 } } -testException('', function () { +testException('private constructor in service type causes error', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('one')->setType(Bad8::class); $builder->complete(); @@ -139,13 +139,13 @@ class Good } } -testException('fail in argument', function () { +testException('unknown class in constructor argument triggers error', 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())"); -testException('fail in argument', function () { +testException('private constructor in argument service causes error', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('one')->setCreator(Good::class, [new Statement(Bad8::class)]); $builder->complete(); @@ -173,7 +173,7 @@ trait Bad10 } } -testException('trait cannot be instantiated', function () { +testException('trait method is not callable as service creator', function () { $builder = new DI\ContainerBuilder; $builder->addDefinition('one')->setCreator('Bad10::method'); $builder->complete(); @@ -194,7 +194,7 @@ class MethodParam } } -testException('autowiring fail', function () { +testException('ambiguous constructor dependency triggers multiple services error', function () { createContainer(new DI\Compiler, ' services: a: stdClass @@ -204,7 +204,7 @@ services: }, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of ConstructorParam): Multiple services of type stdClass found: a, b (required by \$x in ConstructorParam::__construct())"); -testException('forced autowiring fail', function () { +testException('ambiguous constructor dependency via argument reference', function () { createContainer(new DI\Compiler, ' services: a: stdClass @@ -214,7 +214,7 @@ services: }, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of ConstructorParam): Multiple services of type stdClass found: a, b (used in ConstructorParam::__construct())"); -testException('autowiring fail in chain', function () { +testException('ambiguous method parameter dependency triggers error', function () { createContainer(new DI\Compiler, ' services: a: stdClass @@ -224,7 +224,7 @@ services: }, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of MethodParam): Multiple services of type stdClass found: a, b (required by \$x in MethodParam::foo())"); -testException('forced autowiring fail in chain', function () { +testException('ambiguous dependency in method call triggers error', function () { createContainer(new DI\Compiler, ' services: a: stdClass @@ -234,7 +234,7 @@ services: }, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of MethodParam): Multiple services of type stdClass found: a, b (used in foo())"); -testException('autowiring fail in argument', function () { +testException('multiple services in constructor dependency cause ambiguity', function () { createContainer(new DI\Compiler, ' services: a: stdClass @@ -244,7 +244,7 @@ services: }, 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())"); -testException('forced autowiring fail in argument', function () { +testException('ambiguous dependency in constructor argument triggers error', function () { createContainer(new DI\Compiler, ' services: a: stdClass @@ -254,7 +254,7 @@ services: }, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of Good): Multiple services of type stdClass found: a, b (used in ConstructorParam::__construct())"); -testException('autowiring fail in chain in argument', function () { +testException('ambiguous dependency in method parameter causes error', function () { createContainer(new DI\Compiler, ' services: a: stdClass @@ -264,7 +264,7 @@ services: }, 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())"); -testException('forced autowiring fail in chain in argument', function () { +testException('ambiguous dependency in method call triggers error', function () { createContainer(new DI\Compiler, ' services: a: stdClass @@ -274,7 +274,7 @@ services: }, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of Good): Multiple services of type stdClass found: a, b (used in foo())"); -testException('forced autowiring fail in property passing', function () { +testException('ambiguous dependency in property setup triggers error', function () { createContainer(new DI\Compiler, ' services: a: stdClass @@ -287,7 +287,7 @@ services: }, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of Good): Multiple services of type stdClass found: a, b (used in @bad::\$a)"); -testException('autowiring fail in rich property passing', function () { +testException('ambiguous dependency in method setup triggers error', function () { createContainer(new DI\Compiler, ' services: a: stdClass @@ -300,7 +300,7 @@ services: }, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of Good): Multiple services of type stdClass found: a, b (used in foo())"); -testException('autowiring fail in method calling', function () { +testException('ambiguous dependency in method call during setup triggers error', function () { createContainer(new DI\Compiler, ' services: a: stdClass @@ -313,7 +313,7 @@ services: }, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of MethodParam): Multiple services of type stdClass found: a, b (required by \$x in MethodParam::foo())"); -testException('forced autowiring fail in method calling', function () { +testException('ambiguous dependency in method call on service triggers error', function () { createContainer(new DI\Compiler, ' services: a: stdClass @@ -326,7 +326,7 @@ services: }, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of Good): Multiple services of type stdClass found: a, b (used in @bad::bar())"); -testException('autowiring fail in rich method calling', function () { +testException('ambiguous dependency in method call setup triggers error', function () { createContainer(new DI\Compiler, ' services: a: stdClass From ca55ed7ecffdeaee0122765722e9ad9f8254f9a5 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 18 Jun 2025 21:28:51 +0200 Subject: [PATCH 30/56] tests: merges some tests --- tests/DI/Compiler.referenceBug.phpt | 41 ------------------- ...Extension.getInjectProperties().php74.phpt | 39 ------------------ ...Extension.getInjectProperties().php80.phpt | 38 ----------------- ...InjectExtension.getInjectProperties().phpt | 25 ++++++++--- 4 files changed, 19 insertions(+), 124 deletions(-) delete mode 100644 tests/DI/Compiler.referenceBug.phpt delete mode 100644 tests/DI/InjectExtension.getInjectProperties().php74.phpt delete mode 100644 tests/DI/InjectExtension.getInjectProperties().php80.phpt diff --git a/tests/DI/Compiler.referenceBug.phpt b/tests/DI/Compiler.referenceBug.phpt deleted file mode 100644 index 4351067ae..000000000 --- a/tests/DI/Compiler.referenceBug.phpt +++ /dev/null @@ -1,41 +0,0 @@ -args = func_get_args(); - } -} - - -$container = createContainer(new DI\Compiler, ' -services: - - stdClass - a: Lorem(x: true) - b: Lorem(x: Lorem(x: true)) - c: Lorem("@test") -'); - - -Assert::same(['@foo', '@@foo', '@\stdClass', true], $container->getService('a')->args); -Assert::equal(['@foo', '@@foo', '@\stdClass', new Lorem('@foo', '@@foo', '@\stdClass', true)], $container->getService('b')->args); -Assert::same(['@test'], $container->getService('c')->args); 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..80b3793e7 100644 --- a/tests/DI/InjectExtension.getInjectProperties().phpt +++ b/tests/DI/InjectExtension.getInjectProperties().phpt @@ -10,14 +10,14 @@ namespace A { class AClass { - /** @var AInjected @inject */ - public $varA; + /** @var Different @inject */ + public AInjected $varA; /** @var B\BInjected @inject */ public $varB; - /** @var AInjected @inject */ - public $varC; + /** @inject */ + public AInjected $varC; /** @var AInjected */ public $varD; @@ -26,16 +26,23 @@ namespace A class AInjected { } + + class BadClass + { + /** @inject */ + public AClass|\stdClass $var; + } } namespace A\B { use A; + use Nette\DI\Attributes\Inject; class BClass extends A\AClass { - /** @var BInjected @inject */ - public $varF; + #[Inject] + public BInjected $varF; } class BInjected @@ -95,4 +102,10 @@ namespace { 'var3' => C\CInjected::class, 'var4' => C\CInjected::class, ], InjectExtension::getInjectProperties(C\CClass::class)); + + Assert::exception( + fn() => InjectExtension::getInjectProperties(A\BadClass::class), + Nette\InvalidStateException::class, + "Type of property A\\BadClass::\$var is expected to not be nullable/built-in/complex, 'A\\AClass|stdClass' given.", + ); } From 5c1ce07762b1ecebad2b10ac43221e77731929b3 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 Jan 2025 08:31:19 +0100 Subject: [PATCH 31/56] ConnectionPanel: convert templates to Latte-like syntax --- src/Bridges/DITracy/ContainerPanel.php | 4 +- src/Bridges/DITracy/dist/panel.phtml | 87 +++++++++++++++++++ src/Bridges/DITracy/dist/tab.phtml | 10 +++ src/Bridges/DITracy/panel.latte | 84 ++++++++++++++++++ src/Bridges/DITracy/tab.latte | 6 ++ .../templates/ContainerPanel.panel.phtml | 81 ----------------- .../templates/ContainerPanel.tab.phtml | 11 --- 7 files changed, 189 insertions(+), 94 deletions(-) create mode 100644 src/Bridges/DITracy/dist/panel.phtml create mode 100644 src/Bridges/DITracy/dist/tab.phtml create mode 100644 src/Bridges/DITracy/panel.latte create mode 100644 src/Bridges/DITracy/tab.latte delete mode 100644 src/Bridges/DITracy/templates/ContainerPanel.panel.phtml delete mode 100644 src/Bridges/DITracy/templates/ContainerPanel.tab.phtml diff --git a/src/Bridges/DITracy/ContainerPanel.php b/src/Bridges/DITracy/ContainerPanel.php index 82051f931..f0519c3d6 100644 --- a/src/Bridges/DITracy/ContainerPanel.php +++ b/src/Bridges/DITracy/ContainerPanel.php @@ -40,7 +40,7 @@ public function getTab(): string { return Nette\Utils\Helpers::capture(function () { $elapsedTime = $this->elapsedTime; - require __DIR__ . '/templates/ContainerPanel.tab.phtml'; + require __DIR__ . '/dist/tab.phtml'; }); } @@ -76,7 +76,7 @@ public function getPanel(): string $parameters = $rc->getMethod('getStaticParameters')->getDeclaringClass()->getName() === Container::class ? null : $container->getParameters(); - require __DIR__ . '/templates/ContainerPanel.panel.phtml'; + require __DIR__ . '/dist/panel.phtml'; }); } } diff --git a/src/Bridges/DITracy/dist/panel.phtml b/src/Bridges/DITracy/dist/panel.phtml new file mode 100644 index 000000000..8525b5794 --- /dev/null +++ b/src/Bridges/DITracy/dist/panel.phtml @@ -0,0 +1,87 @@ + + + + +

Nette DI Container

+ +
+
+

Source: +

+ + + + + + + + + + + + $type): ?> + + + + + + +
NameAutowiredServiceTags
+ + + + + + + + + + + true, Dumper::LIVE => true, Dumper::DEPTH => 5]) ?> + + + + + + + + =  true]) ?> + + true]) ?> + +
+ +

Parameters

+ +
+ disabled via 'di › export › parameters' + + +
+
+
diff --git a/src/Bridges/DITracy/dist/tab.phtml b/src/Bridges/DITracy/dist/tab.phtml new file mode 100644 index 000000000..f9cb72857 --- /dev/null +++ b/src/Bridges/DITracy/dist/tab.phtml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/src/Bridges/DITracy/panel.latte b/src/Bridges/DITracy/panel.latte new file mode 100644 index 000000000..87d51026e --- /dev/null +++ b/src/Bridges/DITracy/panel.latte @@ -0,0 +1,84 @@ +{use Tracy\Dumper} + + + +

Nette DI Container

+ +
+
+

Source: {Tracy\Helpers::editorLink($file)}

+ + + + + + + + + + + + {foreach $services as $name => $type} + {do $name = (string) $name} + {do $autowired = in_array($name, array_merge($wiring[$type][0] ?? [], $wiring[$type][1] ?? []), strict: true)} + + + + + + + {/foreach} + +
NameAutowiredServiceTags
+ {if is_numeric($name)}{$name}{else}{$name}{/if} + + {$autowired ? yes : (isset($wiring[$type]) ? no : '?')} + + {if isset($instances[$name]) && !$instances[$name] instanceof Nette\DI\Container} + {Dumper::toHtml($instances[$name], [Dumper::COLLAPSE => true, Dumper::LIVE => true, Dumper::DEPTH => 5])} + {elseif isset($instances[$name])} + {get_class($instances[$name])} + {elseif is_string($type)} + {$type} + {/if} + + {if !isset($tags[$name])} + {elseif count($tags[$name]) === 1} + {key($tags[$name])} = {Dumper::toHtml(current($tags[$name]), [Dumper::COLLAPSE => true])} + {else} + {Dumper::toHtml($tags[$name], [Dumper::COLLAPSE => true])} + {/if} +
+ +

Parameters

+ +
+ {if $parameters === null} + disabled via 'di › export › parameters' + {else} + {Dumper::toHtml($parameters)} + {/if} +
+
+
diff --git a/src/Bridges/DITracy/tab.latte b/src/Bridges/DITracy/tab.latte new file mode 100644 index 000000000..42322c5ec --- /dev/null +++ b/src/Bridges/DITracy/tab.latte @@ -0,0 +1,6 @@ + + + + {$elapsedTime ? sprintf('%0.1f ms', $elapsedTime * 1000) : ''} + diff --git a/src/Bridges/DITracy/templates/ContainerPanel.panel.phtml b/src/Bridges/DITracy/templates/ContainerPanel.panel.phtml deleted file mode 100644 index b84022914..000000000 --- a/src/Bridges/DITracy/templates/ContainerPanel.panel.phtml +++ /dev/null @@ -1,81 +0,0 @@ - - - -

Nette DI Container

- -
-
-

Source:

- - - - - - - - - - - - $type): ?> - - - - - - - - - - -
NameAutowiredServiceTags
$name" : Helpers::escapeHtml($name) ?> - - true, Dumper::LIVE => true, Dumper::DEPTH => 5]); ?> - - - - - - true]) - : Dumper::toHtml($tags[$name], [Dumper::COLLAPSE => true]); - } ?>
- -

Parameters

- -
- disabled via 'di › export › parameters'" : Dumper::toHtml($parameters) ?> -
-
-
diff --git a/src/Bridges/DITracy/templates/ContainerPanel.tab.phtml b/src/Bridges/DITracy/templates/ContainerPanel.tab.phtml deleted file mode 100644 index 640319dde..000000000 --- a/src/Bridges/DITracy/templates/ContainerPanel.tab.phtml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - From 6db42d622b6e8bdee8ffc63c8c8353c908c218cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Dobe=C5=A1?= Date: Wed, 18 Jun 2025 20:21:55 +0200 Subject: [PATCH 32/56] used generics for Container::createInstance() (#323) --- src/DI/Container.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/DI/Container.php b/src/DI/Container.php index d72639a63..da8557cc9 100644 --- a/src/DI/Container.php +++ b/src/DI/Container.php @@ -320,6 +320,9 @@ private function preventDeadLock(string $key, \Closure $callback): mixed /** * Creates an instance of the class and passes dependencies to the constructor using autowiring. + * @template T of object + * @param class-string $class + * @return T */ public function createInstance(string $class, array $args = []): object { From 01b29b04e8e4f0552787fb0f3c12e101bde3b99e Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 12 Dec 2024 06:06:50 +0100 Subject: [PATCH 33/56] exception: use natural explanatory style --- src/DI/exceptions.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/DI/exceptions.php b/src/DI/exceptions.php index 83fcb526d..9f13a87d3 100644 --- a/src/DI/exceptions.php +++ b/src/DI/exceptions.php @@ -13,7 +13,7 @@ /** - * Service not found exception. + * The requested service was not found in the container. */ class MissingServiceException extends Nette\InvalidStateException { @@ -21,7 +21,7 @@ class MissingServiceException extends Nette\InvalidStateException /** - * Service creation exception. + * Failed to create the service instance. */ class ServiceCreationException extends Nette\InvalidStateException { @@ -34,7 +34,7 @@ public function setMessage(string $message): static /** - * Not allowed when container is resolving. + * Operation is not allowed while container is resolving dependencies. */ class NotAllowedDuringResolvingException extends Nette\InvalidStateException { @@ -42,7 +42,7 @@ class NotAllowedDuringResolvingException extends Nette\InvalidStateException /** - * Error in configuration. + * The DI container configuration is invalid. */ class InvalidConfigurationException extends Nette\InvalidStateException { From 39accabdbae35d615d85ceaaafa4f4d75d742f67 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 19 Jun 2025 19:10:58 +0200 Subject: [PATCH 34/56] optimized global function calls --- src/Bridges/DITracy/ContainerPanel.php | 1 + src/DI/Autowiring.php | 2 ++ src/DI/Compiler.php | 1 + src/DI/CompilerExtension.php | 1 + src/DI/Config/Adapters/NeonAdapter.php | 1 + src/DI/Config/Helpers.php | 1 + src/DI/Config/Loader.php | 2 ++ src/DI/Container.php | 1 + src/DI/ContainerBuilder.php | 1 + src/DI/ContainerLoader.php | 2 ++ src/DI/Definitions/AccessorDefinition.php | 1 + src/DI/Definitions/Definition.php | 1 + src/DI/Definitions/FactoryDefinition.php | 1 + src/DI/Definitions/LocatorDefinition.php | 1 + src/DI/Definitions/ServiceDefinition.php | 1 + src/DI/Definitions/Statement.php | 1 + src/DI/DependencyChecker.php | 2 ++ src/DI/Extensions/DIExtension.php | 2 ++ src/DI/Extensions/DecoratorExtension.php | 1 + src/DI/Extensions/DefinitionSchema.php | 1 + src/DI/Extensions/ExtensionsExtension.php | 1 + src/DI/Extensions/InjectExtension.php | 1 + src/DI/Extensions/ParametersExtension.php | 1 + src/DI/Extensions/SearchExtension.php | 1 + src/DI/Extensions/ServicesExtension.php | 1 + src/DI/Helpers.php | 2 ++ src/DI/PhpGenerator.php | 1 + src/DI/Resolver.php | 1 + 28 files changed, 34 insertions(+) diff --git a/src/Bridges/DITracy/ContainerPanel.php b/src/Bridges/DITracy/ContainerPanel.php index f0519c3d6..57f4431e9 100644 --- a/src/Bridges/DITracy/ContainerPanel.php +++ b/src/Bridges/DITracy/ContainerPanel.php @@ -12,6 +12,7 @@ use Nette; use Nette\DI\Container; use Tracy; +use const SORT_NATURAL; /** diff --git a/src/DI/Autowiring.php b/src/DI/Autowiring.php index 601f59ed7..3be57cb3a 100644 --- a/src/DI/Autowiring.php +++ b/src/DI/Autowiring.php @@ -9,6 +9,8 @@ namespace Nette\DI; +use function array_merge, class_exists, class_implements, class_parents, count, implode, interface_exists, is_a, is_array, natsort, sprintf, str_contains; + /** * Autowiring. diff --git a/src/DI/Compiler.php b/src/DI/Compiler.php index 80e9d0fb0..6f675174d 100644 --- a/src/DI/Compiler.php +++ b/src/DI/Compiler.php @@ -11,6 +11,7 @@ use Nette; use Nette\Schema; +use function array_diff_key, array_filter, array_keys, array_merge, assert, count, implode, key, sprintf, strtolower; /** diff --git a/src/DI/CompilerExtension.php b/src/DI/CompilerExtension.php index 8d623b891..ed6b17200 100644 --- a/src/DI/CompilerExtension.php +++ b/src/DI/CompilerExtension.php @@ -10,6 +10,7 @@ namespace Nette\DI; use Nette; +use function array_diff_key, array_keys, func_num_args, implode, is_object, is_string, key, sprintf, str_replace, str_starts_with, substr_replace; /** diff --git a/src/DI/Config/Adapters/NeonAdapter.php b/src/DI/Config/Adapters/NeonAdapter.php index fcc2d5459..5b26583b9 100644 --- a/src/DI/Config/Adapters/NeonAdapter.php +++ b/src/DI/Config/Adapters/NeonAdapter.php @@ -15,6 +15,7 @@ use Nette\DI\Definitions\Statement; use Nette\Neon; use Nette\Neon\Node; +use function array_walk_recursive, constant, count, defined, implode, is_array, is_string, ltrim, preg_match, preg_replace, sprintf, str_contains, str_ends_with, str_starts_with, substr; /** diff --git a/src/DI/Config/Helpers.php b/src/DI/Config/Helpers.php index 9124a5236..3f47edbd9 100644 --- a/src/DI/Config/Helpers.php +++ b/src/DI/Config/Helpers.php @@ -10,6 +10,7 @@ namespace Nette\DI\Config; use Nette; +use function is_array; /** diff --git a/src/DI/Config/Loader.php b/src/DI/Config/Loader.php index cd845f019..5bfb0d185 100644 --- a/src/DI/Config/Loader.php +++ b/src/DI/Config/Loader.php @@ -11,6 +11,8 @@ use Nette; use Nette\Utils\Validators; +use function array_unique, dirname, is_file, is_object, is_readable, pathinfo, preg_match, sprintf, strtolower; +use const PATHINFO_EXTENSION; /** diff --git a/src/DI/Container.php b/src/DI/Container.php index da8557cc9..db03ae246 100644 --- a/src/DI/Container.php +++ b/src/DI/Container.php @@ -10,6 +10,7 @@ namespace Nette\DI; use Nette; +use function array_flip, array_key_exists, array_keys, array_map, array_merge, array_values, class_exists, count, get_class_methods, implode, interface_exists, is_a, is_object, natsort, sprintf, str_replace, ucfirst; /** diff --git a/src/DI/ContainerBuilder.php b/src/DI/ContainerBuilder.php index f2844d508..617ec0dbf 100644 --- a/src/DI/ContainerBuilder.php +++ b/src/DI/ContainerBuilder.php @@ -11,6 +11,7 @@ use Nette; use Nette\DI\Definitions\Definition; +use function array_diff, array_filter, array_walk_recursive, class_implements, class_parents, is_a, is_int, key, ksort, preg_match, sprintf, strtolower; /** diff --git a/src/DI/ContainerLoader.php b/src/DI/ContainerLoader.php index 8162013d7..3b9acf4f9 100644 --- a/src/DI/ContainerLoader.php +++ b/src/DI/ContainerLoader.php @@ -10,6 +10,8 @@ namespace Nette\DI; use Nette; +use function class_exists, file_get_contents, file_put_contents, flock, fopen, function_exists, hash, is_file, rename, serialize, sprintf, strlen, substr, unlink, unserialize; +use const LOCK_EX, LOCK_UN; /** diff --git a/src/DI/Definitions/AccessorDefinition.php b/src/DI/Definitions/AccessorDefinition.php index bd6c1a1a5..7900de605 100644 --- a/src/DI/Definitions/AccessorDefinition.php +++ b/src/DI/Definitions/AccessorDefinition.php @@ -12,6 +12,7 @@ use Nette; use Nette\DI\Helpers; use Nette\Utils\Type; +use function count, interface_exists, sprintf, str_starts_with, substr; /** diff --git a/src/DI/Definitions/Definition.php b/src/DI/Definitions/Definition.php index ca96659b8..6183f0a6f 100644 --- a/src/DI/Definitions/Definition.php +++ b/src/DI/Definitions/Definition.php @@ -10,6 +10,7 @@ namespace Nette\DI\Definitions; use Nette; +use function class_exists, interface_exists, is_array, is_string, sprintf; /** diff --git a/src/DI/Definitions/FactoryDefinition.php b/src/DI/Definitions/FactoryDefinition.php index 7c2e69caa..9518ab91b 100644 --- a/src/DI/Definitions/FactoryDefinition.php +++ b/src/DI/Definitions/FactoryDefinition.php @@ -14,6 +14,7 @@ use Nette\DI\ServiceCreationException; use Nette\PhpGenerator as Php; use Nette\Utils\Type; +use function array_keys, array_map, count, implode, interface_exists, is_string, serialize, sprintf, str_replace, unserialize; /** diff --git a/src/DI/Definitions/LocatorDefinition.php b/src/DI/Definitions/LocatorDefinition.php index a9e3f735a..a26296d7b 100644 --- a/src/DI/Definitions/LocatorDefinition.php +++ b/src/DI/Definitions/LocatorDefinition.php @@ -10,6 +10,7 @@ namespace Nette\DI\Definitions; use Nette; +use function array_map, interface_exists, lcfirst, preg_match, sprintf, str_starts_with, substr; /** diff --git a/src/DI/Definitions/ServiceDefinition.php b/src/DI/Definitions/ServiceDefinition.php index ff934c26a..b41fe8574 100644 --- a/src/DI/Definitions/ServiceDefinition.php +++ b/src/DI/Definitions/ServiceDefinition.php @@ -12,6 +12,7 @@ use Nette; use Nette\DI\ServiceCreationException; use Nette\Utils\Strings; +use function array_pop, class_exists, class_parents, count, implode, is_string, preg_grep, serialize, strpbrk, unserialize; /** diff --git a/src/DI/Definitions/Statement.php b/src/DI/Definitions/Statement.php index cd80f6d44..27aebe3da 100644 --- a/src/DI/Definitions/Statement.php +++ b/src/DI/Definitions/Statement.php @@ -10,6 +10,7 @@ namespace Nette\DI\Definitions; use Nette; +use function array_keys, class_exists, explode, is_array, is_string, str_contains, str_starts_with, substr; /** diff --git a/src/DI/DependencyChecker.php b/src/DI/DependencyChecker.php index 94eb9fdc8..b58cb52e4 100644 --- a/src/DI/DependencyChecker.php +++ b/src/DI/DependencyChecker.php @@ -13,6 +13,8 @@ use Nette\Utils\Reflection; use ReflectionClass; use ReflectionMethod; +use function array_combine, array_flip, array_keys, array_map, array_merge, array_unique, class_implements, class_parents, class_uses, count, get_debug_type, get_parent_class, hash, is_object, is_string, rtrim, serialize, sprintf, str_contains; +use const PHP_VERSION_ID, SORT_REGULAR; /** diff --git a/src/DI/Extensions/DIExtension.php b/src/DI/Extensions/DIExtension.php index 99e9de0f1..0bcf76948 100644 --- a/src/DI/Extensions/DIExtension.php +++ b/src/DI/Extensions/DIExtension.php @@ -12,6 +12,8 @@ use Nette; use Nette\DI\Definitions\ServiceDefinition; use Tracy; +use function array_flip, array_intersect_key, is_array, microtime; +use const PHP_VERSION_ID; /** diff --git a/src/DI/Extensions/DecoratorExtension.php b/src/DI/Extensions/DecoratorExtension.php index 0725d8086..00c2596bc 100644 --- a/src/DI/Extensions/DecoratorExtension.php +++ b/src/DI/Extensions/DecoratorExtension.php @@ -12,6 +12,7 @@ use Nette; use Nette\DI\Definitions; use Nette\Schema\Expect; +use function array_filter, array_values, class_exists, interface_exists, is_a, is_array, key, sprintf; /** diff --git a/src/DI/Extensions/DefinitionSchema.php b/src/DI/Extensions/DefinitionSchema.php index 7935324e8..97a16166a 100644 --- a/src/DI/Extensions/DefinitionSchema.php +++ b/src/DI/Extensions/DefinitionSchema.php @@ -16,6 +16,7 @@ use Nette\Schema\Context; use Nette\Schema\Expect; use Nette\Schema\Schema; +use function array_keys, end, get_class, interface_exists, is_array, is_string, method_exists, preg_match, substr; /** diff --git a/src/DI/Extensions/ExtensionsExtension.php b/src/DI/Extensions/ExtensionsExtension.php index 591ad4fda..026680138 100644 --- a/src/DI/Extensions/ExtensionsExtension.php +++ b/src/DI/Extensions/ExtensionsExtension.php @@ -10,6 +10,7 @@ namespace Nette\DI\Extensions; use Nette; +use function is_a, is_int, sprintf; /** diff --git a/src/DI/Extensions/InjectExtension.php b/src/DI/Extensions/InjectExtension.php index 4c40b30c0..278f1019a 100644 --- a/src/DI/Extensions/InjectExtension.php +++ b/src/DI/Extensions/InjectExtension.php @@ -13,6 +13,7 @@ use Nette\DI; use Nette\DI\Definitions; use Nette\Utils\Reflection; +use function array_keys, array_reverse, array_search, array_unshift, get_class_methods, is_a, is_subclass_of, ksort, sprintf, str_starts_with, uksort; /** diff --git a/src/DI/Extensions/ParametersExtension.php b/src/DI/Extensions/ParametersExtension.php index 144554d5f..1d2f3365d 100644 --- a/src/DI/Extensions/ParametersExtension.php +++ b/src/DI/Extensions/ParametersExtension.php @@ -12,6 +12,7 @@ use Nette; use Nette\DI\DynamicParameter; use Nette\DI\Helpers; +use function array_diff_key, array_fill_keys, array_keys, array_walk_recursive, implode, var_export; /** diff --git a/src/DI/Extensions/SearchExtension.php b/src/DI/Extensions/SearchExtension.php index 0be9a8b25..4739b3777 100644 --- a/src/DI/Extensions/SearchExtension.php +++ b/src/DI/Extensions/SearchExtension.php @@ -13,6 +13,7 @@ use Nette\Loaders\RobotLoader; use Nette\Schema\Expect; use Nette\Utils\Arrays; +use function array_filter, array_keys, array_merge, array_unique, class_exists, count, implode, in_array, interface_exists, is_dir, is_string, method_exists, preg_match, preg_quote, sprintf, str_contains, str_replace, trait_exists; /** diff --git a/src/DI/Extensions/ServicesExtension.php b/src/DI/Extensions/ServicesExtension.php index 38d7d47af..dc69ec4a7 100644 --- a/src/DI/Extensions/ServicesExtension.php +++ b/src/DI/Extensions/ServicesExtension.php @@ -13,6 +13,7 @@ use Nette\DI\Definitions; use Nette\DI\Definitions\Statement; use Nette\DI\Helpers; +use function array_replace, array_values, is_array, is_int, is_string, key, preg_match, substr; /** diff --git a/src/DI/Helpers.php b/src/DI/Helpers.php index 48e421fbf..51bcda464 100644 --- a/src/DI/Helpers.php +++ b/src/DI/Helpers.php @@ -14,6 +14,8 @@ use Nette\DI\Definitions\Statement; use Nette\Utils\Reflection; use Nette\Utils\Type; +use function array_key_exists, array_keys, array_shift, class_exists, explode, get_debug_type, implode, interface_exists, is_array, is_scalar, is_string, preg_match, preg_quote, preg_replace, preg_split, settype, sprintf, str_replace, strlen, strncmp, substr, trim, ucfirst, var_export; +use const PREG_SPLIT_DELIM_CAPTURE; /** diff --git a/src/DI/PhpGenerator.php b/src/DI/PhpGenerator.php index b5176c40d..8c02cb236 100644 --- a/src/DI/PhpGenerator.php +++ b/src/DI/PhpGenerator.php @@ -13,6 +13,7 @@ use Nette\DI\Definitions\Reference; use Nette\DI\Definitions\Statement; use Nette\PhpGenerator as Php; +use function array_walk_recursive, is_array, is_object, is_string, ksort, sprintf, str_contains, str_ends_with, str_starts_with, substr; /** diff --git a/src/DI/Resolver.php b/src/DI/Resolver.php index ceab3d135..9dd4339f1 100644 --- a/src/DI/Resolver.php +++ b/src/DI/Resolver.php @@ -18,6 +18,7 @@ use Nette\Utils\Callback; use Nette\Utils\Reflection; use Nette\Utils\Validators; +use function array_filter, array_key_exists, array_map, array_merge, array_values, array_walk_recursive, assert, class_exists, count, ctype_digit, explode, function_exists, gettype, implode, in_array, interface_exists, is_a, is_array, is_int, is_scalar, is_string, iterator_to_array, ltrim, preg_match, preg_replace, sprintf, str_contains, str_ends_with, str_replace, str_starts_with, strlen, substr; /** From 7cdef7115c70406e732126eb5650ef9bb1d96372 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 18 Jun 2025 20:28:44 +0200 Subject: [PATCH 35/56] opened 3.3-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index cbf4588ac..73d99d8dc 100644 --- a/composer.json +++ b/composer.json @@ -42,7 +42,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.3-dev" } } } From 8990bb852efc1d9915857cf27ce921a68e29342f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 18 Jun 2025 21:20:11 +0200 Subject: [PATCH 36/56] uses nette/schema 1.3 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 73d99d8dc..2a9b6241f 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "nette/neon": "^3.3", "nette/php-generator": "^4.1.6", "nette/robot-loader": "^4.0", - "nette/schema": "^1.2.5", + "nette/schema": "^1.3", "nette/utils": "^4.0" }, "require-dev": { From 499e74477ed72f1aa72139a25359d0bdac6dff9d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 18 Jun 2025 21:20:24 +0200 Subject: [PATCH 37/56] uses nette/neon 3.4 --- composer.json | 2 +- src/DI/Config/Adapters/NeonAdapter.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 2a9b6241f..f0a143343 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "php": "8.1 - 8.4", "ext-tokenizer": "*", "ext-ctype": "*", - "nette/neon": "^3.3", + "nette/neon": "^3.4", "nette/php-generator": "^4.1.6", "nette/robot-loader": "^4.0", "nette/schema": "^1.3", diff --git a/src/DI/Config/Adapters/NeonAdapter.php b/src/DI/Config/Adapters/NeonAdapter.php index 5b26583b9..9bc2b63b4 100644 --- a/src/DI/Config/Adapters/NeonAdapter.php +++ b/src/DI/Config/Adapters/NeonAdapter.php @@ -112,7 +112,7 @@ function (&$val): void { } }, ); - return "# generated by Nette\n\n" . Neon\Neon::encode($data, Neon\Neon::BLOCK); + return "# generated by Nette\n\n" . Neon\Neon::encode($data, blockMode: true); } From 4563395ce04b21251cbe06e354324d49abd8f72c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 29 Nov 2024 19:25:33 +0100 Subject: [PATCH 38/56] used attribute Deprecated --- src/DI/ContainerBuilder.php | 4 ++-- src/DI/Definitions/Reference.php | 2 +- src/DI/DependencyChecker.php | 2 +- src/DI/Extensions/InjectExtension.php | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/DI/ContainerBuilder.php b/src/DI/ContainerBuilder.php index 617ec0dbf..2895f4f4c 100644 --- a/src/DI/ContainerBuilder.php +++ b/src/DI/ContainerBuilder.php @@ -23,10 +23,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 25625dec7..a23552003 100644 --- a/src/DI/Definitions/Reference.php +++ b/src/DI/Definitions/Reference.php @@ -17,7 +17,7 @@ final class Reference { 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 b58cb52e4..0fefd3e0d 100644 --- a/src/DI/DependencyChecker.php +++ b/src/DI/DependencyChecker.php @@ -24,7 +24,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 278f1019a..50f7982a3 100644 --- a/src/DI/Extensions/InjectExtension.php +++ b/src/DI/Extensions/InjectExtension.php @@ -23,7 +23,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; From 6f686611a9b2766ae1e798ec7d5047523b918661 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 2 Dec 2024 00:10:08 +0100 Subject: [PATCH 39/56] 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 | 10 +++++----- 6 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/DI/Definitions/AccessorDefinition.php b/src/DI/Definitions/AccessorDefinition.php index 7900de605..ba49ab49c 100644 --- a/src/DI/Definitions/AccessorDefinition.php +++ b/src/DI/Definitions/AccessorDefinition.php @@ -108,7 +108,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()); @@ -124,6 +124,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 6183f0a6f..da5568960 100644 --- a/src/DI/Definitions/Definition.php +++ b/src/DI/Definitions/Definition.php @@ -148,7 +148,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 @@ -160,6 +160,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 9518ab91b..f97e9ee7b 100644 --- a/src/DI/Definitions/FactoryDefinition.php +++ b/src/DI/Definitions/FactoryDefinition.php @@ -197,7 +197,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()); @@ -219,7 +219,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 e9116653b..cc8caf70e 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 a26296d7b..c3601d858 100644 --- a/src/DI/Definitions/LocatorDefinition.php +++ b/src/DI/Definitions/LocatorDefinition.php @@ -128,7 +128,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()); @@ -172,6 +172,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 b41fe8574..d936ff04f 100644 --- a/src/DI/Definitions/ServiceDefinition.php +++ b/src/DI/Definitions/ServiceDefinition.php @@ -182,7 +182,7 @@ 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 { $lines = []; foreach ([$this->creator, ...$this->setup] as $stmt) { @@ -194,15 +194,15 @@ public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGe $lines[0] = (new \ReflectionClass($class))->hasMethod('__construct') ? $generator->formatPhp("\$service->__construct(...?:);\n", [$this->creator->arguments]) : ''; - $method->setBody("return new ReflectionClass($class::class)->newLazyGhost(function (\$service) {\n" + return "return new ReflectionClass($class::class)->newLazyGhost(function (\$service) {\n" . Strings::indent(implode('', $lines)) - . '});'); + . '});'; } elseif (count($lines) === 1) { - $method->setBody('return ' . $lines[0]); + return 'return ' . $lines[0]; } else { - $method->setBody('$service = ' . implode('', $lines) . 'return $service;'); + return '$service = ' . implode('', $lines) . 'return $service;'; } } From a8eb347ff35c23bdcd4fd63c15ad4e8446370f7c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 1 Dec 2024 23:49:52 +0100 Subject: [PATCH 40/56] Resolver: used withCurrentServiceAvailable() to control $currentServiceAllowed --- src/DI/Definitions/ServiceDefinition.php | 2 +- src/DI/Resolver.php | 42 ++++++++++++++---------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/DI/Definitions/ServiceDefinition.php b/src/DI/Definitions/ServiceDefinition.php index d936ff04f..bf16685e2 100644 --- a/src/DI/Definitions/ServiceDefinition.php +++ b/src/DI/Definitions/ServiceDefinition.php @@ -169,7 +169,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 9dd4339f1..93740bdd4 100644 --- a/src/DI/Resolver.php +++ b/src/DI/Resolver.php @@ -43,6 +43,26 @@ 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(); + $dolly->currentServiceAllowed = false; + return $dolly; + } + + + public function withCurrentServiceAvailable(): self + { + $dolly = clone $this; + $dolly->currentServiceAllowed = true; + return $dolly; + } + + public function getContainerBuilder(): ContainerBuilder { return $this->builder; @@ -60,7 +80,6 @@ public function resolveDefinition(Definition $def): void $this->recursive->attach($def); $def->resolveType($this); - if (!$def->getType()) { throw new ServiceCreationException('Type of service is unknown.'); } @@ -160,29 +179,18 @@ 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 @@ -195,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; @@ -269,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 @@ -330,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 eb6f8df8a0adf6674ab4cf541b196eca36be3a30 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 2 Dec 2024 06:11:21 +0100 Subject: [PATCH 41/56] 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 42/56] PhpGenerator::formatStatement() moved to Statement & Reference --- src/DI/Definitions/Expression.php | 1 + src/DI/Definitions/Reference.php | 12 ++++ src/DI/Definitions/ServiceDefinition.php | 2 +- src/DI/Definitions/Statement.php | 57 +++++++++++++++++++ src/DI/PhpGenerator.php | 72 ++---------------------- 5 files changed, 77 insertions(+), 67 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 d3ba5650c..086e210e4 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 bf16685e2..bdc20b7dc 100644 --- a/src/DI/Definitions/ServiceDefinition.php +++ b/src/DI/Definitions/ServiceDefinition.php @@ -186,7 +186,7 @@ public function generateCode(Nette\DI\PhpGenerator $generator): string { $lines = []; foreach ([$this->creator, ...$this->setup] as $stmt) { - $lines[] = $generator->formatStatement($stmt) . ";\n"; + $lines[] = $stmt->generateCode($generator) . ";\n"; } if ($this->canBeLazy() && !preg_grep('#(?:func_get_arg|func_num_args)#i', $lines)) { // latteFactory workaround diff --git a/src/DI/Definitions/Statement.php b/src/DI/Definitions/Statement.php index e91579092..23018621d 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; use function array_keys, class_exists, explode, is_array, is_string, str_contains, str_starts_with, substr; @@ -63,6 +65,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 8c02cb236..35788b294 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; use function array_walk_recursive, is_array, is_object, is_string, ksort, sprintf, str_contains, str_ends_with, str_starts_with, substr; @@ -104,58 +102,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); } @@ -172,18 +122,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 06a2a7093b12aea4fabcfebe6cba7477d918f805 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 1 Dec 2024 19:46:30 +0100 Subject: [PATCH 43/56] 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 f97e9ee7b..4abae2c11 100644 --- a/src/DI/Definitions/FactoryDefinition.php +++ b/src/DI/Definitions/FactoryDefinition.php @@ -144,7 +144,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 086e210e4..af54fc18b 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 bdc20b7dc..8ddcd1180 100644 --- a/src/DI/Definitions/ServiceDefinition.php +++ b/src/DI/Definitions/ServiceDefinition.php @@ -142,7 +142,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 23018621d..95ec858be 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; use function array_keys, class_exists, explode, is_array, is_string, str_contains, str_starts_with, substr; @@ -67,6 +70,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 50f7982a3..3b3461a65 100644 --- a/src/DI/Extensions/InjectExtension.php +++ b/src/DI/Extensions/InjectExtension.php @@ -50,7 +50,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 93740bdd4..9870a7f41 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; @@ -63,6 +64,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; @@ -92,91 +99,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 decab76ec91b5aaada6ae1e0c785a5b270745d0b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 1 Dec 2024 21:06:01 +0100 Subject: [PATCH 44/56] 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 2895f4f4c..46f8c9e79 100644 --- a/src/DI/ContainerBuilder.php +++ b/src/DI/ContainerBuilder.php @@ -395,8 +395,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 ba49ab49c..51a55ef91 100644 --- a/src/DI/Definitions/AccessorDefinition.php +++ b/src/DI/Definitions/AccessorDefinition.php @@ -104,7 +104,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 c3601d858..9b77c7945 100644 --- a/src/DI/Definitions/LocatorDefinition.php +++ b/src/DI/Definitions/LocatorDefinition.php @@ -122,8 +122,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 af54fc18b..b10dd6745 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 8ddcd1180..eb5b46b57 100644 --- a/src/DI/Definitions/ServiceDefinition.php +++ b/src/DI/Definitions/ServiceDefinition.php @@ -162,14 +162,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 95ec858be..efee81d42 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; use function array_keys, class_exists, explode, is_array, is_string, str_contains, str_starts_with, substr; @@ -72,7 +73,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; @@ -138,6 +139,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 9870a7f41..d1c446089 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; use function array_filter, array_key_exists, array_map, array_merge, array_values, array_walk_recursive, assert, class_exists, count, ctype_digit, explode, function_exists, gettype, implode, in_array, interface_exists, is_a, is_array, is_int, is_scalar, is_string, iterator_to_array, ltrim, preg_match, preg_replace, sprintf, str_contains, str_ends_with, str_replace, str_starts_with, strlen, substr; @@ -44,7 +40,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 f36eb1d1052fc128e3024718be4bf24a0e8842cb Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 2 Dec 2024 05:27:24 +0100 Subject: [PATCH 45/56] NeonAdapter: processing of 'prevent merging' and 'entity to statement' moved to visitors --- src/DI/Config/Adapters/NeonAdapter.php | 135 ++++++++++++++++--------- 1 file changed, 90 insertions(+), 45 deletions(-) diff --git a/src/DI/Config/Adapters/NeonAdapter.php b/src/DI/Config/Adapters/NeonAdapter.php index 9bc2b63b4..50578c7c6 100644 --- a/src/DI/Config/Adapters/NeonAdapter.php +++ b/src/DI/Config/Adapters/NeonAdapter.php @@ -25,6 +25,7 @@ final class NeonAdapter implements Nette\DI\Config\Adapter { private const PreventMergingSuffix = '!'; private string $file; + private \WeakMap $parents; /** @@ -41,61 +42,23 @@ public function load(string $file): array $decoder = new Neon\Decoder; $node = $decoder->parseToNode($input); $traverser = new Neon\Traverser; + $node = $traverser->traverse($node, $this->deprecatedQuestionMarkVisitor(...)); $node = $traverser->traverse($node, $this->firstClassCallableVisitor(...)); $node = $traverser->traverse($node, $this->removeUnderscoreVisitor(...)); $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; } @@ -165,6 +128,71 @@ private function firstClassCallableVisitor(Node $node): void } + private function preventMergingVisitor(Node $node): void + { + if ($node instanceof Node\ArrayItemNode + && $node->key instanceof Node\LiteralNode + && is_string($node->key->value) + && str_ends_with($node->key->value, self::PreventMergingSuffix) + ) { + if ($node->value instanceof Node\LiteralNode && $node->value->value === null) { + $node->value = new Node\InlineArrayNode('['); + } elseif (!$node->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')", + $node->key->value, + $this->file, + )); + } + + $node->key->value = substr($node->key->value, 0, -1); + $node->value->items[] = $item = new Node\ArrayItemNode; + $item->key = new Node\LiteralNode(DI\Config\Helpers::PREVENT_MERGING); + $item->value = new Node\LiteralNode(true); + } + } + + + private function deprecatedQuestionMarkVisitor(Node $node): void + { + if ($node instanceof Node\EntityNode + && ($node->value instanceof Node\LiteralNode || $node->value instanceof Node\StringNode) + && is_string($node->value->value) + && str_contains($node->value->value, '?') + ) { + throw new Nette\DI\InvalidConfigurationException("Operator ? is deprecated in config file (used in '$this->file')"); + } + } + + + 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(); + return new Statement( + $chain ? [$this->buildExpression($chain), ltrim($entity->value, ':')] : $entity->value, + $entity->attributes, + ); + } + + private function removeUnderscoreVisitor(Node $node): void { if (!$node instanceof Node\EntityNode) { @@ -241,4 +269,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 9c7619c39d404aa306d50276697c629adffe40ad Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 2 Dec 2024 02:17:53 +0100 Subject: [PATCH 46/56] added FunctionCallable & MethodCallable, expressions representing first-class callables --- src/DI/Config/Adapters/NeonAdapter.php | 48 ++++++++++++------- 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, 133 insertions(+), 42 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 50578c7c6..9363cf67b 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; @@ -43,7 +44,6 @@ public function load(string $file): array $node = $decoder->parseToNode($input); $traverser = new Neon\Traverser; $node = $traverser->traverse($node, $this->deprecatedQuestionMarkVisitor(...)); - $node = $traverser->traverse($node, $this->firstClassCallableVisitor(...)); $node = $traverser->traverse($node, $this->removeUnderscoreVisitor(...)); $node = $traverser->traverse($node, $this->convertAtSignVisitor(...)); $node = $traverser->traverse($node, $this->deprecatedParametersVisitor(...)); @@ -115,19 +115,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\ArrayItemNode @@ -182,14 +169,37 @@ 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(); - return new Statement( + $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 === '...'; } @@ -211,7 +221,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 efee81d42..9c65b3953 100644 --- a/src/DI/Definitions/Statement.php +++ b/src/DI/Definitions/Statement.php @@ -75,10 +75,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]) { @@ -146,15 +143,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 d1c446089..11a8851c0 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 1852e21b1ae9aa0f8006ce126b7b8187d5efd06c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 Sep 2021 23:09:50 +0200 Subject: [PATCH 47/56] opened 4.0-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f0a143343..f2c32ea54 100644 --- a/composer.json +++ b/composer.json @@ -42,7 +42,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.0-dev" } } } From b95b0b05a1805164f7d8d86a8b7a723f5de7e4e4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 2 Dec 2024 05:53:18 +0100 Subject: [PATCH 48/56] 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 51a55ef91..6d15f160a 100644 --- a/src/DI/Definitions/AccessorDefinition.php +++ b/src/DI/Definitions/AccessorDefinition.php @@ -29,8 +29,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, )); } @@ -45,19 +45,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 da5568960..c88984d1d 100644 --- a/src/DI/Definitions/Definition.php +++ b/src/DI/Definitions/Definition.php @@ -47,6 +47,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) { @@ -57,8 +80,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 4abae2c11..31bf5a141 100644 --- a/src/DI/Definitions/FactoryDefinition.php +++ b/src/DI/Definitions/FactoryDefinition.php @@ -37,8 +37,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, )); } @@ -47,13 +47,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); } @@ -106,7 +106,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 9b77c7945..89e744613 100644 --- a/src/DI/Definitions/LocatorDefinition.php +++ b/src/DI/Definitions/LocatorDefinition.php @@ -26,12 +26,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) { @@ -40,8 +40,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, )); @@ -51,6 +51,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, ); } @@ -111,8 +112,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 eb5b46b57..8807fd714 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 9c65b3953..148e3e99a 100644 --- a/src/DI/Definitions/Statement.php +++ b/src/DI/Definitions/Statement.php @@ -243,8 +243,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 3b3461a65..bd95666ac 100644 --- a/src/DI/Extensions/InjectExtension.php +++ b/src/DI/Extensions/InjectExtension.php @@ -68,7 +68,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); } @@ -149,7 +149,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); } } @@ -160,11 +160,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 dc69ec4a7..04d7cf835 100644 --- a/src/DI/Extensions/ServicesExtension.php +++ b/src/DI/Extensions/ServicesExtension.php @@ -54,7 +54,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); @@ -69,7 +69,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 51bcda464..c400a19c3 100644 --- a/src/DI/Helpers.php +++ b/src/DI/Helpers.php @@ -234,17 +234,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; @@ -284,4 +290,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 35788b294..f5cbe8a17 100644 --- a/src/DI/PhpGenerator.php +++ b/src/DI/PhpGenerator.php @@ -97,7 +97,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 11a8851c0..8c3ffbaee 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 6d4ba9a5a..65f0415a0 100644 --- a/tests/DI/ContainerBuilder.create.error.phpt +++ b/tests/DI/ContainerBuilder.create.error.phpt @@ -17,14 +17,16 @@ require __DIR__ . '/../bootstrap.php'; testException('non-existent class in type causes error', 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('missing class in creator triggers service creation error', 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('undefined class in dependency throws error', function () { @@ -32,7 +34,8 @@ testException('undefined class in dependency throws error', 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('reference to undefined class in dependency causes error', function () { @@ -40,28 +43,32 @@ testException('reference to undefined class in dependency causes error', functio $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('non-callable method in creator causes error', 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('uncallable magic method in creator triggers error', 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('non-existent interface in factory definition causes error', 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('undeclared return type in factory interface triggers error', func $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('method with parameters in accessor interface causes error', funct $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('non-callable factory method due to protection level', 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('factory method without return type causes unknown service type er $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('private constructor in service type causes error', 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('unknown class in constructor argument triggers error', 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('private constructor in argument service causes error', 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 method is not callable as service creator', 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('ambiguous constructor dependency via argument reference', 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('ambiguous method parameter dependency triggers error', 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('ambiguous dependency in method call triggers error', 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('multiple services in constructor dependency cause ambiguity', 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('ambiguous dependency in constructor argument triggers error', 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('ambiguous dependency in method parameter causes error', 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('ambiguous dependency in method call triggers error', 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('ambiguous dependency in property setup triggers error', 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('ambiguous dependency in method setup triggers error', 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('ambiguous dependency in method call during setup triggers error', 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('ambiguous dependency in method call on service triggers error', 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('ambiguous dependency in method call setup triggers error', 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 f7bd6c0048f4b195f2d78b41afb65049537487c2 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 2 Dec 2024 00:21:11 +0100 Subject: [PATCH 49/56] 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 148e3e99a..f29424429 100644 --- a/src/DI/Definitions/Statement.php +++ b/src/DI/Definitions/Statement.php @@ -101,20 +101,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 c400a19c3..afb37f5ed 100644 --- a/src/DI/Helpers.php +++ b/src/DI/Helpers.php @@ -220,20 +220,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 50/56] 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 +- ...InjectExtension.getInjectProperties().phpt | 125 +++++------------- ...xtension.getInjectProperties().traits.phpt | 4 +- 6 files changed, 58 insertions(+), 121 deletions(-) diff --git a/src/DI/Extensions/InjectExtension.php b/src/DI/Extensions/InjectExtension.php index bd95666ac..51cf4c909 100644 --- a/src/DI/Extensions/InjectExtension.php +++ b/src/DI/Extensions/InjectExtension.php @@ -118,19 +118,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().phpt b/tests/DI/InjectExtension.getInjectProperties().phpt index 80b3793e7..65bf65a04 100644 --- a/tests/DI/InjectExtension.getInjectProperties().phpt +++ b/tests/DI/InjectExtension.getInjectProperties().phpt @@ -6,106 +6,51 @@ declare(strict_types=1); -namespace A +use Nette\DI\Attributes\Inject; +use Nette\DI\Extensions\InjectExtension; +use Tester\Assert; + + +class AClass { - class AClass - { - /** @var Different @inject */ - public AInjected $varA; - - /** @var B\BInjected @inject */ - public $varB; - - /** @inject */ - public AInjected $varC; - - /** @var AInjected */ - public $varD; - } - - class AInjected - { - } - - class BadClass - { - /** @inject */ - public AClass|\stdClass $var; - } + /** @inject */ + public AInjected $varA; + + /** @inject */ + public BInjected $varB; + + public $varD; + + #[Inject] + public stdClass $varF; } -namespace A\B +class BadClass { - use A; - use Nette\DI\Attributes\Inject; - - class BClass extends A\AClass - { - #[Inject] - public BInjected $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; +class BInjected +{ +} - /** @var B\BInjected @inject */ - public $var2; - /** @var CAlias @inject */ - public $var3; +require __DIR__ . '/../bootstrap.php'; - /** @var CInjected @inject */ - public $var4; - } - class CInjected - { - } -} +Assert::same([ + 'varA' => AInjected::class, + 'varB' => BInjected::class, + 'varF' => stdClass::class, +], InjectExtension::getInjectProperties(AClass::class)); -namespace { - use Nette\DI\Extensions\InjectExtension; - use Tester\Assert; - - 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([ - '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(A\BadClass::class), - Nette\InvalidStateException::class, - "Type of property A\\BadClass::\$var is expected to not be nullable/built-in/complex, 'A\\AClass|stdClass' given.", - ); -} +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 314762d9ecf34dd5129f6a29a5bb8df7279b8840 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 1 Dec 2024 22:15:47 +0100 Subject: [PATCH 51/56] 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 c88984d1d..5ffa59029 100644 --- a/src/DI/Definitions/Definition.php +++ b/src/DI/Definitions/Definition.php @@ -171,7 +171,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 @@ -183,13 +183,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 31bf5a141..166d54d09 100644 --- a/src/DI/Definitions/FactoryDefinition.php +++ b/src/DI/Definitions/FactoryDefinition.php @@ -209,8 +209,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 f5cbe8a17..239baa356 100644 --- a/src/DI/PhpGenerator.php +++ b/src/DI/PhpGenerator.php @@ -93,7 +93,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 73c5d17b438d532d9b8639fe8c4f6f69d5c6b480 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 11 Dec 2023 17:12:33 +0100 Subject: [PATCH 52/56] 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 9363cf67b..5293e7389 100644 --- a/src/DI/Config/Adapters/NeonAdapter.php +++ b/src/DI/Config/Adapters/NeonAdapter.php @@ -220,15 +220,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 d01239d4f2f79cf371ff65413ee23f43a70a66b1 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 20 Dec 2022 00:31:03 +0100 Subject: [PATCH 53/56] 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 8807fd714..1874e5577 100644 --- a/src/DI/Definitions/ServiceDefinition.php +++ b/src/DI/Definitions/ServiceDefinition.php @@ -239,6 +239,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 f29424429..b9650e469 100644 --- a/src/DI/Definitions/Statement.php +++ b/src/DI/Definitions/Statement.php @@ -373,6 +373,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 54/56] 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 1874e5577..96500285d 100644 --- a/src/DI/Definitions/ServiceDefinition.php +++ b/src/DI/Definitions/ServiceDefinition.php @@ -18,9 +18,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 b9650e469..b795ca881 100644 --- a/src/DI/Definitions/Statement.php +++ b/src/DI/Definitions/Statement.php @@ -22,7 +22,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 f8e5082a81fb820dd9f0e2559163bcbe584ec4f0 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 6 Apr 2024 21:02:45 +0200 Subject: [PATCH 55/56] 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 51cf4c909..664f03950 100644 --- a/src/DI/Extensions/InjectExtension.php +++ b/src/DI/Extensions/InjectExtension.php @@ -122,6 +122,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 7eda9780323171676b714099203c6a2af7904d5f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 29 Nov 2024 19:25:33 +0100 Subject: [PATCH 56/56] used attribute Deprecated --- src/DI/Extensions/InjectExtension.php | 2 +- src/DI/Helpers.php | 4 +--- tests/DI/Helpers.parseAnnotation().phpt | 22 +++++++++------------- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/DI/Extensions/InjectExtension.php b/src/DI/Extensions/InjectExtension.php index 664f03950..a631b1edf 100644 --- a/src/DI/Extensions/InjectExtension.php +++ b/src/DI/Extensions/InjectExtension.php @@ -120,7 +120,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 afb37f5ed..e016162d5 100644 --- a/src/DI/Helpers.php +++ b/src/DI/Helpers.php @@ -202,9 +202,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 @@