Commit b8fc6bd1c8e for php.net

commit b8fc6bd1c8ed62080ff7775194c735ea9f230a1e
Author: Niels Dossche <7771979+ndossche@users.noreply.github.com>
Date:   Sun Feb 1 10:41:37 2026 +0100

    Fix GH-21097: Accessing Dom\Node properties can can throw TypeError(s)

    Split the handler again, or defer to instanceof when performance doesn't
    matter.

    Closes GH-21108.

diff --git a/NEWS b/NEWS
index a2fec5115dd..6416ddedb98 100644
--- a/NEWS
+++ b/NEWS
@@ -16,6 +16,8 @@ PHP                                                                        NEWS
 - DOM:
   . Fixed bug GH-21077 (Accessing Dom\Node::baseURI can throw TypeError).
     (ndossche)
+  . Fixed bug GH-21097 (Accessing Dom\Node properties can can throw TypeError).
+    (ndossche)

 - PDO_PGSQL:
   . Fixed bug GH-21055 (connection attribute status typo for GSS negotiation).
diff --git a/ext/dom/documenttype.c b/ext/dom/documenttype.c
index 666dae56dc0..6af23fd73c4 100644
--- a/ext/dom/documenttype.c
+++ b/ext/dom/documenttype.c
@@ -47,7 +47,7 @@ zend_result dom_documenttype_entities_read(dom_object *obj, zval *retval)
 {
 	DOM_PROP_NODE(xmlDtdPtr, dtdptr, obj);

-	php_dom_create_iterator(retval, DOM_DTD_NAMEDNODEMAP, php_dom_follow_spec_intern(obj));
+	php_dom_create_iterator(retval, DOM_DTD_NAMEDNODEMAP, instanceof_function(obj->std.ce, dom_modern_documenttype_class_entry));

 	xmlHashTable *entityht = (xmlHashTable *) dtdptr->entities;

@@ -68,7 +68,7 @@ zend_result dom_documenttype_notations_read(dom_object *obj, zval *retval)
 {
 	DOM_PROP_NODE(xmlDtdPtr, dtdptr, obj);

-	php_dom_create_iterator(retval, DOM_DTD_NAMEDNODEMAP, php_dom_follow_spec_intern(obj));
+	php_dom_create_iterator(retval, DOM_DTD_NAMEDNODEMAP, instanceof_function(obj->std.ce, dom_modern_documenttype_class_entry));

 	xmlHashTable *notationht = (xmlHashTable *) dtdptr->notations;

diff --git a/ext/dom/dom_properties.h b/ext/dom/dom_properties.h
index e8fd7535db8..f82ce2645af 100644
--- a/ext/dom/dom_properties.h
+++ b/ext/dom/dom_properties.h
@@ -102,6 +102,7 @@ zend_result dom_entity_version_read(dom_object *obj, zval *retval);
 zend_result dom_entity_reference_child_read(dom_object *obj, zval *retval);
 zend_result dom_entity_reference_text_content_read(dom_object *obj, zval *retval);
 zend_result dom_entity_reference_child_nodes_read(dom_object *obj, zval *retval);
+zend_result dom_modern_entity_reference_child_nodes_read(dom_object *obj, zval *retval);

 /* namednodemap properties */
 zend_result dom_namednodemap_length_read(dom_object *obj, zval *retval);
@@ -119,6 +120,7 @@ zend_result dom_node_node_type_read(dom_object *obj, zval *retval);
 zend_result dom_node_parent_node_read(dom_object *obj, zval *retval);
 zend_result dom_node_parent_element_read(dom_object *obj, zval *retval);
 zend_result dom_node_child_nodes_read(dom_object *obj, zval *retval);
+zend_result dom_modern_node_child_nodes_read(dom_object *obj, zval *retval);
 zend_result dom_node_first_child_read(dom_object *obj, zval *retval);
 zend_result dom_node_last_child_read(dom_object *obj, zval *retval);
 zend_result dom_node_previous_sibling_read(dom_object *obj, zval *retval);
diff --git a/ext/dom/entityreference.c b/ext/dom/entityreference.c
index bea49d85d0f..215df208f53 100644
--- a/ext/dom/entityreference.c
+++ b/ext/dom/entityreference.c
@@ -106,4 +106,12 @@ zend_result dom_entity_reference_child_nodes_read(dom_object *obj, zval *retval)
 	return dom_node_child_nodes_read(obj, retval);
 }

+zend_result dom_modern_entity_reference_child_nodes_read(dom_object *obj, zval *retval)
+{
+	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
+
+	dom_entity_reference_fetch_and_sync_declaration(nodep);
+	return dom_modern_node_child_nodes_read(obj, retval);
+}
+
 #endif
diff --git a/ext/dom/node.c b/ext/dom/node.c
index b6a794edcdd..9c1a508d669 100644
--- a/ext/dom/node.c
+++ b/ext/dom/node.c
@@ -287,7 +287,18 @@ zend_result dom_node_child_nodes_read(dom_object *obj, zval *retval)
 {
 	DOM_PROP_NODE(xmlNodePtr, nodep, obj);

-	php_dom_create_iterator(retval, DOM_NODELIST, php_dom_follow_spec_intern(obj));
+	php_dom_create_iterator(retval, DOM_NODELIST, false);
+	dom_object *intern = Z_DOMOBJ_P(retval);
+	dom_namednode_iter(obj, XML_ELEMENT_NODE, intern, NULL, NULL, 0, NULL, 0);
+
+	return SUCCESS;
+}
+
+zend_result dom_modern_node_child_nodes_read(dom_object *obj, zval *retval)
+{
+	DOM_PROP_NODE(xmlNodePtr, nodep, obj);
+
+	php_dom_create_iterator(retval, DOM_NODELIST, true);
 	dom_object *intern = Z_DOMOBJ_P(retval);
 	dom_namednode_iter(obj, XML_ELEMENT_NODE, intern, NULL, NULL, 0, NULL, 0);

diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c
index a4dbec08631..135b3cdc5ca 100644
--- a/ext/dom/php_dom.c
+++ b/ext/dom/php_dom.c
@@ -897,7 +897,7 @@ PHP_MINIT_FUNCTION(dom)
 	DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "ownerDocument", dom_node_owner_document_read, NULL);
 	DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "parentNode", dom_node_parent_node_read, NULL);
 	DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "parentElement", dom_node_parent_element_read, NULL);
