Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
877e98e
Resolver: triggers error when reference is called with arguments
dg Nov 29, 2024
b06e66e
Definition::generateMethod() replaced with generateCode()
dg Dec 1, 2024
3fc5456
Resolver: restrictions for named parameters have been removed
dg Nov 29, 2024
4d4eed7
Resolver: used withCurrentServiceAvailable() to control $currentServi…
dg Dec 1, 2024
30adf16
added Definitions\Expression
dg Dec 2, 2024
ee663fe
PhpGenerator::formatStatement() moved to Statement & Reference
dg Dec 1, 2024
93de5d5
Resolver::resolve*Type() moved to Statement & Reference
dg Dec 1, 2024
f55b977
Resolver::completeStatement() moved to Statement & Reference
dg Dec 1, 2024
4862aaa
NeonAdapter: processing of 'prevent merging' and 'entity to statement…
dg Dec 2, 2024
981f0a5
added FunctionCallable & MethodCallable, expressions representing fir…
dg Dec 2, 2024
f049f5b
creating lazy services in PHP 8.4 WIP
dg Nov 25, 2024
880f28d
opened 4.0-dev
dg Sep 12, 2021
b98757e
exception messages use [Service ...]\n format [WIP]
dg Dec 2, 2024
766e9df
annotations @return are no longer supported (BC break)
dg Dec 1, 2024
e9b9331
annotations @var are no longer supported (BC break)
dg Dec 14, 2023
d906854
removed Definition::generateMethod() (BC break)
dg Dec 1, 2024
722be37
removed support for three ... dots
dg Dec 11, 2023
abd0a99
removed compatibility for old class names
dg Dec 19, 2022
eea855e
deprecated magic properties (BC break)
dg Sep 24, 2021
6e75d64
annotations @inject is deprecated (BC break)
dg Apr 6, 2024
59cf699
used attribute Deprecated
dg Nov 29, 2024
71a58fe
Enable to suppress Suspicious dumping error
survik1 Apr 11, 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 composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"php": "8.1 - 8.4",
"ext-tokenizer": "*",
"ext-ctype": "*",
"nette/neon": "^3.3 || ^4.0",
"nette/neon": "^3.3.3 || ^4.0",
"nette/php-generator": "^4.1.6",
"nette/robot-loader": "^4.0",
"nette/schema": "^1.2.5",
Expand All @@ -39,7 +39,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "3.2-dev"
"dev-master": "4.0-dev"
}
}
}
17 changes: 17 additions & 0 deletions src/DI/Attributes/IgnoreDumping.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/

declare(strict_types=1);

