Skip to content

firstChild() returns at most one node regardless of how many elements are selected #78

Description

@jakejackson1

Guidelines

Description of the bug

firstChild() returns the first child of the first selected element only, then stops. Its counterpart lastChild() correctly returns one node per selected element, so the two are asymmetric.

Root cause

src/Helpers/QueryFilters.php:476-494 — the $flag breaks out of the outer loop after the first element is processed:

public function firstChild(): Query
{
	$found = new SplObjectStorage();
	$flag  = false;
	foreach ($this->matches as $m) {
		foreach ($m->childNodes as $c) {
			if ($c->nodeType === XML_ELEMENT_NODE) {
				$found->offsetSet($c);
				$flag = true;
				break;
			}
		}
		if ($flag) {
			break;          // <-- stops after the first matched element
		}
	}

	return $this->inst($found, null);
}

lastChild() has no such outer break, which is why it behaves as documented.

Suggested fix

Drop $flag and the outer break, leaving only the inner break that stops at the first child element of each match.

This does change behaviour for anyone relying on the current output, so it may be worth confirming whether first()-like semantics were ever intended here. The DocBlock ("Get the first child of the matching element") and the symmetry with lastChild() both suggest one node per match was the intent.

Workaround

children()->first() for a single node, or children() grouped per element.

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><i>b</i></p><p><i>c</i><i>d</i></p></root>';

var_dump(qp($xml, 'p')->firstChild()->textImplode(','));  // 'a'   - expected 'a,c'
var_dump(qp($xml, 'p')->lastChild()->textImplode(','));   // 'b,d' - correct

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions