Guidelines
Description of the bug
not() is documented as removing the matching elements. When the argument is an SplObjectStorage it does the exact opposite: it keeps only the nodes in the storage and discards everything else, turning the negation into an intersection.
The other three accepted argument types (CSS selector, DOMElement, array of DOMNodes) all behave correctly, so the bug is confined to one branch.
Root cause
src/Helpers/QueryFilters.php:719-723 — the condition is missing its negation:
} elseif ($selector instanceof SplObjectStorage) {
foreach ($this->matches as $m) {
if ($selector->offsetExists($m)) { // <-- should be ! $selector->offsetExists($m)
$found->offsetSet($m);
}
}
}
Compare the array branch directly above it, which has the negation the storage branch lacks:
} elseif (is_array($selector)) {
foreach ($this->matches as $m) {
if (! in_array($m, $selector, true)) {
$found->offsetSet($m);
}
}
}
Note when reproducing
Node identity is per-document, so both sides must come from the same DOMDocument or nothing matches either way and the bug is masked. The snippet below uses branch() to guarantee that.
Workaround
Pass $other->get() (array) rather than $other->get(null, true) (SplObjectStorage).
QueryPath version
4.2.0 (also reproduces on main at bed5d2c)
PHP Version and environment (server type, cli provider etc., enclosing libraries and their respective versions)
PHP 8.3.16 CLI (macOS, Homebrew). Not PHP-8 specific — the cause is present on every supported version.
Minimal reproducible PHP+HTML snippet to replicate bug
<?php
require __DIR__ . '/vendor/autoload.php';
$xml = '<?xml version="1.0"?><root><a/><b/><c/></root>';
$all = qp($xml, 'a, b, c');
$exclude = $all->branch()->top()->find('a'); // same document, so node identity holds
$names = function ($q) {
$o = [];
foreach ($q->get() as $n) { $o[] = $n->nodeName; }
return implode(',', $o) ?: '(empty)';
};
echo $names($all->not($exclude->get())), "\n"; // b,c - correct
echo $names($all->not($exclude->get(null, true))), "\n"; // a - wrong, expected b,c
Guidelines
Description of the bug
not()is documented as removing the matching elements. When the argument is anSplObjectStorageit does the exact opposite: it keeps only the nodes in the storage and discards everything else, turning the negation into an intersection.The other three accepted argument types (CSS selector,
DOMElement, array ofDOMNodes) all behave correctly, so the bug is confined to one branch.Root cause
src/Helpers/QueryFilters.php:719-723— the condition is missing its negation:Compare the array branch directly above it, which has the negation the storage branch lacks:
Note when reproducing
Node identity is per-document, so both sides must come from the same
DOMDocumentor nothing matches either way and the bug is masked. The snippet below usesbranch()to guarantee that.Workaround
Pass
$other->get()(array) rather than$other->get(null, true)(SplObjectStorage).QueryPath version
4.2.0 (also reproduces on
mainat bed5d2c)PHP Version and environment (server type, cli provider etc., enclosing libraries and their respective versions)
PHP 8.3.16 CLI (macOS, Homebrew). Not PHP-8 specific — the cause is present on every supported version.
Minimal reproducible PHP+HTML snippet to replicate bug