Guidelines
Description of the bug
detach($selector) is documented as detaching the elements matching $selector. The argument has no effect: the query runs, its result is thrown away, and whatever was already selected is detached instead.
This is silently destructive — $qp->detach('i') removes the $qp elements themselves rather than their i descendants.
Root cause
src/Helpers/QueryMutators.php:706-709 discards the return value of find():
public function detach($selector = null): Query
{
if (null !== $selector) {
$this->find($selector); // <-- return value discarded
}
$found = new SplObjectStorage();
$this->last = $this->matches;
foreach ($this->matches as $item) {
find() returns a new DOMQuery and leaves $this->matches untouched, so the loop below still iterates the original match set. findInPlace($selector) is the variant that was intended here.
Suggested fix
if (null !== $selector) {
$this->findInPlace($selector);
}
Note that remove() — the sibling method — does honour its selector, so the two currently disagree.
Workaround
$qp->find($selector)->detach().
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><p><i>a</i></p></root>';
$qp = qp($xml, 'p');
$removed = $qp->detach('i');
var_dump($removed->tag()); // 'p' - expected 'i'
var_dump($qp->top()->xml(true)); // '<root/>' - the <p> was removed, not the <i>
Guidelines
Description of the bug
detach($selector)is documented as detaching the elements matching$selector. The argument has no effect: the query runs, its result is thrown away, and whatever was already selected is detached instead.This is silently destructive —
$qp->detach('i')removes the$qpelements themselves rather than theiridescendants.Root cause
src/Helpers/QueryMutators.php:706-709discards the return value offind():find()returns a newDOMQueryand leaves$this->matchesuntouched, so the loop below still iterates the original match set.findInPlace($selector)is the variant that was intended here.Suggested fix
Note that
remove()— the sibling method — does honour its selector, so the two currently disagree.Workaround
$qp->find($selector)->detach().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