@@ -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"
@@ -657,7 +673,7 @@ static zend_always_inline bool dc_is_std_scope_property(zend_property_info *pi)
657673 && !(pi -> flags & (ZEND_ACC_PROTECTED_SET | ZEND_ACC_PRIVATE_SET ));
658674}
659675
660- #if PHP_VERSION_ID >= 80400
676+ #if PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80600
661677/* fn_proxy slot cached across calls — first invocation fills it via method
662678 * lookup; subsequent invocations reuse the resolved zend_function*. */
663679static zend_function * dc_set_raw_no_lazy_fn = NULL ;
@@ -666,9 +682,11 @@ static zend_function *dc_set_raw_no_lazy_fn = NULL;
666682 * ReflectionProperty instances). Defined later; forward-declared here. */
667683static void dc_lazy_refl_cache_dtor (zval * zv );
668684
669- /* Delegates to ReflectionProperty::setRawValueWithoutLazyInitialization because the
670- * required engine helpers (zend_lazy_object_decr_lazy_props, _realize) aren't ZEND_API.
671- * Per-request cache keyed on pi — the ReflectionProperty is per-class, not per-instance. */
685+ /* Pre-PHP-8.6 fallback: PHP 8.6 exposes zend_reflection_property_set_raw_value_
686+ * without_lazy_initialization() as PHPAPI, so the lazy-prop dance lives in one
687+ * place in ext/reflection. On 8.4/8.5 we delegate through a userland
688+ * ReflectionProperty round-trip — construct (cached per pi) + invoke
689+ * setRawValueWithoutLazyInitialization($obj, $value). */
672690static bool dc_set_raw_value_without_lazy_init (zend_object * obj ,
673691 zend_property_info * pi , zend_string * name , zval * value )
674692{
@@ -796,17 +814,23 @@ static bool dc_write_backed_property(zend_object *obj, zend_property_info *pi,
796814#if PHP_VERSION_ID >= 80400
797815 /* Skip the Reflection round-trip when there's no lazy-init to skip. */
798816 if (no_lazy_init && !zend_lazy_object_initialized (obj )) {
817+ # if PHP_VERSION_ID >= 80600
818+ zend_reflection_property_set_raw_value_without_lazy_initialization (
819+ pi , name , NULL , pi -> ce , obj , value );
820+ bool ok = !EG (exception );
821+ # else
799822 bool ok = dc_set_raw_value_without_lazy_init (obj , pi , name , value );
800- #if PHP_VERSION_ID >= 80100
823+ # endif
824+ # if PHP_VERSION_ID >= 80100
801825 if (enum_holder_used ) {
802826 zval_ptr_dtor (& enum_holder );
803827 }
804- #endif
828+ # endif
805829 return ok ;
806830 }
807831#endif
808832
809- if (!ZEND_TYPE_IS_SET (pi -> type ) && !DC_PROP_HAS_HOOKS (pi )) {
833+ if (!ZEND_TYPE_IS_SET (pi -> type ) && !DC_PROP_HAS_HOOKS (pi ) && ! call_hooks ) {
810834 /* Move the old value out before running its destructor: a __destruct
811835 * on the old value can legitimately read (or reassign) this same slot.
812836 * Install the new value first so reentrant reads see a valid slot. */
@@ -815,7 +839,14 @@ static bool dc_write_backed_property(zend_object *obj, zend_property_info *pi,
815839 ZVAL_COPY (slot , value );
816840 zval_ptr_dtor (& old );
817841 }
818- #if PHP_VERSION_ID >= 80400
842+ #if PHP_VERSION_ID >= 80600
843+ else if (!call_hooks ) {
844+ /* Default mode: setRawValue semantics (bypass set hook on hooked
845+ * non-virtual, type-check on typed). One PHPAPI call replaces our
846+ * old trampoline + zend_update_property_ex split. */
847+ zend_reflection_property_set_raw_value (pi , name , NULL , pi -> ce , obj , value );
848+ }
849+ #elif PHP_VERSION_ID >= 80400
819850 else if (!call_hooks && DC_PROP_HAS_HOOKS (pi ) && pi -> hooks [ZEND_PROPERTY_HOOK_SET ]) {
820851 zend_function * trampoline = zend_get_property_hook_trampoline (
821852 pi , ZEND_PROPERTY_HOOK_SET , name );
0 commit comments