{"record":{"id":"e47927aa37a6d74e","repo":"getgrav/grav","slug":"invalid-arguments-expected-domelement-or-domdocum-e47927","errorCode":null,"errorMessage":"Invalid arguments, expected DOMElement or DOMDocument","messagePattern":"Invalid arguments, expected DOMElement or DOMDocument","errorType":"exception","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"system/src/DOMWordsIterator.php","lineNumber":46,"sourceCode":"    private $offset = -1;\n    /** @var int|null */\n    private $key;\n    /** @var array<int,array<int,int|string>>|null */\n    private $words;\n\n    /**\n     * expects DOMElement or DOMDocument (see DOMDocument::load and DOMDocument::loadHTML)\n     *\n     * @param DOMNode $el\n     */\n    public function __construct(DOMNode $el)\n    {\n        if ($el instanceof DOMDocument) {\n            $el = $el->documentElement;\n        }\n\n        if (!$el instanceof DOMElement) {\n            throw new InvalidArgumentException('Invalid arguments, expected DOMElement or DOMDocument');\n        }\n\n        $this->start = $el;\n    }\n\n    /**\n     * Returns position in text as DOMText node and character offset.\n     * (it's NOT a byte offset, you must use mb_substr() or similar to use this offset properly).\n     * node may be NULL if iterator has finished.\n     *\n     * @return array\n     */\n    public function currentWordPosition(): array\n    {\n        return [$this->current, $this->offset, $this->words];\n    }\n\n    /**","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/DOMWordsIterator.php#L28-L64","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\n$words = new DOMWordsIterator($container); // $container may be null (no <div> found)\n\n// after\n$container = $doc->getElementsByTagName('div')->item(0);\nif (!$container instanceof DOMElement) {\n    return $html; // nothing to truncate\n}\n$words = new DOMWordsIterator($container->parentNode->removeChild($container));","handlingStrategy":"type-guard","validationCode":"$container = $doc->getElementsByTagName('div')->item(0);\nif (!$container instanceof DOMElement) {\n    return $html; // no wrapper found — skip truncation instead of throwing\n}","typeGuard":"function isTraversableDomRoot($node): bool\n{\n    if ($node instanceof DOMDocument) {\n        $node = $node->documentElement;\n    }\n    return $node instanceof DOMElement;\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["php","dom","html-parsing","invalid-argument","iterator"],"backgroundTag":"invalid-constructor-argument","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}