-	DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "childNodes", dom_node_child_nodes_read, NULL);
+	DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "childNodes", dom_modern_node_child_nodes_read, NULL);
 	DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "firstChild", dom_node_first_child_read, NULL);
 	DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "lastChild", dom_node_last_child_read, NULL);
 	DOM_REGISTER_PROP_HANDLER(&dom_modern_node_prop_handlers, "previousSibling", dom_node_previous_sibling_read, NULL);
@@ -1294,7 +1294,7 @@ PHP_MINIT_FUNCTION(dom)
 	DOM_OVERWRITE_PROP_HANDLER(&dom_modern_entity_reference_prop_handlers, "firstChild", dom_entity_reference_child_read, NULL);
 	DOM_OVERWRITE_PROP_HANDLER(&dom_modern_entity_reference_prop_handlers, "lastChild", dom_entity_reference_child_read, NULL);
 	DOM_OVERWRITE_PROP_HANDLER(&dom_modern_entity_reference_prop_handlers, "textContent", dom_entity_reference_text_content_read, NULL);
-	DOM_OVERWRITE_PROP_HANDLER(&dom_modern_entity_reference_prop_handlers, "childNodes", dom_entity_reference_child_nodes_read, NULL);
+	DOM_OVERWRITE_PROP_HANDLER(&dom_modern_entity_reference_prop_handlers, "childNodes", dom_modern_entity_reference_child_nodes_read, NULL);
 	zend_hash_add_new_ptr(&classes, dom_modern_entityreference_class_entry->name, &dom_modern_entity_reference_prop_handlers);

 	dom_processinginstruction_class_entry = register_class_DOMProcessingInstruction(dom_node_class_entry);
diff --git a/ext/dom/tests/modern/common/gh21097.phpt b/ext/dom/tests/modern/common/gh21097.phpt
new file mode 100644
index 00000000000..db6abd12fcf
--- /dev/null
+++ b/ext/dom/tests/modern/common/gh21097.phpt
@@ -0,0 +1,49 @@
+--TEST--
+GH-21097 (Accessing Dom\Node properties can can throw TypeError(s))
+--EXTENSIONS--
+dom
+--CREDITS--
+mbeccati
+--FILE--
+<?php
+
+$implementation = new \Dom\Implementation();
+$node = $implementation->createDocumentType('html', 'publicId', 'systemId');
+
+$r = new \ReflectionClass($node);
+foreach ($r->getProperties(\ReflectionProperty::IS_PUBLIC) as $p) {
+    echo $p->getName(), ": ";
+    var_dump($node->{$p->getName()});
+}
+
+?>
+--EXPECT--
+nodeType: int(10)
+nodeName: string(4) "html"
+baseURI: string(11) "about:blank"
+isConnected: bool(false)
+ownerDocument: NULL
+parentNode: NULL
+parentElement: NULL
+childNodes: object(Dom\NodeList)#24 (1) {
+  ["length"]=>
+  int(0)
+}
+firstChild: NULL
+lastChild: NULL
+previousSibling: NULL
+nextSibling: NULL
+nodeValue: NULL
+textContent: string(0) ""
+name: string(4) "html"
+entities: object(Dom\DtdNamedNodeMap)#24 (1) {
+  ["length"]=>
+  int(0)
+}
+notations: object(Dom\DtdNamedNodeMap)#24 (1) {
+  ["length"]=>
+  int(0)
+}
+publicId: string(8) "publicId"
+systemId: string(8) "systemId"
+internalSubset: NULL