twigphp/Twig · error · LogicException
getTokenParsers() must return an array of…
Error message
getTokenParsers() must return an array of \Twig\TokenParser\TokenParserInterface.
What it means
During extension initialization Twig iterates each extension's getTokenParsers() and validates that every element implements \Twig\TokenParser\TokenParserInterface. This LogicException means a returned element is not a valid token parser — usually a class-name string, a non-parser object, or a wrong getter returning the wrong collection.
Solutions
- Make getTokenParsers() return a flat array of instances: return [new MyTagParser()]; not [MyTagParser::class].
- Verify each element passes `instanceof \Twig\TokenParser\TokenParserInterface` and filter/assert before returning.
- Check that filters come from getFilters(), tests from getTests(), and only token parsers from getTokenParsers().
- Upgrade or replace the incompatible third-party extension for your Twig major version.
Example fix
// before
class MyExtension extends AbstractExtension {
public function getTokenParsers(): array { return [MyTagParser::class]; } // string, not instance
}
// after
class MyExtension extends AbstractExtension {
public function getTokenParsers(): array { return [new MyTagParser()]; }
} Defensive patterns
Strategy: validation
Validate before calling
// Self-check an extension before registering:
foreach ($ext->getTokenParsers() as $p) {
if (!$p instanceof \Twig\TokenParser\TokenParserInterface) {
throw new \TypeError(get_class($ext).'::getTokenParsers() returned '.get_debug_type($p));
}
} Type guard
function isValidTokenParser(mixed $p): bool {
return $p instanceof \Twig\TokenParser\TokenParserInterface;
} Try / catch
try {
$twig->addExtension($ext);
$twig->loadTemplate('any.twig'); // triggers initExtensions()
} catch (\LogicException $e) {
if (str_contains($e->getMessage(), 'getTokenParsers()')) {
// fix or replace the broken extension's getTokenParsers()
}
throw $e;
} Prevention
- Always return instances (new MyParser()), never class-name strings, from getTokenParsers()
- Add a return type (: array) and @return TokenParserInterface[] docblock
- Keep getFilters/getTests/getTokenParsers contents matched to their respective getters
- Run an environment-initialization smoke test in CI to catch bad extensions early
When it happens
Trigger: An extension's getTokenParsers() returns elements that are not TokenParserInterface instances — returning class-name strings, returning null entries, mixing filters/tests into getTokenParsers(), or a mis-typed collection in a custom or third-party extension. Fires when initExtensions() runs (first template load/render after adding the extension).
Common situations: Upgrading Twig across major versions where an old extension returned parser class names; copy-pasted extension code returning the wrong collection; hand-written extensions returning new MyParser() wrapped in an extra array level or with typos.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- " ::getOperators()" must return an array with operators…
- " ::getOperators()" must return an array of 2 elements, got…
- Unable to add a token parser as extensions have already…
- Unable to add test " " as extensions have already been…
- The "format_list" filter requires the "IntlListFormatter"…
AI-assisted analysis of twigphp/Twig@a414c3a491 (2026-09-13).
Data as JSON: /api/errors/9add8180c0a6d761.
Report an issue: GitHub.
Appendix: source
Thrown at src/ExtensionSet.php:491
foreach ($extension->getFunctions() as $function) {
$this->functions[$name = $function->getName()] = $function;
if (str_contains($name, '*')) {
$this->dynamicFunctions['#^'.str_replace('\\*', '(.*?)', preg_quote($name, '#')).'$#'] = $function;
}
}
// tests
foreach ($extension->getTests() as $test) {
$this->tests[$name = $test->getName()] = $test;
if (str_contains($name, '*')) {
$this->dynamicTests['#^'.str_replace('\\*', '(.*?)', preg_quote($name, '#')).'$#'] = $test;
}
}
// token parsers
foreach ($extension->getTokenParsers() as $parser) {
if (!$parser instanceof TokenParserInterface) {
throw new \LogicException('getTokenParsers() must return an array of \Twig\TokenParser\TokenParserInterface.');
}
$this->parsers[$parser->getTag()] = $parser;
}
// node visitors
foreach ($extension->getNodeVisitors() as $visitor) {
$this->visitors[] = $visitor;
}
// expression parsers
if (method_exists($extension, 'getExpressionParsers')) {
$this->expressionParsers->add($extension->getExpressionParsers());
}
$operators = $extension->getOperators();
if (!\is_array($operators)) {
throw new \InvalidArgumentException(\sprintf('"%s::getOperators()" must return an array with operators, got "%s".', $extension::class, get_debug_type($operators).(\is_resource($operators) ? '' : '#'.$operators)));View on GitHub (pinned to a414c3a491)