getgrav/grav · error · InvalidArgumentException

Invalid arguments, expected DOMElement or DOMDocument

Error message

Invalid arguments, expected DOMElement or DOMDocument

What it means

DOMLettersIterator walks an HTML/DOM subtree one character at a time (used by Grav\Common\Helpers\Truncator::truncateLetters). Its constructor only accepts a DOMElement, or a DOMDocument from which it takes documentElement. If you hand it any other DOMNode subtype — DOMText, DOMAttr, DOMComment — or a DOMDocument that has no documentElement (empty/failed load), the instanceof check fails and it throws InvalidArgumentException. The iterator needs a real element as its recursion root, so it refuses anything else.

Source

Thrown at system/src/DOMLettersIterator.php:47

    private $offset = -1;
    /** @var int|null */
    private $key;
    /** @var array<int,string>|null */
    private $letters;

    /**
     * expects DOMElement or DOMDocument (see DOMDocument::load and DOMDocument::loadHTML)
     *
     * @param DOMNode $el
     */
    public function __construct(DOMNode $el)
    {
        if ($el instanceof DOMDocument) {
            $el = $el->documentElement;
        }

        if (!$el instanceof DOMElement) {
            throw new InvalidArgumentException('Invalid arguments, expected DOMElement or DOMDocument');
        }

        $this->start = $el;
    }

    /**
     * Returns position in text as DOMText node and character offset.
     * (it's NOT a byte offset, you must use mb_substr() or similar to use this offset properly).
     * node may be NULL if iterator has finished.
     *
     * @return array
     */
    public function currentTextPosition(): array
    {
        return [$this->current, $this->offset];
    }

    /**

View on GitHub (pinned to 6040efed04)

Solutions

  1. Pass the element itself: for a DOMDocument use $doc->documentElement, for a fragment use the wrapper DOMElement (Truncator uses the <div> it injected via loadHTML("<div>$html</div>"))
  2. Null-check any node fetched by ->item(0)/->firstChild before constructing the iterator
  3. If starting from raw HTML, wrap and load it first: $doc = new DOMDocument(); $doc->loadHTML('<div>' . $html . '</div>'); then pass $doc or its documentElement
  4. Add an instanceof DOMElement/DOMDocument guard at your call site so the failure surfaces with your own context

Example fix

// before
$iterator = new DOMLettersIterator($node); // $node may be DOMText or null

// after
if ($node instanceof DOMDocument) {
    $node = $node->documentElement;
}
if (!$node instanceof DOMElement) {
    throw new InvalidArgumentException('Expected DOMElement, got ' . get_class($node));
}
$iterator = new DOMLettersIterator($node);
Defensive patterns

Strategy: type-guard

Validate before calling

if ($node instanceof DOMDocument) {
    $node = $node->documentElement;
}
if (!$node instanceof DOMElement) {
    throw new InvalidArgumentException('DOMLettersIterator needs a DOMElement, got ' . (is_object($node) ? get_class($node) : gettype($node)));
}

Type guard

/**
 * True when DOMLettersIterator/DOMWordsIterator will accept the node.
 * @param DOMNode|DOMDocument|null $node
 */
function isTraversableDomRoot($node): bool
{
    if ($node instanceof DOMDocument) {
        $node = $node->documentElement;
    }
    return $node instanceof DOMElement;
}

Prevention

When it happens

Trigger: Calling new DOMLettersIterator($node) where $node is a DOMText/DOMAttr/DOMComment (e.g. a node grabbed via ->firstChild or ->nodeValue's parent), passing null (e.g. getElementsByTagName('div')->item(0) returned null because the markup had no div), or passing a DOMDocument produced by loadHTML('') whose documentElement is null.

Common situations: Custom Twig filters or plugins that truncate HTML and pass the wrong node from a DOM walk; feeding Truncator-style code malformed HTML so the expected wrapper element is missing; code upgraded from a version that silently tolerated other node types.

Related errors


AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17). Data as JSON: /api/errors/d04ffbfad8e3ba6a. Report an issue: GitHub.