Commit 70384ffd412 for php.net

commit 70384ffd4122a6b6ab98b4023389d47e24e6a23a
Author: Arnaud Le Blanc <arnaud.lb@gmail.com>
Date:   Wed Apr 15 11:41:39 2026 +0200

    Expose zend_reflection_property_set_raw_value, zend_reflection_property_set_raw_value_without_lazy_initialization

    Closes GH-21763

diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS
index 2f0d016a99c..a467df3b6be 100644
--- a/UPGRADING.INTERNALS
+++ b/UPGRADING.INTERNALS
@@ -118,6 +118,10 @@ PHP 8.6 INTERNALS UPGRADE NOTES
   . Added ZEND_CONTAINER_OF().
   . The OPENBASEDIR_CHECKPATH() compatibility macro has been removed, instead
     use php_check_open_basedir() directly.
+  . Added zend_reflection_property_set_raw_value_without_lazy_initialization(),
+    zend_reflection_property_set_raw_value() to expose the functionality of
+    ReflectionProperty::setRawValueWithoutLazyInitialization() and
+    ReflectionProperty::setRawValue() to C extensions.

 ========================
 2. Build system changes
diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c
index 00085968be7..6c50619a35d 100644
--- a/ext/reflection/php_reflection.c
+++ b/ext/reflection/php_reflection.c
@@ -6016,10 +6016,10 @@ ZEND_METHOD(ReflectionProperty, setValue)
  * property is overridden on 'object' and was not private in 'scope'.
  * The effective prop may add hooks or change flags. */
 static zend_property_info *reflection_property_get_effective_prop(
-		property_reference *ref, zend_class_entry *scope, zend_object *object) {
-	zend_property_info *prop = ref->prop;
+		zend_property_info *prop, zend_string *unmangled_name,
+		zend_class_entry *scope, zend_object *object) {
 	if (scope != object->ce && !(prop && (prop->flags & ZEND_ACC_PRIVATE))) {
-		prop = zend_hash_find_ptr(&object->ce->properties_info, ref->unmangled_name);
+		prop = zend_hash_find_ptr(&object->ce->properties_info, unmangled_name);
 	}
 	return prop;
 }
@@ -6052,8 +6052,8 @@ ZEND_METHOD(ReflectionProperty, getRawValue)
 		}
 	}

-	zend_property_info *prop = reflection_property_get_effective_prop(ref,
-			intern->ce, Z_OBJ_P(object));
+	zend_property_info *prop = reflection_property_get_effective_prop(ref->prop,
+			ref->unmangled_name, intern->ce, Z_OBJ_P(object));

 	if (UNEXPECTED(prop && (prop->flags & ZEND_ACC_STATIC))) {
 		zend_throw_exception(reflection_exception_ptr, "May not use getRawValue on static properties", 0);
@@ -6083,13 +6083,15 @@ ZEND_METHOD(ReflectionProperty, getRawValue)
 	}
 }

