Skip to content

Commit

Permalink
NeonAdapter: processing of 'prevent merging' and 'entity to statement…
Browse files Browse the repository at this point in the history
…' moved to visitors
  • Loading branch information
dg committed Jan 10, 2025
1 parent 673b9a8 commit 45ef8bd
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 46 deletions.
2 changes: 1 addition & 1 deletion 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 Down
135 changes: 90 additions & 45 deletions src/DI/Config/Adapters/NeonAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ final class NeonAdapter implements Nette\DI\Config\Adapter
{
private const PreventMergingSuffix = '!';
private string $file;
private \WeakMap $parents;


/**
Expand All @@ -40,61 +41,23 @@ public function load(string $file): array
$decoder = new Neon\Decoder;
$node = $decoder->parseToNode($input);
$traverser = new Neon\Traverser;
$node = $traverser->traverse($node, $this->deprecatedQuestionMarkVisitor(...));
$node = $traverser->traverse($node, $this->firstClassCallableVisitor(...));
$node = $traverser->traverse($node, $this->removeUnderscoreVisitor(...));
$node = $traverser->traverse($node, $this->convertAtSignVisitor(...));
$node = $traverser->traverse($node, $this->deprecatedParametersVisitor(...));
$node = $traverser->traverse($node, $this->resolveConstantsVisitor(...));
return $this->process((array) $node->toValue());
$node = $traverser->traverse($node, $this->preventMergingVisitor(...));
$this->connectParentsVisitor($traverser, $node);
$node = $traverser->traverse($node, leave: $this->entityToExpressionVisitor(...));
return (array) $node->toValue();
}


/** @throws Nette\InvalidStateException */
/** @deprecated */
public function process(array $arr): array
{
$res = [];
foreach ($arr as $key => $val) {
if (is_string($key) && str_ends_with($key, self::PreventMergingSuffix)) {
if (!is_array($val) && $val !== null) {
throw new Nette\DI\InvalidConfigurationException(sprintf(
"Replacing operator is available only for arrays, item '%s' is not array (used in '%s')",
$key,
$this->file,
));
}

$key = substr($key, 0, -1);
$val[DI\Config\Helpers::PREVENT_MERGING] = true;
}

if (is_array($val)) {
$val = $this->process($val);

} elseif ($val instanceof Neon\Entity) {
if ($val->value === Neon\Neon::CHAIN) {
$tmp = null;
foreach ($this->process($val->attributes) as $st) {
$tmp = new Statement(
$tmp === null ? $st->getEntity() : [$tmp, ltrim(implode('::', (array) $st->getEntity()), ':')],
$st->arguments,
);
}

$val = $tmp;
} else {
$tmp = $this->process([$val->value]);
if (is_string($tmp[0]) && str_contains($tmp[0], '?')) {
throw new Nette\DI\InvalidConfigurationException("Operator ? is deprecated in config file (used in '$this->file')");
}

$val = new Statement($tmp[0], $this->process($val->attributes));
}
}

$res[$key] = $val;
}

return $res;
return $arr;
}


Expand Down Expand Up @@ -164,6 +127,71 @@ private function firstClassCallableVisitor(Node $node): void
}


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

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


private function deprecatedQuestionMarkVisitor(Node $node): void
{
if ($node instanceof Node\EntityNode
&& ($node->value instanceof Node\LiteralNode || $node->value instanceof Node\StringNode)
&& is_string($node->value->value)
&& str_contains($node->value->value, '?')
) {
throw new Nette\DI\InvalidConfigurationException("Operator ? is deprecated in config file (used in '$this->file')");
}
}


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

} elseif (
$node instanceof Node\EntityNode
&& !$this->parents[$node] instanceof Node\EntityChainNode
) {
return new Node\LiteralNode($this->buildExpression([$node]));

} else {
return $node;
}
}


private function buildExpression(array $chain): Statement
{
$node = array_pop($chain);
$entity = $node->toValue();
return new Statement(
$chain ? [$this->buildExpression($chain), ltrim($entity->value, ':')] : $entity->value,
$entity->attributes,
);
}


private function removeUnderscoreVisitor(Node $node): void
{
if (!$node instanceof Node\EntityNode) {
Expand Down Expand Up @@ -240,4 +268,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);
},
);
}
}

0 comments on commit 45ef8bd

Please sign in to comment.