twigphp/Twig · error · Twig\Error\RuntimeError

Only iterable values can be appended to SeparatedTokenList.

Error message

Only iterable values can be appended to SeparatedTokenList.

What it means

SeparatedTokenList::appendFrom adds new tokens to this list; the new value must be iterable so it can be spread into the internal token array. Non-iterable values (string, null, scalar) raise this RuntimeError. Note the constructor accepts scalars but appendFrom does not — wrap a single token in an array.

Solutions

  1. Wrap the token in an array: appendFrom(['extra-class']).
  2. Append an array/Traversable of tokens.
  3. If appending another list, use a SeparatedTokenList with the same separator and merge/append its value array.

Example fix

// before
$list->appendFrom('extra-class');
// after
$list->appendFrom(['extra-class']);
Defensive patterns

Strategy: type-guard

Validate before calling

if (!is_iterable($newValue)) {
    $newValue = [$newValue];
}
$list->appendFrom($newValue);

Type guard

function asTokenIterable(mixed $v): array {
    return is_iterable($v) ? [...$v] : [$v];
}

Try / catch

try {
    $list->appendFrom($newValue);
} catch (\Twig\Error\RuntimeError $e) {
    if (str_contains($e->getMessage(), 'can be appended to SeparatedTokenList')) {
        $list->appendFrom([$newValue]);
    } else {
        throw $e;
    }
}

Prevention

When it happens

Trigger: Calling $list->appendFrom('extra-class') with a raw string, appendFrom(null), or appendFrom(42).

Common situations: Appending a single class name string instead of an array of class names.

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/14263ff864d9d2be. Report an issue: GitHub.

Appendix: source

Thrown at extra/html-extra/HtmlAttr/SeparatedTokenList.php:50

    }

    public function mergeInto(mixed $previous): mixed
    {
        if ($previous instanceof self && $previous->separator === $this->separator) {
            return new self([...$previous->value, ...$this->value], $this->separator);
        }

        if (is_iterable($previous)) {
            return new self([...$previous, ...$this->value], $this->separator);
        }

        throw new RuntimeError('SeparatedTokenList can only be merged with iterables or other SeparatedTokenList instances using the same separator.');
    }

    public function appendFrom(mixed $newValue): mixed
    {
        if (!is_iterable($newValue)) {
            throw new RuntimeError('Only iterable values can be appended to SeparatedTokenList.');
        }

        return new self([...$this->value, ...$newValue], $this->separator);
    }

    public function getValue(): ?string
    {
        $filtered = array_filter($this->value, static fn ($v) => null !== $v && false !== $v);

        // Omit attribute if list contains only false or null values
        if (!$filtered) {
            return null;
        }

        // true values are not printed, but result in the attribute not being omitted
        return trim(implode($this->separator, array_filter($filtered, static fn ($v) => true !== $v)));
    }
}

View on GitHub (pinned to a414c3a491)