@@ -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"
@@ -652,7 +668,7 @@ static zend_always_inline bool dc_is_std_scope_property(zend_property_info *pi)
652668 && !(pi -> flags & (ZEND_ACC_PROTECTED_SET | ZEND_ACC_PRIVATE_SET ));
653669}
654670
655- #if PHP_VERSION_ID >= 80400
671+ #if PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80600
656672/* fn_proxy slot cached across calls — first invocation fills it via method
657673 * lookup; subsequent invocations reuse the resolved zend_function*. */
658674static zend_function * dc_set_raw_no_lazy_fn = NULL ;
@@ -661,9 +677,11 @@ static zend_function *dc_set_raw_no_lazy_fn = NULL;
661677 * ReflectionProperty instances). Defined later; forward-declared here. */
662678static void dc_lazy_refl_cache_dtor (zval * zv );
663679
664- /* Delegates to ReflectionProperty::setRawValueWithoutLazyInitialization because the
665- * required engine helpers (zend_lazy_object_decr_lazy_props, _realize) aren't ZEND_API.
666- * Per-request cache keyed on pi — the ReflectionProperty is per-class, not per-instance. */
680+ /* Pre-PHP-8.6 fallback: PHP 8.6 exposes zend_reflection_property_set_raw_value_
681+ * without_lazy_initialization() as PHPAPI, so the lazy-prop dance lives in one
682+ * place in ext/reflection. On 8.4/8.5 we delegate through a userland
683+ * ReflectionProperty round-trip — construct (cached per pi) + invoke
684+ * setRawValueWithoutLazyInitialization($obj, $value). */
667685static bool dc_set_raw_value_without_lazy_init (zend_object * obj ,
668686 zend_property_info * pi , zend_string * name , zval * value )
669687{
@@ -810,17 +828,23 @@ static bool dc_write_backed_property(zend_object *obj, zend_property_info *pi,
810828#if PHP_VERSION_ID >= 80400
811829 /* Skip the Reflection round-trip when there's no lazy-init to skip. */
812830 if (no_lazy_init && !zend_lazy_object_initialized (obj )) {
831+ # if PHP_VERSION_ID >= 80600
832+ zend_reflection_property_set_raw_value_without_lazy_initialization (
833+ pi , name , NULL , pi -> ce , obj , value );
834+ bool ok = !EG (exception );
835+ # else
813836 bool ok = dc_set_raw_value_without_lazy_init (obj , pi , name , value );
814- #if PHP_VERSION_ID >= 80100
837+ # endif
838+ # if PHP_VERSION_ID >= 80100
815839 if (enum_holder_used ) {
816840 zval_ptr_dtor (& enum_holder );
817841 }
818- #endif
842+ # endif
819843 return ok ;
820844 }
821845#endif
822846
823- if (!ZEND_TYPE_IS_SET (pi -> type ) && !DC_PROP_HAS_HOOKS (pi )) {
847+ if (!ZEND_TYPE_IS_SET (pi -> type ) && !DC_PROP_HAS_HOOKS (pi ) && ! call_hooks ) {
824848 /* Move the old value out before running its destructor: a __destruct
825849 * on the old value can legitimately read (or reassign) this same slot.
826850 * Install the new value first so reentrant reads see a valid slot. */
@@ -829,7 +853,14 @@ static bool dc_write_backed_property(zend_object *obj, zend_property_info *pi,
829853 ZVAL_COPY (slot , value );
830854 zval_ptr_dtor (& old );
831855 }
832- #if PHP_VERSION_ID >= 80400
856+ #if PHP_VERSION_ID >= 80600
857+ else if (!call_hooks ) {
858+ /* Default mode: setRawValue semantics (bypass set hook on hooked
859+ * non-virtual, type-check on typed). One PHPAPI call replaces our
860+ * old trampoline + zend_update_property_ex split. */
861+ zend_reflection_property_set_raw_value (pi , name , NULL , pi -> ce , obj , value );
862+ }
863+ #elif PHP_VERSION_ID >= 80400
833864 else if (!call_hooks && DC_PROP_HAS_HOOKS (pi ) && pi -> hooks [ZEND_PROPERTY_HOOK_SET ]) {
834865 zend_function * trampoline = zend_get_property_hook_trampoline (
835866 pi , ZEND_PROPERTY_HOOK_SET , name );
0 commit comments