twigphp/Twig · error · RuntimeError

The "reduce" filter expects a sequence or a mapping, got

Error message

The "reduce" filter expects a sequence or a mapping, got "%s".

What it means

The Twig `reduce` filter (CoreExtension::reduce, called via twig_array_reduce) folds an iterable into a single value using an arrow function. Twig throws this RuntimeError when the input is not a sequence or mapping (i.e. `is_iterable()` fails). The check happens at runtime in PHP, so only affected code paths surface it.

Solutions

  1. Default the input to an empty array: `{{ (items ?? [])|reduce((acc, v) => acc + v, 0) }}`.
  2. Ensure the upstream code always returns an array (e.g. `?? []` in PHP, or fix the repository method).
  3. Branch on `{% if items is iterable %}` when the shape can legitimately differ.
  4. Catch Twig\Error\RuntimeError around Template::render to capture the failing template and line.

Example fix

// before (Twig)
{{ amounts|reduce((carry, a) => carry + a, 0) }}

// after
{{ (amounts ?? [])|reduce((carry, a) => carry + a, 0) }}
Defensive patterns

Strategy: validation

Validate before calling

// PHP, before rendering
def ensure_iterable_for_reduce($value): void {
    if (!is_iterable($value)) {
        throw new InvalidArgumentException('reduce() input must be iterable, got ' . get_debug_type($value));
    }
}

Type guard

function isIterable($v): bool { return is_array($v) || $v instanceof \Traversable; }

Try / catch

try {
    $html = $twig->render('tpl.html.twig', $context);
} catch (\Twig\Error\RuntimeError $e) {
    if (str_contains($e->getMessage(), '"reduce" filter expects a sequence or a mapping')) {
        // use a zero-value default instead
    } else { throw $e; }
}

Prevention

When it happens

Trigger: `{{ items|reduce((acc, v) => acc + v, 0) }}` where items is null/scalar. Also when a nullable field is passed directly and happens to be null. Guard at CoreExtension.php:2122.

Common situations: Aggregating totals over a collection that is missing/null (no rows fetched, optional relation not loaded), config scalars passed into aggregation chains, functions returning null on empty results instead of arrays.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

Thrown at src/Extension/CoreExtension.php:2122

        self::checkArrow($isSandboxed, $arrow, 'map', 'filter');

        $r = [];
        foreach ($array as $k => $v) {
            $r[$k] = $arrow($v, $k);
        }

        return $r;
    }

    /**
     * @param \Closure $arrow
     *
     * @internal
     */
    public static function reduce(Environment $env, bool $isSandboxed, $array, $arrow, $initial = null)
    {
        if (!is_iterable($array)) {
            throw new RuntimeError(\sprintf('The "reduce" filter expects a sequence or a mapping, got "%s".', get_debug_type($array)));
        }

        self::checkArrow($isSandboxed, $arrow, 'reduce', 'filter');

        $accumulator = $initial;
        foreach ($array as $key => $value) {
            $accumulator = $arrow($accumulator, $value, $key);
        }

        return $accumulator;
    }

    /**
     * @param \Closure $arrow
     *
     * @internal
     */
    public static function arraySome(Environment $env, $array, $arrow, bool $isSandboxed = false)

View on GitHub (pinned to a414c3a491)