namespace Nette\DI\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_CLASS)]
class IgnoreDumping
{
}
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);
163 changes: 105 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 @@ -24,6 +25,7 @@ final class NeonAdapter implements Nette\DI\Config\Adapter
{
private const PreventMergingSuffix = '!';
private string $file;
private \WeakMap $parents;


/**
Expand All @@ -40,61 +42,21 @@ public function load(string $file): array
$decoder = new Neon\Decoder;
$node = $decoder->parseToNode($input);
$traverser = new Neon\Traverser;
$node = $traverser->traverse($node, $this->firstClassCallableVisitor(...));
$node = $traverser->traverse($node, $this->removeUnderscoreVisitor(...));
$node = $traverser->traverse($node, $this->convertAtSignVisitor(...));
$node = $traverser->traverse($node, $this->deprecatedParametersVisitor(...));
$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 Down Expand Up @@ -151,16 +113,89 @@ function (&$val): void {
}


private function firstClassCallableVisitor(Node $node): void
private function preventMergingVisitor(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 === '...'
if (!$node instanceof Node\ArrayNode) {
return;
}

foreach ($node->items as $item) {
if (
$item->key instanceof Node\LiteralNode
&& is_string($item->key->value)
&& str_ends_with($item->key->value, self::PreventMergingSuffix)
) {
if ($item->value instanceof Node\LiteralNode && $item->value->value === null) {
$item->value = new Node\InlineArrayNode('[');
} elseif (!$item->value instanceof Node\ArrayNode) {
throw new Nette\DI\InvalidConfigurationException(sprintf(
"Replacing operator is available only for arrays, item '%s' is not array (used in '%s')",
$item->key->value,
$this->file,
));
}

$item->key->value = substr($item->key->value, 0, -1);
$item->value->items[] = $newItem = new Node\ArrayItemNode;
$newItem->key = new Node\LiteralNode(DI\Config\Helpers::PREVENT_MERGING);
$newItem->value = new Node\LiteralNode(true);
}
}
}


private function entityToExpressionVisitor(Node $node): Node
{
if ($node instanceof Node\EntityChainNode) {
return new Node\LiteralNode($this->buildExpression($node->chain));

} elseif (
$node instanceof Node\EntityNode
&& !$this->parents[$node] instanceof Node\EntityChainNode
) {
$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();
if (is_string($entity->value) && str_contains($entity->value, '?')) {
throw new Nette\DI\InvalidConfigurationException("Operator ? is deprecated in config file (used in '$this->file')");
}

$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 === '...';
}


Expand All @@ -181,11 +216,6 @@ private function removeUnderscoreVisitor(Node $node): void
if ($attr->value instanceof Node\LiteralNode && $attr->value->value === '_') {
unset($node->attributes[$i]);
$index = true;

} elseif ($attr->value instanceof Node\LiteralNode && $attr->value->value === '...') {
trigger_error("Replace ... with _ in configuration file '$this->file'.", E_USER_DEPRECATED);
unset($node->attributes[$i]);
$index = true;
}
}
}
Expand Down Expand Up @@ -240,4 +270,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);
},
);
}
}
8 changes: 4 additions & 4 deletions src/DI/ContainerBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ class ContainerBuilder
ThisService = 'self',
ThisContainer = 'container';

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

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

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

} elseif ($val instanceof Definition) {
$val = new Definitions\Reference($val->getName());
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 @@ -28,8 +28,8 @@ public function setImplement(string $interface): static
{
if (!interface_exists($interface)) {
throw new Nette\InvalidArgumentException(sprintf(
"Service '%s': Interface '%s' not found.",
$this->getName(),
"[%s]\nInterface '%s' not found.",
$this->getDescriptor(),
$interface,
));
}
Expand All @@ -44,19 +44,19 @@ public function setImplement(string $interface): static
|| count($rc->getMethods()) > 1
) {
throw new Nette\InvalidArgumentException(sprintf(
"Service '%s': Interface %s must have just one non-static method get().",
$this->getName(),
"[%s]\nInterface %s must have just one non-static method get().",
$this->getDescriptor(),
$interface,
));
} elseif ($method->getNumberOfParameters()) {
throw new Nette\InvalidArgumentException(sprintf(
"Service '%s': Method %s::get() must have no parameters.",
$this->getName(),
"[%s]\nMethod %s::get() must have no parameters.",
$this->getDescriptor(),
$interface,
));
}

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

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

$method->setBody('return new class ($this) ' . $class . ';');
return 'return new class ($this) ' . $class . ';';
}
}
29 changes: 26 additions & 3 deletions src/DI/Definitions/Definition.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,29 @@ final public function getName(): ?string
}


final public function isAnonymous(): bool
{
return !$this->name || ctype_digit($this->name);
}


public function getDescriptor(): string
{
if (!$this->isAnonymous()) {
return "Service '$this->name'" . ($this->type ? " of type $this->type" : '');

} elseif ($this->type) {
return "Service of type $this->type";

} elseif ($this->name) {
return "Service '$this->name'";

} else {
return 'Service ?';
}
}


protected function setType(?string $type): static
{
if ($this->autowired && $this->notifier && $this->type !== $type) {
Expand All @@ -56,8 +79,8 @@ protected function setType(?string $type): static
$this->type = null;
} elseif (!class_exists($type) && !interface_exists($type)) {
throw new Nette\InvalidArgumentException(sprintf(
"Service '%s': Class or interface '%s' not found.",
$this->name,
"[%s]\nClass or interface '%s' not found.",
$this->getDescriptor(),
$type,
));
} else {
Expand Down Expand Up @@ -147,7 +170,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
Expand Down
Loading