getgrav/grav · error · InvalidArgumentException
Invalid arguments, expected DOMElement or DOMDocument
Error message
Invalid arguments, expected DOMElement or DOMDocument
What it means
DOMWordsIterator walks a DOM subtree one word at a time (used by Grav\Common\Helpers\Truncator::truncateWords and the |safe_truncate Twig filters). Like its letters sibling, the constructor accepts only a DOMElement or a DOMDocument (from which it takes documentElement); any other DOMNode — or a document with no root element — fails the instanceof check and throws InvalidArgumentException. The iterator must anchor its recursive word walk on an element.
Source
Thrown at system/src/DOMWordsIterator.php:46
private $offset = -1;
/** @var int|null */
private $key;
/** @var array<int,array<int,int|string>>|null */
private $words;
/**
* 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 currentWordPosition(): array
{
return [$this->current, $this->offset, $this->words];
}
/**View on GitHub (pinned to 6040efed04)
Solutions
- Pass a DOMElement: use $doc->documentElement when starting from a DOMDocument, or the wrapper element you created around the fragment
- Check for null before constructing: ->item(0) and ->firstChild return null when nothing matched
- Load raw HTML through a wrapper first: $doc->loadHTML('<div>' . $html . '</div>') and iterate that div
- Guard the call site with an instanceof check to fail with a clearer message
Example fix
// before
$words = new DOMWordsIterator($container); // $container may be null (no <div> found)
// after
$container = $doc->getElementsByTagName('div')->item(0);
if (!$container instanceof DOMElement) {
return $html; // nothing to truncate
}
$words = new DOMWordsIterator($container->parentNode->removeChild($container)); Defensive patterns
Strategy: type-guard
Validate before calling
$container = $doc->getElementsByTagName('div')->item(0);
if (!$container instanceof DOMElement) {
return $html; // no wrapper found — skip truncation instead of throwing
} Type guard
function isTraversableDomRoot($node): bool
{
if ($node instanceof DOMDocument) {
$node = $node->documentElement;
}
return $node instanceof DOMElement;
} Prevention
- Centralize DOM loading in one helper that wraps fragments in a <div> and returns the element, not the document
- Treat DOMText/DOMAttr/DOMComment as invalid roots — only DOMElement (or a non-empty DOMDocument) is accepted
- Write a unit test feeding empty strings and attribute nodes to your truncation helpers to catch regressions
When it happens
Trigger: new DOMWordsIterator($textNode) where the node is a DOMText/DOMAttr/DOMComment; passing the result of an unchecked ->item(0) lookup that returned null; passing an empty DOMDocument (loadHTML of an empty string) whose documentElement is null.
Common situations: Plugins/themes implementing custom safe-truncate Twig filters that walk the DOM and pass the wrong node; processing user-supplied HTML whose structure does not match what the traversal expected; refactors that changed which node gets passed to the iterator.
Related errors
- Invalid arguments, expected DOMElement or DOMDocument
- Configuration is missing streams.schemes!
- Unable to %s: %s
- Unknown error
- Unable to create directory: %s
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/e47927aa37a6d74e.
Report an issue: GitHub.