twigphp/Twig · error · InvalidArgumentException

" ::getOperators()" must return an array of 2 elements, got…

Error message

"%s::getOperators()" must return an array of 2 elements, got %d.

What it means

After confirming getOperators() returns an array, Twig requires exactly two elements: element [0] is unary operators, element [1] is binary operators. This InvalidArgumentException fires when the array has any other count (0, 1, 3, ...), typically because unary and binary were merged into one flat array or [unary, binary] was wrapped in an extra level.

Solutions

  1. Return exactly two array elements: [unaryOperators, binaryOperators], even if one is empty ([]).
  2. Check for extra wrapping levels: returning [[unary, binary]] counts as 1 element — flatten one level.
  3. Copy the structure from \Twig\Extension\CoreExtension::getOperators() as a reference.
  4. Upgrade or replace the incompatible third-party extension and add an initialization smoke test in CI.

Example fix

// before
class MyExtension extends AbstractExtension {
    public function getOperators(): array { return [['bnot' => [...]]]; } // 1 element
}

// after
class MyExtension extends AbstractExtension {
    public function getOperators(): array {
        return [
            ['bnot' => ['precedence' => 50, 'class' => MyUnary::class]], // unary
            [],                                                           // binary (may be empty)
        ];
    }
}
Defensive patterns

Strategy: validation

Validate before calling

$ops = $ext->getOperators();
if (!\is_array($ops) || \count($ops) !== 2 || !\is_array($ops[0]) || !\is_array($ops[1])) {
    throw new \UnexpectedValueException('getOperators() must return exactly [unaryOperators, binaryOperators]');
}

Type guard

function hasTwoOperatorArrays(mixed $ops): bool {
    return \is_array($ops) && 2 === \count($ops) && \is_array($ops[0]) && \is_array($ops[1]);
}

Try / catch

try {
    $twig->addExtension($ext);
    $twig->loadTemplate('any.twig');
} catch (\InvalidArgumentException $e) {
    if (str_contains($e->getMessage(), 'array of 2 elements')) {
        // restructure getOperators() to the two-element [unary, binary] shape
    }
    throw $e;
}

Prevention

When it happens

Trigger: getOperators() returns an array with fewer or more than 2 elements — returning only the unary operators array, merging unary and binary into a single map, or returning [[unary, binary]] which counts as 1 element. Fires during initExtensions(), triggered by the first template load/render after adding the extension.

Common situations: Custom extensions written against outdated documentation; third-party extension incompatible with the installed Twig version; refactoring that flattened the [unary, binary] structure; returning [[unary, binary], extra] after wrapping.

Related errors


AI-assisted analysis of twigphp/Twig@a414c3a491 (2026-09-13). Data as JSON: /api/errors/ea80eb8ee9bd02ab. Report an issue: GitHub.

Appendix: source

Thrown at src/ExtensionSet.php:513

        }

        // 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)));
        }

        if (2 !== \count($operators)) {
            throw new \InvalidArgumentException(\sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.', $extension::class, \count($operators)));
        }

        $expressionParsers = [];
        foreach ($operators[0] as $operator => $op) {
            $expressionParsers[] = new UnaryOperatorExpressionParser($op['class'], $operator, $op['precedence'], $op['precedence_change'] ?? null, '', $op['aliases'] ?? []);
        }
        foreach ($operators[1] as $operator => $op) {
            $op['associativity'] = match ($op['associativity']) {
                1 => InfixAssociativity::Left,
                2 => InfixAssociativity::Right,
                default => throw new \InvalidArgumentException(\sprintf('Invalid associativity "%s" for operator "%s".', $op['associativity'], $operator)),
            };

            if (isset($op['callable'])) {
                $expressionParsers[] = $this->convertInfixExpressionParser($op['class'], $operator, $op['precedence'], $op['associativity'], $op['precedence_change'] ?? null, $op['aliases'] ?? [], $op['callable']);
            } else {
                $expressionParsers[] = new BinaryOperatorExpressionParser($op['class'], $operator, $op['precedence'], $op['associativity'], $op['precedence_change'] ?? null, '', $op['aliases'] ?? []);
            }

View on GitHub (pinned to a414c3a491)