@@ -69,6 +69,22 @@ extern PHPAPI zend_class_entry *reflector_ptr;
6969extern PHPAPI zend_class_entry * reflection_type_ptr ;
7070extern PHPAPI zend_class_entry * reflection_property_ptr ;
7171
72+ /* PHPAPI helpers exposed by ext/reflection in PHP 8.6+. They encapsulate the
73+ * setRawValue / setRawValueWithoutLazyInitialization logic — including the
74+ * trampoline-based hook bypass and lazy-prop/realize handling — that we
75+ * previously had to either re-implement or delegate to via a userland
76+ * ReflectionProperty round-trip. */
77+ #if PHP_VERSION_ID >= 80600
78+ extern PHPAPI void zend_reflection_property_set_raw_value (
79+ const zend_property_info * prop , zend_string * unmangled_name ,
80+ void * cache_slot [3 ], const zend_class_entry * scope ,
81+ zend_object * object , zval * value );
82+ extern PHPAPI void zend_reflection_property_set_raw_value_without_lazy_initialization (
83+ const zend_property_info * prop , zend_string * unmangled_name ,
84+ void * cache_slot [3 ], const zend_class_entry * scope ,
85+ zend_object * object , zval * value );
86+ #endif
87+
7288/* ── Compatibility shims for older PHP versions ────────────── */
7389
7490/* zend_zval_value_name() landed in PHP 8.3 (returns "true"/"false"/"null"
@@ -656,7 +672,7 @@ static zend_always_inline bool dc_is_std_scope_property(zend_property_info *pi)
656672 && !(pi -> flags & (ZEND_ACC_PROTECTED_SET | ZEND_ACC_PRIVATE_SET ));
657673}
658674
659- #if PHP_VERSION_ID >= 80400
675+ #if PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80600
660676/* fn_proxy slot cached across calls — first invocation fills it via method
661677 * lookup; subsequent invocations reuse the resolved zend_function*. */
662678static zend_function * dc_set_raw_no_lazy_fn = NULL ;
@@ -665,9 +681,11 @@ static zend_function *dc_set_raw_no_lazy_fn = NULL;
665681 * ReflectionProperty instances). Defined later; forward-declared here. */
666682static void dc_lazy_refl_cache_dtor (zval * zv );
667683
668- /* Delegates to ReflectionProperty::setRawValueWithoutLazyInitialization because the
669- * required engine helpers (zend_lazy_object_decr_lazy_props, _realize) aren't ZEND_API.
670- * Per-request cache keyed on pi — the ReflectionProperty is per-class, not per-instance. */
684+ /* Pre-PHP-8.6 fallback: PHP 8.6 exposes zend_reflection_property_set_raw_value_
685+ * without_lazy_initialization() as PHPAPI, so the lazy-prop dance lives in one
686+ * place in ext/reflection. On 8.4/8.5 we delegate through a userland
687+ * ReflectionProperty round-trip — construct (cached per pi) + invoke
688+ * setRawValueWithoutLazyInitialization($obj, $value). */
671689static bool dc_set_raw_value_without_lazy_init (zend_object * obj ,
672690 zend_property_info * pi , zend_string * name , zval * value )
673691{
@@ -795,17 +813,23 @@ static bool dc_write_backed_property(zend_object *obj, zend_property_info *pi,
795813#if PHP_VERSION_ID >= 80400
796814 /* Skip the Reflection round-trip when there's no lazy-init to skip. */
797815 if (no_lazy_init && !zend_lazy_object_initialized (obj )) {
816+ # if PHP_VERSION_ID >= 80600
817+ zend_reflection_property_set_raw_value_without_lazy_initialization (
818+ pi , name , NULL , pi -> ce , obj , value );
819+ bool ok = !EG (exception );
820+ # else
798821 bool ok = dc_set_raw_value_without_lazy_init (obj , pi , name , value );
799- #if PHP_VERSION_ID >= 80100
822+ # endif
823+ # if PHP_VERSION_ID >= 80100
800824 if (enum_holder_used ) {
801825 zval_ptr_dtor (& enum_holder );
802826 }
803- #endif
827+ # endif
804828 return ok ;
805829 }
806830#endif
807831
808- if (!ZEND_TYPE_IS_SET (pi -> type ) && !DC_PROP_HAS_HOOKS (pi )) {
832+ if (!ZEND_TYPE_IS_SET (pi -> type ) && !DC_PROP_HAS_HOOKS (pi ) && ! call_hooks ) {
809833 /* Move the old value out before running its destructor: a __destruct
810834 * on the old value can legitimately read (or reassign) this same slot.
811835 * Install the new value first so reentrant reads see a valid slot. */
@@ -814,7 +838,14 @@ static bool dc_write_backed_property(zend_object *obj, zend_property_info *pi,
814838 ZVAL_COPY (slot , value );
815839 zval_ptr_dtor (& old );
816840 }
817- #if PHP_VERSION_ID >= 80400
841+ #if PHP_VERSION_ID >= 80600
842+ else if (!call_hooks ) {
843+ /* Default mode: setRawValue semantics (bypass set hook on hooked
844+ * non-virtual, type-check on typed). One PHPAPI call replaces our
845+ * old trampoline + zend_update_property_ex split. */
846+ zend_reflection_property_set_raw_value (pi , name , NULL , pi -> ce , obj , value );
847+ }
848+ #elif PHP_VERSION_ID >= 80400
818849 else if (!call_hooks && DC_PROP_HAS_HOOKS (pi ) && pi -> hooks [ZEND_PROPERTY_HOOK_SET ]) {
819850 zend_function * trampoline = zend_get_property_hook_trampoline (
820851 pi , ZEND_PROPERTY_HOOK_SET , name );
0 commit comments