twigphp/Twig · error · LogicException
The documentation target for a tag can only be set once.
Error message
The documentation target for a tag can only be set once.
What it means
When a tag being parsed opens a documentation target slot, setDocumentationTarget() may be called only once to attach the Node. A second call finds the slot already filled and throws, because a single tag can have at most one documentation target.
Solutions
- Ensure setDocumentationTarget() is invoked exactly once per parsed tag; remove duplicate call sites.
- Track locally whether the target was already set before calling again.
- If two nodes need documentation, restructure so only the outermost/primary node is registered.
Example fix
// before $this->parser->setDocumentationTarget($exprNode); $this->parser->setDocumentationTarget($blockNode); // second call throws // after $this->parser->setDocumentationTarget($blockNode); // single target only
Defensive patterns
Strategy: validation
Try / catch
try { $parser->setDocumentationTarget($node); } catch (\LogicException $e) { if (str_contains($e->getMessage(), 'can only be set once')) { /* duplicate call site */ } throw $e; } Prevention
- Register the documentation target at exactly one call site per TokenParser
- Search custom parsers for duplicate setDocumentationTarget calls before release
- Use a single code path for tag finalization
When it happens
Trigger: Calling $parser->setDocumentationTarget($node) twice within the same tag's parse (e.g. in both the expression parse and the closing logic of a custom TokenParser), or a tag parser that calls it in a loop.
Common situations: Custom TokenParser with duplicated wiring code; refactored parsers where the call was accidentally left in two code paths that both execute; base-class + subclass both invoking the method.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- A documentation target can only be set while parsing a tag.
- Left side must be ArrayExpression for object/mapping…
- The "name" attribute must be a string.
- The "format_list" filter requires the "IntlListFormatter"…
- SpanishInflector is not available.
AI-assisted analysis of twigphp/Twig@a414c3a491 (2026-09-13).
Data as JSON: /api/errors/59925fa6b23ec698.
Report an issue: GitHub.
Appendix: source
Thrown at src/Parser.php:328
}
$this->blocks[$name] = new BodyNode([$value], [], $value->getTemplateLine());
}
public function hasMacro(string $name): bool
{
trigger_deprecation('twig/twig', '3.12', 'Method "%s()" is deprecated.', __METHOD__);
return isset($this->macros[$name]);
}
public function setDocumentationTarget(Node $node): void
{
if (null === $index = array_key_last($this->documentationTargets)) {
throw new \LogicException('A documentation target can only be set while parsing a tag.');
}
if (null !== $this->documentationTargets[$index]) {
throw new \LogicException('The documentation target for a tag can only be set once.');
}
$this->documentationTargets[$index] = $node;
}
public function setMacro(string $name, MacroNode $node): void
{
if (isset($this->macros[$name])) {
trigger_deprecation('twig/twig', '3.29', 'Defining the macro "%s" more than once in "%s" is deprecated and will throw a SyntaxError in Twig 4.0 (previous definition at line %d, new definition at line %d). The last definition is used in Twig 3.', $name, $this->stream->getSourceContext()->getName(), $this->macros[$name]->getTemplateLine(), $node->getTemplateLine());
}
$this->macros[$name] = $node;
}
public function addTrait($trait): void
{
$this->traits[] = $trait;
}View on GitHub (pinned to a414c3a491)