* * @implements \IteratorAggregate */ #[YieldReady] class Node implements \Countable, \IteratorAggregate { /** * @var array */ protected $nodes; protected $attributes; protected $lineno; protected $tag; private $sourceContext; /** @var array */ private $nodeNameDeprecations = []; /** @var array */ private $attributeNameDeprecations = []; /** * @param array $nodes An array of named nodes * @param array $attributes An array of attributes (should not be nodes) * @param int $lineno The line number */ public function __construct(array $nodes = [], array $attributes = [], int $lineno = 0) { if (self::class === static::class) { trigger_deprecation('twig/twig', '3.15', \sprintf('Instantiating "%s" directly is deprecated; the class will become abstract in 4.0.', self::class)); } foreach ($nodes as $name => $node) { if (!$node instanceof self) { throw new \InvalidArgumentException(\sprintf('Using "%s" for the value of node "%s" of "%s" is not supported. You must pass a \Twig\Node\Node instance.', get_debug_type($node), $name, static::class)); } } $this->nodes = $nodes; $this->attributes = $attributes; $this->lineno = $lineno; if (\func_num_args() > 3) { trigger_deprecation('twig/twig', '3.12', \sprintf('The "tag" constructor argument of the "%s" class is deprecated and ignored (check which TokenParser class set it to "%s"), the tag is now automatically set by the Parser when needed.', static::class, func_get_arg(3) ?: 'null')); } } public function __toString() { $repr = static::class; if ($this->tag) { $repr .= \sprintf("\n tag: %s", $this->tag); } $attributes = []; foreach ($this->attributes as $name => $value) { if (\is_callable($value)) { $v = '\Closure'; } elseif ($value instanceof \Stringable) { $v = (string) $value; } else { $v = str_replace("\n", '', var_export($value, true)); } $attributes[] = \sprintf('%s: %s', $name, $v); } if ($attributes) { $repr .= \sprintf("\n attributes:\n %s", implode("\n ", $attributes)); } if (\count($this->nodes)) { $repr .= "\n nodes:"; foreach ($this->nodes as $name => $node) { $len = \strlen($name) + 6; $noderepr = []; foreach (explode("\n", (string) $node) as $line) { $noderepr[] = str_repeat(' ', $len).$line; } $repr .= \sprintf("\n %s: %s", $name, ltrim(implode("\n", $noderepr))); } } return $repr; } /** * @return void */ public function compile(Compiler $compiler) { foreach ($this->nodes as $node) { $compiler->subcompile($node); } } public function getTemplateLine(): int { return $this->lineno; } public function getNodeTag(): ?string { return $this->tag; } /** * @internal */ public function setNodeTag(string $tag): void { if ($this->tag) { throw new \LogicException('The tag of a node can only be set once.'); } $this->tag = $tag; } public function hasAttribute(string $name): bool { return \array_key_exists($name, $this->attributes); } public function getAttribute(string $name) { if (!\array_key_exists($name, $this->attributes)) { throw new \LogicException(\sprintf('Attribute "%s" does not exist for Node "%s".', $name, static::class)); } $triggerDeprecation = \func_num_args() > 1 ? func_get_arg(1) : true; if ($triggerDeprecation && isset($this->attributeNameDeprecations[$name])) { $dep = $this->attributeNameDeprecations[$name]; if ($dep->getNewName()) { trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting attribute "%s" on a "%s" class is deprecated, get the "%s" attribute instead.', $name, static::class, $dep->getNewName()); } else { trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting attribute "%s" on a "%s" class is deprecated.', $name, static::class); } } return $this->attributes[$name]; } public function setAttribute(string $name, $value): void { $triggerDeprecation = \func_num_args() > 2 ? func_get_arg(2) : true; if ($triggerDeprecation && isset($this->attributeNameDeprecations[$name])) { $dep = $this->attributeNameDeprecations[$name]; if ($dep->getNewName()) { trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting attribute "%s" on a "%s" class is deprecated, set the "%s" attribute instead.', $name, static::class, $dep->getNewName()); } else { trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting attribute "%s" on a "%s" class is deprecated.', $name, static::class); } } $this->attributes[$name] = $value; } public function deprecateAttribute(string $name, NameDeprecation $dep): void { $this->attributeNameDeprecations[$name] = $dep; } public function removeAttribute(string $name): void { unset($this->attributes[$name]); } /** * @param string|int $name */ public function hasNode(string $name): bool { return isset($this->nodes[$name]); } /** * @param string|int $name */ public function getNode(string $name): self { if (!isset($this->nodes[$name])) { throw new \LogicException(\sprintf('Node "%s" does not exist for Node "%s".', $name, static::class)); } $triggerDeprecation = \func_num_args() > 1 ? func_get_arg(1) : true; if ($triggerDeprecation && isset($this->nodeNameDeprecations[$name])) { $dep = $this->nodeNameDeprecations[$name]; if ($dep->getNewName()) { trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting node "%s" on a "%s" class is deprecated, get the "%s" node instead.', $name, static::class, $dep->getNewName()); } else { trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting node "%s" on a "%s" class is deprecated.', $name, static::class); } } return $this->nodes[$name]; } /** * @param string|int $name */ public function setNode(string $name, self $node): void { $triggerDeprecation = \func_num_args() > 2 ? func_get_arg(2) : true; if ($triggerDeprecation && isset($this->nodeNameDeprecations[$name])) { $dep = $this->nodeNameDeprecations[$name]; if ($dep->getNewName()) { trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting node "%s" on a "%s" class is deprecated, set the "%s" node instead.', $name, static::class, $dep->getNewName()); } else { trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting node "%s" on a "%s" class is deprecated.', $name, static::class); } } if (null !== $this->sourceContext) { $node->setSourceContext($this->sourceContext); } $this->nodes[$name] = $node; } /** * @param string|int $name */ public function removeNode(string $name): void { unset($this->nodes[$name]); } /** * @param string|int $name */ public function deprecateNode(string $name, NameDeprecation $dep): void { $this->nodeNameDeprecations[$name] = $dep; } /** * @return int */ #[\ReturnTypeWillChange] public function count() { return \count($this->nodes); } public function getIterator(): \Traversable { return new \ArrayIterator($this->nodes); } public function getTemplateName(): ?string { return $this->sourceContext ? $this->sourceContext->getName() : null; } public function setSourceContext(Source $source): void { $this->sourceContext = $source; foreach ($this->nodes as $node) { $node->setSourceContext($source); } } public function getSourceContext(): ?Source { return $this->sourceContext; } }