-static void reflection_property_set_raw_value(zend_property_info *prop,
-		zend_string *unmangled_name, void *cache_slot[3], reflection_object *intern,
-		zend_object *object, zval *value)
+static void zend_reflection_property_set_raw_value_ex(zend_property_info *prop,
+		zend_string *unmangled_name, void *cache_slot[3],
+		zend_class_entry *scope, zend_object *object, zval *value)
 {
+	ZEND_ASSERT(!prop || !(prop->flags & ZEND_ACC_STATIC));
+
 	if (!prop || !prop->hooks || !prop->hooks[ZEND_PROPERTY_HOOK_SET]) {
 		const zend_class_entry *old_scope = EG(fake_scope);
-		EG(fake_scope) = intern->ce;
+		EG(fake_scope) = scope;
 		object->handlers->write_property(object, unmangled_name, value, cache_slot);
 		EG(fake_scope) = old_scope;
 	} else {
@@ -6098,6 +6100,22 @@ static void reflection_property_set_raw_value(zend_property_info *prop,
 	}
 }

+PHPAPI void zend_reflection_property_set_raw_value(zend_property_info *prop,
+		zend_string *unmangled_name, void *cache_slot[3],
+		zend_class_entry *scope, zend_object *object, zval *value)
+{
+	prop = reflection_property_get_effective_prop(prop,
+			unmangled_name, scope, object);
+
+	if (UNEXPECTED(prop && (prop->flags & ZEND_ACC_STATIC))) {
+		zend_throw_exception(reflection_exception_ptr, "May not use setRawValue on static properties", 0);
+		return;
+	}
+
+	zend_reflection_property_set_raw_value_ex(prop, unmangled_name, cache_slot,
+			scope, object, value);
+}
+
 ZEND_METHOD(ReflectionProperty, setRawValue)
 {
 	reflection_object *intern;
@@ -6112,26 +6130,18 @@ ZEND_METHOD(ReflectionProperty, setRawValue)
 		Z_PARAM_ZVAL(value)
 	} ZEND_PARSE_PARAMETERS_END();

-	zend_property_info *prop = reflection_property_get_effective_prop(ref,
-			intern->ce, Z_OBJ_P(object));
-
-	if (UNEXPECTED(prop && (prop->flags & ZEND_ACC_STATIC))) {
-		zend_throw_exception(reflection_exception_ptr, "May not use setRawValue on static properties", 0);
-		RETURN_THROWS();
-	}
-
-	reflection_property_set_raw_value(prop, ref->unmangled_name,
-			ref->cache_slot, intern, Z_OBJ_P(object), value);
+	zend_reflection_property_set_raw_value(ref->prop, ref->unmangled_name,
+			ref->cache_slot, intern->ce, Z_OBJ_P(object), value);
 }

 static zend_result reflection_property_check_lazy_compatible(
 		zend_property_info *prop, zend_string *unmangled_name,
-		reflection_object *intern, zend_object *object, const char *method)
+		zend_class_entry *scope, zend_object *object, const char *method)
 {
 	if (!prop) {
 		zend_throw_exception_ex(reflection_exception_ptr, 0,
 				"Can not use %s on dynamic property %s::$%s",
-				method, ZSTR_VAL(intern->ce->name),
+				method, ZSTR_VAL(scope->name),
 				ZSTR_VAL(unmangled_name));
 		return FAILURE;
 	}
@@ -6166,32 +6176,23 @@ static zend_result reflection_property_check_lazy_compatible(
 	return SUCCESS;
 }

-/* {{{ Set property value without triggering initializer while skipping hooks if any */
-ZEND_METHOD(ReflectionProperty, setRawValueWithoutLazyInitialization)
+PHPAPI void zend_reflection_property_set_raw_value_without_lazy_initialization(
+		zend_property_info *prop, zend_string *unmangled_name,
+		void *cache_slot[3], zend_class_entry *scope,
+		zend_object *object, zval *value)
 {
-	reflection_object *intern;
-	property_reference *ref;
-	zend_object *object;
-	zval *value;
-
-	GET_REFLECTION_OBJECT_PTR(ref);
-
-	ZEND_PARSE_PARAMETERS_START(2, 2) {
-		Z_PARAM_OBJ_OF_CLASS(object, intern->ce)
-		Z_PARAM_ZVAL(value)
-	} ZEND_PARSE_PARAMETERS_END();
-
 	while (zend_object_is_lazy_proxy(object)
 			&& zend_lazy_object_initialized(object)) {
 		object = zend_lazy_object_get_instance(object);
 	}

-	zend_property_info *prop = reflection_property_get_effective_prop(ref,
-			intern->ce, object);
+	prop = reflection_property_get_effective_prop(prop,
+			unmangled_name, scope, object);

-	if (reflection_property_check_lazy_compatible(prop, ref->unmangled_name,
-				intern, object, "setRawValueWithoutLazyInitialization") == FAILURE) {
-		RETURN_THROWS();
+	if (reflection_property_check_lazy_compatible(prop, unmangled_name,
+				scope, object, "setRawValueWithoutLazyInitialization") == FAILURE) {
+		ZEND_ASSERT(EG(exception));
+		return;
 	}

 	zval *var_ptr = OBJ_PROP(object, prop->offset);
@@ -6200,8 +6201,8 @@ ZEND_METHOD(ReflectionProperty, setRawValueWithoutLazyInitialization)
 	/* Do not trigger initialization */
 	Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_LAZY;

-	reflection_property_set_raw_value(prop, ref->unmangled_name,
-			ref->cache_slot, intern, object, value);
+	zend_reflection_property_set_raw_value_ex(prop, unmangled_name,
+			cache_slot, scope, object, value);

 	/* Mark property as lazy again if an exception prevented update */
 	if (EG(exception) && prop_was_lazy && Z_TYPE_P(var_ptr) == IS_UNDEF
@@ -6220,6 +6221,26 @@ ZEND_METHOD(ReflectionProperty, setRawValueWithoutLazyInitialization)
 	}
 }

+/* {{{ Set property value without triggering initializer while skipping hooks if any */
+ZEND_METHOD(ReflectionProperty, setRawValueWithoutLazyInitialization)
+{
+	reflection_object *intern;
+	property_reference *ref;
+	zend_object *object;
+	zval *value;
+
+	GET_REFLECTION_OBJECT_PTR(ref);
+
+	ZEND_PARSE_PARAMETERS_START(2, 2) {
+		Z_PARAM_OBJ_OF_CLASS(object, intern->ce)
+		Z_PARAM_ZVAL(value)
+	} ZEND_PARSE_PARAMETERS_END();
+
+	zend_reflection_property_set_raw_value_without_lazy_initialization(
+			ref->prop, ref->unmangled_name, ref->cache_slot, intern->ce,
+			object, value);
+}
+
 /* {{{ Mark property as non-lazy, and initialize to default value */
 ZEND_METHOD(ReflectionProperty, skipLazyInitialization)
 {
@@ -6234,7 +6255,7 @@ ZEND_METHOD(ReflectionProperty, skipLazyInitialization)
 	} ZEND_PARSE_PARAMETERS_END();

 	if (reflection_property_check_lazy_compatible(ref->prop,
-				ref->unmangled_name, intern, object,
+				ref->unmangled_name, intern->ce, object,
 				"skipLazyInitialization") == FAILURE) {
 		RETURN_THROWS();
 	}
@@ -6715,7 +6736,8 @@ ZEND_METHOD(ReflectionProperty, isReadable)
 			zend_throw_exception(reflection_exception_ptr, "Given object is not an instance of the class this property was declared in", 0);
 			RETURN_THROWS();
 		}
-		prop = reflection_property_get_effective_prop(ref, intern->ce, obj);
+		prop = reflection_property_get_effective_prop(ref->prop,
+				ref->unmangled_name, intern->ce, obj);
 	}

 	zend_class_entry *ce = obj ? obj->ce : intern->ce;
