{"record":{"id":"fa06b3bbb2f98b65","repo":"hamcrest/hamcrest-php","slug":"must-pass-a-valid-xml-document","errorCode":null,"errorMessage":"Must pass a valid XML document","messagePattern":"Must pass a valid XML document","errorType":"exception","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"hamcrest/Hamcrest/Xml/HasXPath.php","lineNumber":81,"sourceCode":"        } else {\n            return $this->matchesExpression($result, $mismatchDescription);\n        }\n    }\n\n    /**\n     * Creates and returns a <code>DOMDocument</code> from the given\n     * XML or HTML string.\n     *\n     * @param string $text\n     * @return \\DOMDocument built from <code>$text</code>\n     * @throws \\InvalidArgumentException if the document is not valid\n     */\n    protected function createDocument($text)\n    {\n        $document = new \\DOMDocument();\n        if (preg_match('/^\\s*<\\?xml/', $text)) {\n            if (!@$document->loadXML($text)) {\n                throw new \\InvalidArgumentException('Must pass a valid XML document');\n            }\n        } else {\n            if (!@$document->loadHTML($text)) {\n                throw new \\InvalidArgumentException('Must pass a valid HTML or XHTML document');\n            }\n        }\n\n        return $document;\n    }\n\n    /**\n     * Applies the configured XPath to the DOM node and returns either\n     * the result if it's an expression or the node list if it's a query.\n     *\n     * @param \\DOMNode $node context from which to issue query\n     * @return mixed result of expression or DOMNodeList from query\n     */\n    protected function evaluate(\\DOMNode $node)","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/hamcrest/hamcrest-php/blob/aa726aeff92ee86f82a89ca30dc0af9e01fb3539/hamcrest/Hamcrest/Xml/HasXPath.php#L63-L99","documentation":"HasXPath::createDocument() detects a leading XML declaration (<?xml) and then attempts loadXML(); when parsing fails (invalid XML) it throws InvalidArgumentException with 'Must pass a valid XML document'. loadXML errors are suppressed with @ so the exception is the only failure signal.","triggerScenarios":"Passing text starting with <?xml that is not well-formed XML to hasXPath(), e.g. truncated documents, unescaped ampersands, mismatched tags.","commonSituations":"API responses truncated mid-document; invalid XML fed from config files or fixtures; XML containing entities not declared.","solutions":["Validate the XML string with a standalone loadXML check before matching","Fix the XML source so it is well-formed (escape &, close tags, valid declaration)","If the content is actually HTML, remove the <?xml declaration so the HTML branch is used","Use a validator (DOMDocument::loadXML in try/catch or xml_parse) on the producer side"],"exampleFix":"// before\nassertThat($badXml, hasXPath('//item'));\n// after\n$doc = new DOMDocument();\nlibxml_use_internal_errors(true);\nif (!@$doc->loadXML($badXml)) { throw new RuntimeException('XML malformed: '.libxml_get_last_error()->message); }\nassertThat($badXml, hasXPath('//item'));","handlingStrategy":"validation","validationCode":"function isValidXml(string $text): bool {\n    if (!preg_match('/^\\s*<\\?xml/', $text)) return false;\n    $d = new DOMDocument();\n    libxml_use_internal_errors(true);\n    $ok = @$d->loadXML($text);\n    libxml_clear_errors();\n    return $ok;\n}","typeGuard":null,"tryCatchPattern":"try {\n    assertThat($xml, hasXPath('//item'));\n} catch (\\InvalidArgumentException $e) {\n    if (strpos($e->getMessage(), 'valid XML document') === false) throw $e;\n    fail('Malformed XML received: '.substr($xml, 0, 200));\n}","preventionTips":["Validate fixtures/responses with loadXML once at setup","Set libxml_use_internal_errors(true) and log libxml_get_errors() in producers","Escape & as &amp; and verify character encoding declarations","Snapshot-test API XML output to catch regressions"],"tags":["php","xml","hamcrest","parse-error"],"backgroundTag":"xml-parse-error","analyzedSha":"aa726aeff92ee86f82a89ca30dc0af9e01fb3539","analyzedAt":"2026-09-15T04:07:57.177Z","contentChangedAt":"2026-09-15T04:07:57.177Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}