diff --git a/ext/simplexml/simplexml.c b/ext/simplexml/simplexml.c
index 0e02a843e95a..e81a2193a623 100644
--- a/ext/simplexml/simplexml.c
+++ b/ext/simplexml/simplexml.c
@@ -1107,7 +1107,14 @@ static HashTable *sxe_get_prop_hash(zend_object *object, int is_debug) /* {{{ */
}
GET_NODE(sxe, node);
- node = php_sxe_get_first_node_non_destructive(sxe, node);
+ if (sxe->iter.type == SXE_ITER_CHILD) {
+ /* Start at the first child rather than at the first element child, so that
+ * non-element nodes (e.g. PIs, comments) before it are not skipped. Like
+ * sxe_prop_is_empty(), only do so if there is a matching element child. */
+ node = php_sxe_get_first_node_non_destructive(sxe, node) ? node->children : NULL;
+ } else {
+ node = php_sxe_get_first_node_non_destructive(sxe, node);
+ }
if (node && sxe->iter.type != SXE_ITER_ATTRLIST) {
if (node->type == XML_ATTRIBUTE_NODE) {
@@ -1128,7 +1135,7 @@ static HashTable *sxe_get_prop_hash(zend_object *object, int is_debug) /* {{{ */
}
while (node) {
- if (node->children != NULL || node->prev != NULL || node->next != NULL || xmlIsBlankNode(node)) {
+ if (node->children != NULL || node->prev != NULL || node->next != NULL || xmlIsBlankNode(node) || sxe->iter.type == SXE_ITER_CHILD) {
SKIP_TEXT(node);
} else {
if (node->type == XML_TEXT_NODE) {
diff --git a/ext/simplexml/tests/gh12168.phpt b/ext/simplexml/tests/gh12168.phpt
new file mode 100644
index 000000000000..3f4cd9bba986
--- /dev/null
+++ b/ext/simplexml/tests/gh12168.phpt
@@ -0,0 +1,74 @@
+--TEST--
+GH-12168 (Mixing processing instructions and element nodes gives inconsistent results in SimpleXML)
+--EXTENSIONS--
+simplexml
+--FILE--
+');
+var_dump($sxe->children());
+
+$sxe = simplexml_load_string('');
+var_dump($sxe->children());
+
+foreach ([
+ '',
+ '',
+ '',
+ 'text',
+] as $xml) {
+ $sxe = simplexml_load_string($xml);
+ echo $xml, ': ', json_encode(array_keys((array) $sxe->children())), "\n";
+}
+
+/* Non-element nodes only show up alongside a matching element, consistent with (bool) */
+foreach ([
+ '',
+ '',
+ 't',
+ ']>&e;',
+] as $xml) {
+ $sxe = simplexml_load_string($xml);
+ echo $xml, ': ', json_encode(array_keys((array) $sxe->children())), ' ', var_export((bool) $sxe->children(), true), "\n";
+}
+
+$sxe = simplexml_load_string('');
+var_dump(array_keys((array) $sxe->children('urn:a')));
+$sxe = simplexml_load_string('');
+var_dump(array_keys((array) $sxe->children('urn:a')), (bool) $sxe->children('urn:a'));
+
+?>
+--EXPECTF--
+object(SimpleXMLElement)#%d (2) {
+ ["x"]=>
+ object(SimpleXMLElement)#%d (0) {
+ }
+ ["hello"]=>
+ object(SimpleXMLElement)#%d (0) {
+ }
+}
+object(SimpleXMLElement)#%d (2) {
+ ["hello"]=>
+ object(SimpleXMLElement)#%d (0) {
+ }
+ ["x"]=>
+ object(SimpleXMLElement)#%d (0) {
+ }
+}
+: ["comment","x"]
+: ["x","comment"]
+: ["pi","x","y"]
+text: []
+: [] false
+: [] false
+t: [] false
+]>&e;: ["e","x"] true
+array(2) {
+ [0]=>
+ string(2) "pi"
+ [1]=>
+ string(1) "x"
+}
+array(0) {
+}
+bool(false)