hamcrest/hamcrest-php · error · InvalidArgumentException

Must pass a valid XML document

Error message

Must pass a valid XML document

What it means

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.

Solutions

  1. Validate the XML string with a standalone loadXML check before matching
  2. Fix the XML source so it is well-formed (escape &, close tags, valid declaration)
  3. If the content is actually HTML, remove the <?xml declaration so the HTML branch is used
  4. Use a validator (DOMDocument::loadXML in try/catch or xml_parse) on the producer side

Example fix

// before
assertThat($badXml, hasXPath('//item'));
// after
$doc = new DOMDocument();
libxml_use_internal_errors(true);
if (!@$doc->loadXML($badXml)) { throw new RuntimeException('XML malformed: '.libxml_get_last_error()->message); }
assertThat($badXml, hasXPath('//item'));
Defensive patterns

Strategy: validation

Validate before calling

function isValidXml(string $text): bool {
    if (!preg_match('/^\s*<\?xml/', $text)) return false;
    $d = new DOMDocument();
    libxml_use_internal_errors(true);
    $ok = @$d->loadXML($text);
    libxml_clear_errors();
    return $ok;
}

Try / catch

try {
    assertThat($xml, hasXPath('//item'));
} catch (\InvalidArgumentException $e) {
    if (strpos($e->getMessage(), 'valid XML document') === false) throw $e;
    fail('Malformed XML received: '.substr($xml, 0, 200));
}

Prevention

When it happens

Trigger: Passing text starting with <?xml that is not well-formed XML to hasXPath(), e.g. truncated documents, unescaped ampersands, mismatched tags.

Common situations: API responses truncated mid-document; invalid XML fed from config files or fixtures; XML containing entities not declared.

Related errors


AI-assisted analysis of hamcrest/hamcrest-php@aa726aeff9 (2026-09-15). Data as JSON: /api/errors/fa06b3bbb2f98b65. Report an issue: GitHub.

Appendix: source

Thrown at hamcrest/Hamcrest/Xml/HasXPath.php:81

        } else {
            return $this->matchesExpression($result, $mismatchDescription);
        }
    }

    /**
     * Creates and returns a <code>DOMDocument</code> from the given
     * XML or HTML string.
     *
     * @param string $text
     * @return \DOMDocument built from <code>$text</code>
     * @throws \InvalidArgumentException if the document is not valid
     */
    protected function createDocument($text)
    {
        $document = new \DOMDocument();
        if (preg_match('/^\s*<\?xml/', $text)) {
            if (!@$document->loadXML($text)) {
                throw new \InvalidArgumentException('Must pass a valid XML document');
            }
        } else {
            if (!@$document->loadHTML($text)) {
                throw new \InvalidArgumentException('Must pass a valid HTML or XHTML document');
            }
        }

        return $document;
    }

    /**
     * Applies the configured XPath to the DOM node and returns either
     * the result if it's an expression or the node list if it's a query.
     *
     * @param \DOMNode $node context from which to issue query
     * @return mixed result of expression or DOMNodeList from query
     */
    protected function evaluate(\DOMNode $node)

View on GitHub (pinned to aa726aeff9)