[php-src] master: Merge branch 'PHP-8.4' into PHP-8.5

Ilia Alshanetsky <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Date: 2026-06-25T18:55:02-04:00

Commit: https://github.com/php/php-src/commit/f605d20cfd23417230c5b5e5e5269530b10e0e8f
Raw diff: https://github.com/php/php-src/commit/f605d20cfd23417230c5b5e5e5269530b10e0e8f.diff

Merge branch 'PHP-8.4' into PHP-8.5

* PHP-8.4:
  Report dynamic property shadowing a private parent in Reflection

Changed paths:
  A  ext/reflection/tests/gh22441.phpt
  M  NEWS
  M  ext/reflection/php_reflection.c


Diff:

diff --git a/NEWS b/NEWS
index 4a08f3391d77..ef8d40d00f82 100644
--- a/NEWS
+++ b/NEWS
@@ -31,6 +31,8 @@ PHP                                                                        NEWS
 - Reflection:
   . Fixed bug GH-22324 (Ignore leading namespace separator in
     ReflectionParameter::__construct()). (jorgsowa)
+  . Fixed bug GH-22441 (ReflectionClass::hasProperty() and getProperty() ignore
+    dynamic properties shadowing a private parent property). (iliaal)
 
 - SPL:
   . Fix	class_parents for classes with leading slash in non-autoload mode.                                                                     
diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c
index 5d7a15e24fef..956e6ea22d41 100644
--- a/ext/reflection/php_reflection.c
+++ b/ext/reflection/php_reflection.c
@@ -4616,19 +4616,17 @@ ZEND_METHOD(ReflectionClass, hasProperty)
 	}
 
 	GET_REFLECTION_OBJECT_PTR(ce);
-	if ((property_info = zend_hash_find_ptr(&ce->properties_info, name)) != NULL) {
-		if ((property_info->flags & ZEND_ACC_PRIVATE) && property_info->ce != ce) {
-			RETURN_FALSE;
-		}
+	if ((property_info = zend_hash_find_ptr(&ce->properties_info, name)) != NULL
+	 && (!(property_info->flags & ZEND_ACC_PRIVATE)
+	  || property_info->ce == ce)) {
 		RETURN_TRUE;
-	} else {
-		if (Z_TYPE(intern->obj) != IS_UNDEF) {
-			if (Z_OBJ_HANDLER(intern->obj, has_property)(Z_OBJ(intern->obj), name, ZEND_PROPERTY_EXISTS, NULL)) {
-				RETURN_TRUE;
-			}
+	}
+	if (Z_TYPE(intern->obj) != IS_UNDEF) {
+		if (Z_OBJ_HANDLER(intern->obj, has_property)(Z_OBJ(intern->obj), name, ZEND_PROPERTY_EXISTS, NULL)) {
+			RETURN_TRUE;
 		}
-		RETURN_FALSE;
 	}
+	RETURN_FALSE;
 }
 /* }}} */
 
@@ -4647,12 +4645,13 @@ ZEND_METHOD(ReflectionClass, getProperty)
 	}
 
 	GET_REFLECTION_OBJECT_PTR(ce);
-	if ((property_info = zend_hash_find_ptr(&ce->properties_info, name)) != NULL) {
-		if (!(property_info->flags & ZEND_ACC_PRIVATE) || property_info->ce == ce) {
-			reflection_property_factory(ce, name, property_info, return_value);
-			return;
-		}
-	} else if (Z_TYPE(intern->obj) != IS_UNDEF) {
+	if ((property_info = zend_hash_find_ptr(&ce->properties_info, name)) != NULL
+	 && (!(property_info->flags & ZEND_ACC_PRIVATE)
+	  || property_info->ce == ce)) {
+		reflection_property_factory(ce, name, property_info, return_value);
+		return;
+	}
+	if (Z_TYPE(intern->obj) != IS_UNDEF) {
 		/* Check for dynamic properties */
 		if (zend_hash_exists(Z_OBJ_HT(intern->obj)->get_properties(Z_OBJ(intern->obj)), name)) {
 			reflection_property_factory(ce, name, NULL, return_value);
diff --git a/ext/reflection/tests/gh22441.phpt b/ext/reflection/tests/gh22441.phpt
new file mode 100644
index 000000000000..9ca3cb0eee74
--- /dev/null
+++ b/ext/reflection/tests/gh22441.phpt
@@ -0,0 +1,44 @@
+--TEST--
+GH-22441 (ReflectionClass::hasProperty()/getProperty() ignore dynamic properties shadowing a private parent property)
+--FILE--
+<?php
+
+class Base {
+    private mixed $shadow;
+    private mixed $onlyBase;
+}
+
+#[AllowDynamicProperties]
+class Child extends Base {}
+
+$o = new Child();
+$o->shadow = true;
+$o->noShadow = true;
+
+$r = new ReflectionObject($o);
+
+echo "hasProperty:\n";
+echo "shadow (dynamic over private parent): ";   var_dump($r->hasProperty('shadow'));
+echo "noShadow (plain dynamic): ";               var_dump($r->hasProperty('noShadow'));
+echo "onlyBase (private parent, no dynamic): ";  var_dump($r->hasProperty('onlyBase'));
+
+echo "\ngetProperty:\n";
+foreach (['shadow', 'noShadow', 'onlyBase'] as $name) {
+    try {
+        $p = $r->getProperty($name);
+        printf("%s: %s::\$%s\n", $name, $p->getDeclaringClass()->getName(), $p->getName());
+    } catch (ReflectionException $e) {
+        printf("%s: %s\n", $name, $e->getMessage());
+    }
+}
+?>
+--EXPECT--
+hasProperty:
+shadow (dynamic over private parent): bool(true)
+noShadow (plain dynamic): bool(true)
+onlyBase (private parent, no dynamic): bool(false)
+
+getProperty:
+shadow: Child::$shadow
+noShadow: Child::$noShadow
+onlyBase: Property Child::$onlyBase does not exist
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.