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..56fe2d503 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', '8.4']
+ php: ['8.1', '8.2', '8.3', '8.4', '8.5']
fail-fast: false
@@ -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
diff --git a/composer.json b/composer.json
index c3bec0059..7136a860b 100644
--- a/composer.json
+++ b/composer.json
@@ -15,22 +15,25 @@
}
],
"require": {
- "php": "8.1 - 8.4",
+ "php": "8.1 - 8.5",
"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.2.5",
+ "nette/schema": "^1.3",
"nette/utils": "^4.0"
},
"require-dev": {
"nette/tester": "^2.5.2",
"tracy/tracy": "^2.9",
- "phpstan/phpstan": "^1.0"
+ "phpstan/phpstan-nette": "^2.0@stable"
},
"autoload": {
- "classmap": ["src/"]
+ "classmap": ["src/"],
+ "psr-4": {
+ "Nette\\": "src"
+ }
},
"minimum-stability": "dev",
"scripts": {
@@ -39,7 +42,7 @@
},
"extra": {
"branch-alias": {
- "dev-master": "3.2-dev"
+ "dev-master": "4.0-dev"
}
}
}
diff --git a/src/Bridges/DITracy/ContainerPanel.php b/src/Bridges/DITracy/ContainerPanel.php
index 82051f931..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;
/**
@@ -40,7 +41,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 +77,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: = Tracy\Helpers::editorLink($file) ?>
+
+
+
+
+
+ | Name |
+ Autowired |
+ Service |
+ Tags |
+
+
+
+ $type): ?>
+ |
+
+ = Tracy\Helpers::escapeHtml($name) ?>
+
+= Tracy\Helpers::escapeHtml($name) ?>
+
+
+ |
+
+ = Tracy\Helpers::escapeHtml($autowired ? 'yes' : (isset($wiring[$type]) ? 'no' : '?')) ?>
+
+ |
+
+ = Dumper::toHtml($instances[$name], [Dumper::COLLAPSE => true, Dumper::LIVE => true, Dumper::DEPTH => 5]) ?>
+
+ = Tracy\Helpers::escapeHtml(get_class($instances[$name])) ?>
+
+ = Tracy\Helpers::escapeHtml($type) ?>
+
+ |
+
+ = Tracy\Helpers::escapeHtml(key($tags[$name])) ?>
+ = = Dumper::toHtml(current($tags[$name]), [Dumper::COLLAPSE => true]) ?>
+
+ = Dumper::toHtml($tags[$name], [Dumper::COLLAPSE => true]) ?>
+
+ |
+
+
+
+
+
Parameters
+
+
+ disabled via 'di › export › parameters'
+ = Dumper::toHtml($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 @@
+
+
+ = Tracy\Helpers::escapeHtml($elapsedTime ? sprintf('%0.1f ms', $elapsedTime * 1000) : '') ?>
+
+
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)}
+
+
+
+
+ | Name |
+ Autowired |
+ Service |
+ Tags |
+
+
+
+ {foreach $services as $name => $type}
+ {do $name = (string) $name}
+ {do $autowired = in_array($name, array_merge($wiring[$type][0] ?? [], $wiring[$type][1] ?? []), strict: true)}
+
+ |
+ {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}
+ |
+
+ {/foreach}
+
+
+
+
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: = Helpers::editorLink($file) ?>
-
-
-
-
- | Name |
- Autowired |
- Service |
- Tags |
-
-
-
- $type): ?>
-
-
-
- | = is_numeric($name) ? "$name" : Helpers::escapeHtml($name) ?> |
- = $autowired ? 'yes' : (isset($wiring[$type]) ? 'no' : '?') ?> |
-
-
- = Dumper::toHtml($instances[$name], [Dumper::COLLAPSE => true, Dumper::LIVE => true, Dumper::DEPTH => 5]); ?>
-
- = get_class($instances[$name]) ?>
-
- = Helpers::escapeHtml($type) ?>
-
- |
- true])
- : Dumper::toHtml($tags[$name], [Dumper::COLLAPSE => true]);
- } ?> |
-
-
-
-
-
-
Parameters
-
-
- = $parameters === null ? "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 @@
-
-
-= $elapsedTime ? sprintf('%0.1f ms', $elapsedTime * 1000) : '' ?>
-
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/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/Config/Adapters/NeonAdapter.php b/src/DI/Config/Adapters/NeonAdapter.php
index fcc2d5459..5293e7389 100644
--- a/src/DI/Config/Adapters/NeonAdapter.php
+++ b/src/DI/Config/Adapters/NeonAdapter.php
@@ -11,10 +11,12 @@
use Nette;
use Nette\DI;
+use Nette\DI\Definitions;
use Nette\DI\Definitions\Reference;
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;
/**
@@ -24,6 +26,7 @@ final class NeonAdapter implements Nette\DI\Config\Adapter
{
private const PreventMergingSuffix = '!';
private string $file;
+ private \WeakMap $parents;
/**
@@ -40,61 +43,22 @@ public function load(string $file): array
$decoder = new Neon\Decoder;
$node = $decoder->parseToNode($input);
$traverser = new Neon\Traverser;
- $node = $traverser->traverse($node, $this->firstClassCallableVisitor(...));
+ $node = $traverser->traverse($node, $this->deprecatedQuestionMarkVisitor(...));
$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;
}
@@ -111,7 +75,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);
}
@@ -151,19 +115,94 @@ function (&$val): void {
}
- 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
- && count($node->attributes) === 1
- && $node->attributes[0]->key === null
- && $node->attributes[0]->value instanceof Node\LiteralNode
- && $node->attributes[0]->value->value === '...'
+ && ($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
) {
- $node->attributes[0]->value->value = Nette\DI\Resolver::getFirstClassCallable()[0];
+ return new Node\LiteralNode($this->buildExpression([$node]));
+
+ } else {
+ return $node;
}
}
+ private function buildExpression(array $chain): Definitions\Expression
+ {
+ $node = array_pop($chain);
+ $entity = $node->toValue();
+ $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 === '...';
+ }
+
+
private function removeUnderscoreVisitor(Node $node): void
{
if (!$node instanceof Node\EntityNode) {
@@ -181,11 +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 === '...') {
- trigger_error("Replace ... with _ in configuration file '$this->file'.", E_USER_DEPRECATED);
- unset($node->attributes[$i]);
- $index = true;
}
}
}
@@ -240,4 +274,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);
+ },
+ );
+ }
}
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 d72639a63..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;
/**
@@ -320,6 +321,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
{
diff --git a/src/DI/ContainerBuilder.php b/src/DI/ContainerBuilder.php
index f2844d508..46f8c9e79 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;
/**
@@ -22,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 = [];
@@ -394,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/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..6d15f160a 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;
/**
@@ -28,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,
));
}
@@ -44,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);
}
@@ -103,11 +104,11 @@ public function complete(Nette\DI\Resolver $resolver): void
$this->setReference(Type::fromReflection($method)->getSingleName());
}
- $this->reference = $resolver->normalizeReference($this->reference);
+ $this->reference->complete($resolver);
}
- public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void
+ public function generateCode(Nette\DI\PhpGenerator $generator): string
{
$class = (new Nette\PhpGenerator\ClassType)
->addImplement($this->getType());
@@ -123,6 +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 ca96659b8..5ffa59029 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;
/**
@@ -46,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) {
@@ -56,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 {
@@ -147,7 +171,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
diff --git a/src/DI/Definitions/Expression.php b/src/DI/Definitions/Expression.php
new file mode 100644
index 000000000..da7b4a24f
--- /dev/null
+++ b/src/DI/Definitions/Expression.php
@@ -0,0 +1,24 @@
+getName(),
+ "[%s]\nInterface '%s' not found.",
+ $this->getDescriptor(),
$interface,
));
}
@@ -46,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);
}
@@ -105,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(),
));
@@ -143,7 +145,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) {
@@ -196,7 +198,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());
@@ -207,8 +209,7 @@ public function generateMethod(Php\Method $method, Nette\DI\PhpGenerator $genera
->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);
@@ -218,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/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/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 a9e3f735a..89e744613 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;
/**
@@ -25,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) {
@@ -39,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,
));
@@ -50,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,
);
}
@@ -110,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,
));
@@ -121,13 +123,13 @@ 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);
}
}
- public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void
+ public function generateCode(Nette\DI\PhpGenerator $generator): string
{
$class = (new Nette\PhpGenerator\ClassType)
->addImplement($this->getType());
@@ -171,6 +173,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/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/Reference.php b/src/DI/Definitions/Reference.php
index 25625dec7..b10dd6745 100644
--- a/src/DI/Definitions/Reference.php
+++ b/src/DI/Definitions/Reference.php
@@ -9,15 +9,18 @@
namespace Nette\DI\Definitions;
+use Nette\DI;
+
+
/**
* Reference to service. Either by name or by type or reference to the 'self' service.
*/
-final class Reference
+final class Reference extends Expression
{
public const Self = 'self';
- /** @deprecated use Reference::Self */
+ #[\Deprecated('use Reference::Self')]
public const SELF = self::Self;
private string $value;
@@ -61,4 +64,58 @@ public function isSelf(): bool
{
return $this->value === self::Self;
}
+
+
+ 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();
+ }
+
+
+ /**
+ * 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) {
+ $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 ff934c26a..96500285d 100644
--- a/src/DI/Definitions/ServiceDefinition.php
+++ b/src/DI/Definitions/ServiceDefinition.php
@@ -12,14 +12,15 @@
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;
/**
* 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
{
@@ -38,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);
@@ -141,7 +153,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.');
}
@@ -161,14 +173,18 @@ 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->completeStatement($setup, true);
+ foreach ($this->setup as $setup) {
+ try {
+ $setup->complete($resolver->withCurrentServiceAvailable());
+ } catch (ServiceCreationException $e) {
+ throw $e->setMessage($e->getMessage() . ' (in setup)');
+ }
}
}
@@ -181,11 +197,11 @@ private function prependSelf(Statement $setup): Statement
}
- public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void
+ public function generateCode(Nette\DI\PhpGenerator $generator): string
{
$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
@@ -193,15 +209,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;';
}
}
@@ -223,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 cd80f6d44..b795ca881 100644
--- a/src/DI/Definitions/Statement.php
+++ b/src/DI/Definitions/Statement.php
@@ -10,14 +10,21 @@
namespace Nette\DI\Definitions;
use Nette;
+use Nette\DI;
+use Nette\DI\Resolver;
+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;
/**
* Assignment or calling statement.
*
- * @property string|array|Definition|Reference|null $entity
+ * @property-deprecated string|array|Definition|Reference|null $entity
*/
-final class Statement implements Nette\Schema\DynamicParameter
+final class Statement extends Expression implements Nette\Schema\DynamicParameter
{
use Nette\SmartObject;
@@ -62,7 +69,307 @@ public function getEntity(): string|array|Definition|Reference|null
{
return $this->entity;
}
-}
-class_exists(Nette\DI\Statement::class);
+ public function resolveType(Resolver $resolver): ?string
+ {
+ $entity = $this->normalizeEntity($resolver);
+
+ if (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);
+ 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);
+
+ } 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 complete(Resolver $resolver): void
+ {
+ $entity = $this->normalizeEntity($resolver);
+ $this->convertReferences($resolver);
+ $arguments = $this->arguments;
+
+ switch (true) {
+ 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(), "\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;
+ }
+ }
+
+
+ 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.
+ */
+ 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/DependencyChecker.php b/src/DI/DependencyChecker.php
index 94eb9fdc8..0fefd3e0d 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;
/**
@@ -22,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/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..a631b1edf 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;
/**
@@ -22,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;
@@ -49,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();
@@ -67,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);
}
@@ -117,19 +118,18 @@ 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 // @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);
+ }
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));
}
}
@@ -148,7 +148,7 @@ public static function callInjects(DI\Container $container, object $service): vo
}
foreach (self::getInjectProperties($service::class) as $property => $type) {
- self::checkType($service, $property, $type, $container);
+ self::checkType($service, $property, $type, $container, null);
$service->$property = $container->getByType($type);
}
}
@@ -159,11 +159,13 @@ private static function checkType(
string $name,
?string $type,
DI\Container|DI\ContainerBuilder $container,
+ ?Definitions\Definition $def,
): void
{
if (!$container->getByType($type, throw: false)) {
throw new Nette\DI\MissingServiceException(sprintf(
- 'Service of type %s required by %s not found. Did you add it to configuration file?',
+ "%sService of type %s required by %s not found.\nDid you add it to configuration file?",
+ $def ? '[' . $def->getDescriptor() . "]\n" : '',
$type,
Reflection::toString(new \ReflectionProperty($class, $name)),
));
diff --git a/src/DI/Extensions/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..04d7cf835 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;
/**
@@ -53,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);
@@ -68,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 48e421fbf..e016162d5 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;
/**
@@ -200,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()) {
@@ -218,31 +218,23 @@ 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, bool $allowNullable = false): string
+ public static function ensureClassType(
+ ?Type $type,
+ string $hint,
+ string $descriptor = '',
+ bool $allowNullable = false,
+ ): string
{
+ $descriptor = $descriptor ? "[$descriptor]\n" : '';
if (!$type) {
- throw new ServiceCreationException(sprintf('%s is not declared.', ucfirst($hint)));
+ throw new ServiceCreationException(sprintf('%s%s is not declared.', $descriptor, ucfirst($hint)));
} elseif (!$type->isClass() || (!$allowNullable && $type->allows('null'))) {
- throw new ServiceCreationException(sprintf("%s is expected to not be %sbuilt-in/complex, '%s' given.", ucfirst($hint), $allowNullable ? '' : 'nullable/', $type));
+ throw new ServiceCreationException(sprintf("%s%s is expected to not be %sbuilt-in/complex, '%s' given.", $descriptor, ucfirst($hint), $allowNullable ? '' : 'nullable/', $type));
}
$class = $type->getSingleName();
if (!class_exists($class) && !interface_exists($class)) {
- throw new ServiceCreationException(sprintf("Class '%s' not found.\nCheck the %s.", $class, $hint));
+ throw new ServiceCreationException(sprintf("%sClass '%s' not found.\nCheck the %s.", $descriptor, $class, $hint));
}
return $class;
@@ -282,4 +274,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 b5176c40d..239baa356 100644
--- a/src/DI/PhpGenerator.php
+++ b/src/DI/PhpGenerator.php
@@ -9,10 +9,9 @@
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;
/**
@@ -94,67 +93,19 @@ 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) {
- throw new ServiceCreationException("Service '$name': " . $e->getMessage(), 0, $e);
+ throw new ServiceCreationException(sprintf("[%s]\n%s", $def->getDescriptor(), $e->getMessage()), 0, $e);
}
}
- /**
- * Formats PHP code for class instantiating, function calling or property setting in PHP.
- */
- public function formatStatement(Statement $statement): string
+ /** @deprecated */
+ public function formatStatement(Definitions\Statement $statement): string
{
- $entity = $statement->getEntity();
- $arguments = $statement->arguments;
-
- switch (true) {
- case is_string($entity) && str_contains($entity, '?'): // PHP literal
- return $this->formatPhp($entity, $arguments);
-
- case is_string($entity): // create class
- return $arguments
- ? $this->formatPhp("new $entity(...?:)", [$arguments])
- : $this->formatPhp("new $entity", []);
-
- case is_array($entity):
- switch (true) {
- case $entity[1][0] === '$': // property getter, setter or appender
- $name = substr($entity[1], 1);
- if ($append = (str_ends_with($name, '[]'))) {
- $name = substr($name, 0, -2);
- }
-
- $prop = $entity[0] instanceof Reference
- ? $this->formatPhp('?->?', [$entity[0], $name])
- : $this->formatPhp('?::$?', [$entity[0], $name]);
- return $arguments
- ? $this->formatPhp(($append ? '?[]' : '?') . ' = ?', [new Php\Literal($prop), $arguments[0]])
- : $prop;
-
- case $entity[0] instanceof Statement:
- $inner = $this->formatPhp('?', [$entity[0]]);
- if (str_starts_with($inner, 'new ')) {
- $inner = "($inner)";
- }
-
- return $this->formatPhp('?->?(...?:)', [new Php\Literal($inner), $entity[1], $arguments]);
-
- case $entity[0] instanceof Reference:
- return $this->formatPhp('?->?(...?:)', [$entity[0], $entity[1], $arguments]);
-
- case $entity[0] === '': // function call
- return $this->formatPhp('?(...?:)', [new Php\Literal($entity[1]), $arguments]);
-
- case is_string($entity[0]): // static method call
- return $this->formatPhp('?::?(...?:)', [new Php\Literal($entity[0]), $entity[1], $arguments]);
- }
- }
-
- throw new Nette\InvalidStateException;
+ return $statement->generateCode($this);
}
@@ -171,18 +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
diff --git a/src/DI/Resolver.php b/src/DI/Resolver.php
index ceab3d135..f38420ca0 100644
--- a/src/DI/Resolver.php
+++ b/src/DI/Resolver.php
@@ -13,11 +13,9 @@
use Nette\DI\Definitions\Definition;
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;
/**
@@ -42,6 +40,32 @@ public function __construct(ContainerBuilder $builder)
}
+ public 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 getCurrentService(bool $type = false): Definition|string|null
+ {
+ return $type ? $this->currentServiceType : $this->currentService;
+ }
+
+
public function getContainerBuilder(): ContainerBuilder
{
return $this->builder;
@@ -50,16 +74,15 @@ public function getContainerBuilder(): ContainerBuilder
public function resolveDefinition(Definition $def): void
{
- if ($this->recursive->contains($def)) {
+ if ($this->recursive->offsetExists($def)) {
$names = array_map(fn($item) => $item->getName(), iterator_to_array($this->recursive));
throw new ServiceCreationException(sprintf('Circular reference detected for services: %s.', implode(', ', $names)));
}
try {
- $this->recursive->attach($def);
+ $this->recursive->offsetSet($def);
$def->resolveType($this);
-
if (!$def->getType()) {
throw new ServiceCreationException('Type of service is unknown.');
}
@@ -67,338 +90,23 @@ public function resolveDefinition(Definition $def): void
throw $this->completeException($e, $def);
} finally {
- $this->recursive->detach($def);
+ $this->recursive->offsetUnset($def);
}
}
- 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
{
- $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
- {
- $this->currentServiceAllowed = $currentServiceAllowed;
- $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], $this->currentServiceAllowed);
- }
- 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], $this->currentServiceAllowed);
- // 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 Reference
- ? $this->resolveReferenceType($entity[0])
- : $this->resolveEntityType($entity[0] instanceof Statement ? $entity[0] : new Statement($entity[0]))
- ) {
- $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, $this->currentServiceAllowed);
- }
- } 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] */
- private 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);
- }
- }
-
-
- 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
@@ -436,27 +144,19 @@ 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 '")) {
+ $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)
@@ -464,50 +164,12 @@ private function completeException(\Throwable $e, Definition $def): ServiceCreat
}
- private 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;
- }
-
-
- 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($this->resolveReferenceType(new Reference($pair[0])) . '::' . $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);
}
@@ -602,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,
));
@@ -654,10 +316,50 @@ private static function isArrayOf(\ReflectionParameter $parameter, ?Nette\Utils\
}
- /** @internal */
- public static function getFirstClassCallable(): array
+ /** @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());
+ }
+
+
+ /** @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
{
- static $x = [new Nette\PhpGenerator\Literal('...')];
- return $x;
+ return (new Statement(null, $arguments))->completeArguments($this, $arguments);
}
}
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
{
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 @@
-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/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.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%)");
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/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/Container.inject.properties.phpt b/tests/DI/Container.inject.properties.phpt
index b633c0f8b..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,20 +24,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 +50,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/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.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..65f0415a0 100644
--- a/tests/DI/ContainerBuilder.create.error.phpt
+++ b/tests/DI/ContainerBuilder.create.error.phpt
@@ -14,54 +14,61 @@ 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.");
+}, 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.");
+}, Nette\DI\ServiceCreationException::class, "[Service of type Unknown]
+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');
$builder->complete();
-}, Nette\InvalidStateException::class, "Service 'two': Class 'Unknown' not found.");
+}, 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');
$builder->complete();
-}, Nette\InvalidStateException::class, "Service 'two': Class 'Unknown' not found.");
+}, 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.");
+}, 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.");
+}, 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');
-}, Nette\InvalidArgumentException::class, "Service 'one': Interface 'Unknown' not found.");
+}, Nette\InvalidArgumentException::class, "[Service 'one']
+Interface 'Unknown' not found.");
@@ -70,11 +77,12 @@ 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);
-}, 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
@@ -82,12 +90,13 @@ 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);
$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
@@ -97,11 +106,12 @@ 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();
-}, 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
@@ -111,11 +121,12 @@ 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();
-}, 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
@@ -125,11 +136,12 @@ 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();
-}, 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
@@ -139,17 +151,21 @@ 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())");
+}, Nette\InvalidStateException::class, "[Service 'one' of type Good]
+Class 'Unknown' not found.
+Related to 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();
-}, 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
@@ -173,11 +190,12 @@ 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();
-}, Nette\InvalidStateException::class, "Service 'one': Method Bad10::method() is not callable.");
+}, Nette\InvalidStateException::class, "[Service 'one']
+Method Bad10::method() is not callable.");
class ConstructorParam
@@ -194,87 +212,105 @@ class MethodParam
}
}
-testException('autowiring fail', function () {
+testException('ambiguous constructor dependency triggers multiple services error', function () {
createContainer(new DI\Compiler, '
services:
a: stdClass
b: stdClass
bad: ConstructorParam
');
-}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of ConstructorParam): Multiple services of type stdClass found: a, b (required by \$x in ConstructorParam::__construct())");
+}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type ConstructorParam]
+Multiple services of type stdClass found: a, b
+Required by \$x in ConstructorParam::__construct().");
-testException('forced autowiring fail', function () {
+testException('ambiguous constructor dependency via argument reference', function () {
createContainer(new DI\Compiler, '
services:
a: stdClass
b: stdClass
bad: ConstructorParam(@\stdClass)
');
-}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of ConstructorParam): Multiple services of type stdClass found: a, b (used in ConstructorParam::__construct())");
+}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type ConstructorParam]
+Multiple services of type stdClass found: a, b
+Related to ConstructorParam::__construct().");
-testException('autowiring fail in chain', function () {
+testException('ambiguous method parameter dependency triggers error', function () {
createContainer(new DI\Compiler, '
services:
a: stdClass
b: stdClass
bad: MethodParam()::foo()
');
-}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of MethodParam): Multiple services of type stdClass found: a, b (required by \$x in MethodParam::foo())");
+}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type MethodParam]
+Multiple services of type stdClass found: a, b
+Required by \$x in MethodParam::foo().");
-testException('forced autowiring fail in chain', function () {
+testException('ambiguous dependency in method call triggers error', function () {
createContainer(new DI\Compiler, '
services:
a: stdClass
b: stdClass
bad: MethodParam()::foo(@\stdClass)
');
-}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of MethodParam): Multiple services of type stdClass found: a, b (used in foo())");
+}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type MethodParam]
+Multiple services of type stdClass found: a, b
+Related to MethodParam()::foo().");
-testException('autowiring fail in argument', function () {
+testException('multiple services in constructor dependency cause ambiguity', function () {
createContainer(new DI\Compiler, '
services:
a: stdClass
b: stdClass
bad: Good(ConstructorParam())
');
-}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of Good): Multiple services of type stdClass found: a, b (required by \$x in ConstructorParam::__construct()) (used in Good::__construct())");
+}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type Good]
+Multiple services of type stdClass found: a, b
+Required by \$x in ConstructorParam::__construct().
+Related to Good::__construct().");
-testException('forced autowiring fail in argument', function () {
+testException('ambiguous dependency in constructor argument triggers error', function () {
createContainer(new DI\Compiler, '
services:
a: stdClass
b: stdClass
bad: Good(ConstructorParam(@\stdClass))
');
-}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of Good): Multiple services of type stdClass found: a, b (used in ConstructorParam::__construct())");
+}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type Good]
+Multiple services of type stdClass found: a, b
+Related to ConstructorParam::__construct().");
-testException('autowiring fail in chain in argument', function () {
+testException('ambiguous dependency in method parameter causes error', function () {
createContainer(new DI\Compiler, '
services:
a: stdClass
b: stdClass
bad: Good(MethodParam()::foo())
');
-}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of Good): Multiple services of type stdClass found: a, b (required by \$x in MethodParam::foo()) (used in Good::__construct())");
+}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type Good]
+Multiple services of type stdClass found: a, b
+Required by \$x in MethodParam::foo().
+Related to Good::__construct().");
-testException('forced autowiring fail in chain in argument', function () {
+testException('ambiguous dependency in method call triggers error', function () {
createContainer(new DI\Compiler, '
services:
a: stdClass
b: stdClass
bad: Good(MethodParam()::foo(@\stdClass))
');
-}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of Good): Multiple services of type stdClass found: a, b (used in foo())");
+}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type Good]
+Multiple services of type stdClass found: a, b
+Related to MethodParam()::foo().");
-testException('forced autowiring fail in property passing', function () {
+testException('ambiguous dependency in property setup triggers error', function () {
createContainer(new DI\Compiler, '
services:
a: stdClass
@@ -284,10 +320,12 @@ services:
setup:
- $a = @\stdClass
');
-}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of Good): Multiple services of type stdClass found: a, b (used in @bad::\$a)");
+}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type Good]
+Multiple services of type stdClass found: a, b
+Related to \$a. (in setup)");
-testException('autowiring fail in rich property passing', function () {
+testException('ambiguous dependency in method setup triggers error', function () {
createContainer(new DI\Compiler, '
services:
a: stdClass
@@ -297,10 +335,12 @@ services:
setup:
- $a = MethodParam()::foo(@\stdClass)
');
-}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of Good): Multiple services of type stdClass found: a, b (used in foo())");
+}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type Good]
+Multiple services of type stdClass found: a, b
+Related to MethodParam()::foo(). (in setup)");
-testException('autowiring fail in method calling', function () {
+testException('ambiguous dependency in method call during setup triggers error', function () {
createContainer(new DI\Compiler, '
services:
a: stdClass
@@ -310,10 +350,12 @@ services:
setup:
- foo
');
-}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of MethodParam): Multiple services of type stdClass found: a, b (required by \$x in MethodParam::foo())");
+}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type MethodParam]
+Multiple services of type stdClass found: a, b
+Required by \$x in MethodParam::foo(). (in setup)");
-testException('forced autowiring fail in method calling', function () {
+testException('ambiguous dependency in method call on service triggers error', function () {
createContainer(new DI\Compiler, '
services:
a: stdClass
@@ -323,10 +365,12 @@ services:
setup:
- bar(@\stdClass)
');
-}, Nette\DI\ServiceCreationException::class, "Service 'bad' (type of Good): Multiple services of type stdClass found: a, b (used in @bad::bar())");
+}, Nette\DI\ServiceCreationException::class, "[Service 'bad' of type Good]
+Multiple services of type stdClass found: a, b
+Related to bar(). (in setup)");
-testException('autowiring fail in rich method calling', function () {
+testException('ambiguous dependency in method call setup triggers error', function () {
createContainer(new DI\Compiler, '
services:
a: stdClass
@@ -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.metadata.phpt b/tests/DI/ContainerBuilder.metadata.phpt
index 539a43858..1d9c97de0 100644
--- a/tests/DI/ContainerBuilder.metadata.phpt
+++ b/tests/DI/ContainerBuilder.metadata.phpt
@@ -16,7 +16,6 @@ require __DIR__ . '/../bootstrap.php';
function getPropertyValue($obj, string $name)
{
$prop = (new ReflectionObject($obj))->getProperty($name);
- $prop->setAccessible(true);
return $prop->getValue($obj);
}
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..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,66 +94,42 @@ 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')
->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.");
-
-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']
+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, '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.");
-
-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.");
-
-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().");
+}, 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, '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..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,66 +85,42 @@ 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')
->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.");
-
-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']
+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, '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.");
-
-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.");
-
-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().");
+}, 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, '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/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 @@
- 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..c7c997a1b 100644
--- a/tests/DI/InjectExtension.getInjectProperties().phpt
+++ b/tests/DI/InjectExtension.getInjectProperties().phpt
@@ -6,93 +6,51 @@
declare(strict_types=1);
-namespace A
-{
- class AClass
- {
- /** @var AInjected @inject */
- public $varA;
+use Nette\DI\Attributes\Inject;
+use Nette\DI\Extensions\InjectExtension;
+use Tester\Assert;
+
- /** @var B\BInjected @inject */
- public $varB;
+class AClass
+{
+ #[Inject]
+ public AInjected $varA;
- /** @var AInjected @inject */
- public $varC;
+ #[Inject]
+ public BInjected $varB;
- /** @var AInjected */
- public $varD;
- }
+ public $varD;
- class AInjected
- {
- }
+ #[Inject]
+ public stdClass $varF;
}
-namespace A\B
+class BadClass
{
- use A;
-
- class BClass extends A\AClass
- {
- /** @var BInjected @inject */
- public $varF;
- }
-
- class BInjected
- {
- }
+ #[Inject]
+ public AClass|stdClass $var;
}
-namespace C
+class AInjected
{
- use A\AInjected;
- use A\B;
- use C\CInjected as CAlias;
-
- class CClass
- {
- /** @var AInjected @inject */
- public $var1;
-
- /** @var B\BInjected @inject */
- public $var2;
-
- /** @var CAlias @inject */
- public $var3;
-
- /** @var CInjected @inject */
- public $var4;
- }
-
- class CInjected
- {
- }
}
-namespace {
- use Nette\DI\Extensions\InjectExtension;
- use Tester\Assert;
+class BInjected
+{
+}
- require __DIR__ . '/../bootstrap.php';
+require __DIR__ . '/../bootstrap.php';
- Assert::same([
- 'varA' => A\AInjected::class,
- 'varB' => A\B\BInjected::class,
- 'varC' => A\AInjected::class,
- ], InjectExtension::getInjectProperties(A\AClass::class));
- Assert::same([
- 'varA' => A\AInjected::class,
- 'varB' => A\B\BInjected::class,
- 'varC' => A\AInjected::class,
- 'varF' => A\B\BInjected::class,
- ], InjectExtension::getInjectProperties(A\B\BClass::class));
+Assert::same([
+ 'varA' => AInjected::class,
+ 'varB' => BInjected::class,
+ 'varF' => stdClass::class,
+], InjectExtension::getInjectProperties(AClass::class));
- Assert::same([
- 'var1' => A\AInjected::class,
- 'var2' => A\B\BInjected::class,
- 'var3' => C\CInjected::class,
- 'var4' => C\CInjected::class,
- ], InjectExtension::getInjectProperties(C\CClass::class));
-}
+Assert::exception(
+ fn() => InjectExtension::getInjectProperties(BadClass::class),
+ Nette\InvalidStateException::class,
+ "Type of property BadClass::\$var is expected to not be nullable/built-in/complex, 'AClass|stdClass' given.",
+);
diff --git a/tests/DI/InjectExtension.getInjectProperties().traits.phpt b/tests/DI/InjectExtension.getInjectProperties().traits.phpt
index 1fc6c211e..ba4022d49 100644
--- a/tests/DI/InjectExtension.getInjectProperties().traits.phpt
+++ b/tests/DI/InjectExtension.getInjectProperties().traits.phpt
@@ -16,11 +16,12 @@ namespace A
namespace B
{
use A\AInjected;
+ use Nette\DI\Attributes\Inject;
trait BTrait
{
- /** @var AInjected @inject */
- public $varA;
+ #[Inject]
+ public AInjected $varA;
}
}
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
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?',
);
diff --git a/tests/DI/fixtures/Helpers.getReturnType.php b/tests/DI/fixtures/Helpers.getReturnType.php
deleted file mode 100644
index 93bb93e5b..000000000
--- a/tests/DI/fixtures/Helpers.getReturnType.php
+++ /dev/null
@@ -1,49 +0,0 @@
-