Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
55764a6
opened 3.3-dev
dg Jun 18, 2025
1afffc2
uses nette/schema 1.3
dg Jun 18, 2025
f7e5cd6
uses nette/neon 3.4
dg Jun 18, 2025
8a2126b
used attribute Deprecated
dg Nov 29, 2024
007e333
Definition::generateMethod() replaced with generateCode()
dg Dec 1, 2024
8c8c2f4
Resolver: used withCurrentServiceAvailable() to control $currentServi…
dg Dec 1, 2024
ca2f422
added Definitions\Expression
dg Dec 2, 2024
671e72b
PhpGenerator::formatStatement() moved to Statement & Reference
dg Dec 1, 2024
6d0dc1c
Resolver::resolve*Type() moved to Statement & Reference
dg Dec 1, 2024
c10437c
Resolver::completeStatement() moved to Statement & Reference
dg Dec 1, 2024
d0a24e3
NeonAdapter: processing of 'prevent merging' and 'entity to statement…
dg Dec 2, 2024
ae91921
added FunctionCallable & MethodCallable, expressions representing fir…
dg Dec 2, 2024
a9d44df
opened 4.0-dev
dg Sep 12, 2021
7b5870f
exception messages use [Service ...]\n format [WIP]
dg Dec 2, 2024
6859669
annotations @return are no longer supported (BC break)
dg Dec 1, 2024
03df71e
annotations @var are no longer supported (BC break)
dg Dec 14, 2023
7647db5
removed Definition::generateMethod() (BC break)
dg Dec 1, 2024
01963ea
removed support for three ... dots
dg Dec 11, 2023
b524282
removed compatibility for old class names
dg Dec 19, 2022
1449355
deprecated magic properties (BC break)
dg Sep 24, 2021
2de10f5
annotations @inject is deprecated (BC break)
dg Apr 6, 2024
e05a39c
used attribute Deprecated
dg Nov 29, 2024
f2252cc
Container: added findServicesByType
vrana Sep 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
"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": {
Expand All @@ -42,7 +42,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "3.2-dev"
"dev-master": "4.0-dev"
}
}
}
3 changes: 0 additions & 3 deletions src/DI/Config/Adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,3 @@ interface Adapter
*/
function load(string $file): array;
}


class_exists(IAdapter::class);
166 changes: 108 additions & 58 deletions src/DI/Config/Adapters/NeonAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

use Nette;
use Nette\DI;
use Nette\DI\Definitions;
use Nette\DI\Definitions\Reference;
use Nette\DI\Definitions\Statement;
use Nette\Neon;
Expand All @@ -25,6 +26,7 @@ final class NeonAdapter implements Nette\DI\Config\Adapter
{
private const PreventMergingSuffix = '!';
private string $file;
private \WeakMap $parents;


/**
Expand All @@ -41,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;
}


Expand All @@ -112,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);
}


Expand Down Expand Up @@ -152,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) {
Expand All @@ -182,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;
}
}
}
Expand Down Expand Up @@ -241,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);
},
);
}
}
12 changes: 12 additions & 0 deletions src/DI/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,18 @@ public function findByType(string $type): array
}


/**
* Returns all services of the given type.
* @template T
* @param class-string<T> $type
* @return T[]
*/
public function findServicesByType(string $type): array
{
return array_map($this->getService(...), $this->findByType($type));
}


/**
* Returns the names of services with the given tag.
* @return array of [service name => tag attributes]
Expand Down
8 changes: 4 additions & 4 deletions src/DI/ContainerBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ class ContainerBuilder
ThisService = 'self',
ThisContainer = 'container';

/** @deprecated use ContainerBuilder::ThisService */
#[\Deprecated('use ContainerBuilder::ThisService')]
public const THIS_SERVICE = self::ThisService;

/** @deprecated use ContainerBuilder::ThisContainer */
#[\Deprecated('use ContainerBuilder::ThisContainer')]
public const THIS_CONTAINER = self::ThisContainer;

public array $parameters = [];
Expand Down Expand Up @@ -395,8 +395,8 @@ public static function literal(string $code, ?array $args = null): Nette\PhpGene
public function formatPhp(string $statement, array $args): string
{
array_walk_recursive($args, function (&$val): void {
if ($val instanceof Nette\DI\Definitions\Statement) {
$val = (new Resolver($this))->completeStatement($val);
if ($val instanceof Nette\DI\Definitions\Expression) {
$val->complete(new Resolver($this));

} elseif ($val instanceof Definition) {
$val = new Definitions\Reference($val->getName());
Expand Down
20 changes: 10 additions & 10 deletions src/DI/Definitions/AccessorDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ public function setImplement(string $interface): static
{
if (!interface_exists($interface)) {
throw new Nette\InvalidArgumentException(sprintf(
"Service '%s': Interface '%s' not found.",
$this->getName(),
"[%s]\nInterface '%s' not found.",
$this->getDescriptor(),
$interface,
));
}
Expand All @@ -45,19 +45,19 @@ public function setImplement(string $interface): static
|| count($rc->getMethods()) > 1
) {
throw new Nette\InvalidArgumentException(sprintf(
"Service '%s': Interface %s must have just one non-static method get().",
$this->getName(),
"[%s]\nInterface %s must have just one non-static method get().",
$this->getDescriptor(),
$interface,
));
} elseif ($method->getNumberOfParameters()) {
throw new Nette\InvalidArgumentException(sprintf(
"Service '%s': Method %s::get() must have no parameters.",
$this->getName(),
"[%s]\nMethod %s::get() must have no parameters.",
$this->getDescriptor(),
$interface,
));
}

Helpers::ensureClassType(Type::fromReflection($method), "return type of $interface::get()");
Helpers::ensureClassType(Type::fromReflection($method), "return type of $interface::get()", $this->getDescriptor());
return parent::setType($interface);
}

Expand Down Expand Up @@ -104,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());
Expand All @@ -124,6 +124,6 @@ public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGe
->setBody('return $this->container->getService(?);', [$this->reference->getValue()])
->setReturnType((string) Type::fromReflection($rm));

$method->setBody('return new class ($this) ' . $class . ';');
return 'return new class ($this) ' . $class . ';';
}
}
Loading
Loading