Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a3501ab
operator ?: replaced with ??
dg Nov 30, 2025
74055d7
opened 3.3-dev
dg Jun 18, 2025
4443001
requires PHP 8.2
dg Oct 30, 2025
4ada2dd
uses nette/schema 1.3
dg Jun 18, 2025
e7176f9
uses nette/neon 3.4
dg Jun 18, 2025
e603037
used attribute Deprecated
dg Nov 29, 2024
4812e72
Definition::generateMethod() replaced with generateCode()
dg Dec 1, 2024
6573bf3
Resolver: used withCurrentServiceAvailable() to control $currentServi…
dg Dec 1, 2024
5bcfc3a
added Definitions\Expression
dg Dec 2, 2024
4db0f42
PhpGenerator::formatStatement() moved to Statement & Reference
dg Dec 1, 2024
43050f7
Resolver::resolve*Type() moved to Statement & Reference
dg Dec 1, 2024
1a30b89
Resolver::completeStatement() moved to Statement & Reference
dg Dec 1, 2024
a46c2e7
NeonAdapter: processing of 'prevent merging' and 'entity to statement…
dg Dec 2, 2024
33c668c
added FunctionCallable & MethodCallable, expressions representing fir…
dg Dec 2, 2024
0c995ae
opened 4.0-dev
dg Sep 12, 2021
4eea841
exception messages use [Service ...]\n format [WIP]
dg Dec 2, 2024
9970625
annotations @return are no longer supported (BC break)
dg Dec 1, 2024
c8aecb8
annotations @var are no longer supported (BC break)
dg Dec 14, 2023
d7c88c3
removed Definition::generateMethod() (BC break)
dg Dec 1, 2024
fae3e45
removed support for three ... dots
dg Dec 11, 2023
0b3292c
removed compatibility for old class names
dg Dec 19, 2022
edf6b45
deprecated magic properties (BC break)
dg Sep 24, 2021
f10e5d0
annotations @inject is deprecated (BC break)
dg Apr 6, 2024
51b3399
used attribute Deprecated
dg Nov 29, 2024
8deabf4
Add keysToHide to the DI Tracy panel with 'password' default
spaze Dec 22, 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
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
php: ['8.1', '8.2', '8.3', '8.4', '8.5']
php: ['8.2', '8.3', '8.4', '8.5']

fail-fast: false

Expand Down Expand Up @@ -35,7 +35,7 @@ jobs:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: 8.1
php-version: 8.2
coverage: none

- run: composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable
Expand Down
8 changes: 4 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
}
],
"require": {
"php": "8.1 - 8.5",
"php": "8.2 - 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"
}
}
}
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ The recommended way to install is via Composer:
composer require nette/di
```

It requires PHP version 8.1 and supports PHP up to 8.5.
It requires PHP version 8.2 and supports PHP up to 8.5.

 <!---->

Expand Down
3 changes: 3 additions & 0 deletions src/Bridges/DITracy/ContainerPanel.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ class ContainerPanel implements Tracy\IBarPanel
private Nette\DI\Container $container;
private ?float $elapsedTime;

/** @var string[] sensitive keys not displayed in the panel by default */
public static array $keysToHide = ['password'];


public function __construct(Container $container)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Bridges/DITracy/dist/panel.phtml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ declare(strict_types=1);

</td>
<td>
<?php if (isset($instances[$name]) && !$instances[$name] instanceof Nette\DI\Container): ?> <?= Dumper::toHtml($instances[$name], [Dumper::COLLAPSE => true, Dumper::LIVE => true, Dumper::DEPTH => 5]) ?>
<?php if (isset($instances[$name]) && !$instances[$name] instanceof Nette\DI\Container): ?> <?= Dumper::toHtml($instances[$name], [Dumper::COLLAPSE => true, Dumper::LIVE => true, Dumper::DEPTH => 5, Dumper::KEYS_TO_HIDE => Nette\Bridges\DITracy\ContainerPanel::$keysToHide]) ?>

<?php elseif (isset($instances[$name])): ?> <code><?= Tracy\Helpers::escapeHtml(get_class($instances[$name])) ?>
</code>
Expand Down
2 changes: 1 addition & 1 deletion src/Bridges/DITracy/panel.latte
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
</td>
<td>
{if isset($instances[$name]) && !$instances[$name] instanceof Nette\DI\Container}
{Dumper::toHtml($instances[$name], [Dumper::COLLAPSE => true, Dumper::LIVE => true, Dumper::DEPTH => 5])}
{Dumper::toHtml($instances[$name], [Dumper::COLLAPSE => true, Dumper::LIVE => true, Dumper::DEPTH => 5, Dumper::KEYS_TO_HIDE => Nette\Bridges\DITracy\ContainerPanel::$keysToHide])}
{elseif isset($instances[$name])}
<code>{get_class($instances[$name])}</code>
{elseif is_string($type)}
Expand Down
4 changes: 2 additions & 2 deletions src/DI/Compiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class Compiler

public function __construct(?ContainerBuilder $builder = null)
{
$this->builder = $builder ?: new ContainerBuilder;
$this->builder = $builder ?? new ContainerBuilder;
$this->dependencies = new DependencyChecker;
$this->addExtension(self::Services, new Extensions\ServicesExtension);
$this->addExtension(self::Parameters, new Extensions\ParametersExtension($this->configs));
Expand Down Expand Up @@ -113,7 +113,7 @@ public function addConfig(array $config): static
public function loadConfig(string $file, ?Config\Loader $loader = null): static
{
$sources = $this->sources . "// source: $file\n";
$loader = $loader ?: new Config\Loader;
$loader ??= new Config\Loader;
foreach ($loader->load($file, merge: false) as $data) {
$this->addConfig($data);
}
Expand Down
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);
},
);
}
}
10 changes: 5 additions & 5 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 @@ -83,7 +83,7 @@ public function addDefinition(?string $name, ?Definition $definition = null): De
}
}

$definition = $definition ?: new Definitions\ServiceDefinition;
$definition ??= new Definitions\ServiceDefinition;
$definition->setName($name);
$definition->setNotifier(function (): void {
$this->needsResolve = true;
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
2 changes: 1 addition & 1 deletion src/DI/ContainerLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ protected function generate(string $class, callable $generator): array
{
$compiler = new Compiler;
$compiler->setClassName($class);
$code = $generator(...[&$compiler]) ?: $compiler->compile();
$code = $generator(...[&$compiler]) ?? $compiler->compile();
return [
"<?php\n$code",
serialize($compiler->exportDependencies()),
Expand Down
Loading
Loading