@@ -6826,7 +6848,8 @@ ZEND_METHOD(ReflectionProperty, isWritable)
 			zend_throw_exception(reflection_exception_ptr, "Given object is not an instance of the class this property was declared in", 0);
 			RETURN_THROWS();
 		}
-		prop = reflection_property_get_effective_prop(ref, intern->ce, obj);
+		prop = reflection_property_get_effective_prop(ref->prop,
+				ref->unmangled_name, intern->ce, obj);
 	}

 	zend_class_entry *ce = obj ? obj->ce : intern->ce;
diff --git a/ext/reflection/php_reflection.h b/ext/reflection/php_reflection.h
index ba03f1d9ac2..51f48b8039c 100644
--- a/ext/reflection/php_reflection.h
+++ b/ext/reflection/php_reflection.h
@@ -50,6 +50,27 @@ extern PHPAPI zend_class_entry *reflection_lazy_object_ptr;

 PHPAPI void zend_reflection_class_factory(zend_class_entry *ce, zval *object);

+/* Sets the value of a property, bypassing a set hook if defined.
+ * 'prop': The property to set
+ * 'unmangled_name': The name of the property
+ * 'cache_slot': An opaque pointer used as an internal cache. The same
+ * cache_slot can be used again with the same 'unmangled_name' and 'scope'.
+ * Must be zeroed on first use. May be NULL.
+ * 'scope': The scope from which to set the property
+ * 'object': The object to set the value on
+ * 'value': The value to set
+ */
+PHPAPI void zend_reflection_property_set_raw_value(
+		zend_property_info *prop, zend_string *unmangled_name,
+		void *cache_slot[3], zend_class_entry *scope,
+		zend_object *object, zval *value);
+
+/* Same as zend_reflection_property_set_raw_value(), but skips lazy object initialization. */
+PHPAPI void zend_reflection_property_set_raw_value_without_lazy_initialization(
+		zend_property_info *prop, zend_string *unmangled_name,
+		void *cache_slot[3], zend_class_entry *scope,
+		zend_object *object, zval *value);
+
 END_EXTERN_C()

 #endif /* PHP_REFLECTION_H */