From fca6080b7e866407b62a988673e7ffeb04cdbb51 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 19 Jul 2026 09:58:22 -0700 Subject: [PATCH 1/4] `ReflectionMethod::getPrototype()`: add parentheses to error message --- ext/reflection/php_reflection.c | 2 +- ext/reflection/tests/bug74949.phpt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index a068f9f48ac6..bba42bdfcc1a 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -3728,7 +3728,7 @@ ZEND_METHOD(ReflectionMethod, getPrototype) if (!mptr->common.prototype) { zend_throw_exception_ex(reflection_exception_ptr, 0, - "Method %s::%s does not have a prototype", ZSTR_VAL(intern->ce->name), ZSTR_VAL(mptr->common.function_name)); + "Method %s::%s() does not have a prototype", ZSTR_VAL(intern->ce->name), ZSTR_VAL(mptr->common.function_name)); RETURN_THROWS(); } diff --git a/ext/reflection/tests/bug74949.phpt b/ext/reflection/tests/bug74949.phpt index 20e0fc00e101..858e766c0144 100644 --- a/ext/reflection/tests/bug74949.phpt +++ b/ext/reflection/tests/bug74949.phpt @@ -21,4 +21,4 @@ try { Method [ public method __invoke ] { } -Method Closure::__invoke does not have a prototype +Method Closure::__invoke() does not have a prototype From 11743aaabf6185c3be5520c2461c253f2fc99159 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 19 Jul 2026 10:11:11 -0700 Subject: [PATCH 2/4] Reflection: add regression tests for lazy initialization errors Add tests for the four error cases in `reflection_property_check_lazy_compatible()`, as triggered by both `ReflectionProperty::setRawValueWithoutLazyInitialization()` and `ReflectionProperty::skipLazyInitialization()`. While some of these errors are covered by existing tests, having all of the errors in one place will make it easier to see the changes when the error messages are improved. --- ...onProperty_lazy_initialization_errors.phpt | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 ext/reflection/tests/ReflectionProperty_lazy_initialization_errors.phpt diff --git a/ext/reflection/tests/ReflectionProperty_lazy_initialization_errors.phpt b/ext/reflection/tests/ReflectionProperty_lazy_initialization_errors.phpt new file mode 100644 index 000000000000..bb7bb56c0fa9 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_lazy_initialization_errors.phpt @@ -0,0 +1,51 @@ +--TEST-- +Test ReflectionProperty::setRawValueWithoutLazyInitialization() and skipLazyInitialization() errors +--FILE-- + true; + } +} + +function test(object $obj, string $propertyName) { + $r = new ReflectionProperty($obj, $propertyName); + try { + $r->setRawValueWithoutLazyInitialization($obj, true); + } catch (ReflectionException $e) { + echo $e->getMessage() . "\n"; + } + try { + $r->skipLazyInitialization($obj); + } catch (ReflectionException $e) { + echo $e->getMessage() . "\n\n"; + } +} + +$obj = new Demo(); +test($obj, 'myStatic'); +test($obj, 'myVirtual'); + +$obj->myDynamic = true; +test($obj, 'myDynamic'); + +$obj = new ReflectionClass(Demo::class); +test($obj, 'name'); + +?> +--EXPECT-- +Can not use setRawValueWithoutLazyInitialization on static property Demo::$myStatic +Can not use skipLazyInitialization on static property Demo::$myStatic + +Can not use setRawValueWithoutLazyInitialization on virtual property Demo::$myVirtual +Can not use skipLazyInitialization on virtual property Demo::$myVirtual + +Can not use setRawValueWithoutLazyInitialization on dynamic property Demo::$myDynamic +Can not use skipLazyInitialization on dynamic property Demo::$myDynamic + +Can not use setRawValueWithoutLazyInitialization on internal class ReflectionClass +Can not use skipLazyInitialization on internal class ReflectionClass From 024c1a6dae8c17f66f6ed43360275b4bb1b09a25 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 19 Jul 2026 10:14:34 -0700 Subject: [PATCH 3/4] Reflection: improve error messages for lazy initialization errors Improve error messages from `reflection_property_check_lazy_compatible()`, as triggered by both `ReflectionProperty::setRawValueWithoutLazyInitialization()` and `ReflectionProperty::skipLazyInitialization()`. Say "cannot" instead of "can not", and include parentheses after the method name. --- ...ithoutLazyInitialization_no_dynamic_prop.phpt | 4 ++-- .../lazy_objects/skipLazyInitialization.phpt | 16 ++++++++-------- .../skipLazyInitialization_no_dynamic_prop.phpt | 4 ++-- ext/reflection/php_reflection.c | 8 ++++---- ...ctionProperty_lazy_initialization_errors.phpt | 16 ++++++++-------- ext/reflection/tests/property_hooks/gh17713.phpt | 4 ++-- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_no_dynamic_prop.phpt b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_no_dynamic_prop.phpt index 9151e58f3fc6..3c9686ba04c4 100644 --- a/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_no_dynamic_prop.phpt +++ b/Zend/tests/lazy_objects/setRawValueWithoutLazyInitialization_no_dynamic_prop.phpt @@ -38,6 +38,6 @@ test('Proxy', $obj); ?> --EXPECT-- # Ghost -ReflectionException: Can not use setRawValueWithoutLazyInitialization on dynamic property C::$dyn +ReflectionException: Cannot use setRawValueWithoutLazyInitialization() on dynamic property C::$dyn # Proxy -ReflectionException: Can not use setRawValueWithoutLazyInitialization on dynamic property C::$dyn +ReflectionException: Cannot use setRawValueWithoutLazyInitialization() on dynamic property C::$dyn diff --git a/Zend/tests/lazy_objects/skipLazyInitialization.phpt b/Zend/tests/lazy_objects/skipLazyInitialization.phpt index 4fc47b13db67..8a95e684a5a2 100644 --- a/Zend/tests/lazy_objects/skipLazyInitialization.phpt +++ b/Zend/tests/lazy_objects/skipLazyInitialization.phpt @@ -198,10 +198,10 @@ getValue(): string(5) "value" ## Property [ public static $static = 'static' ] skipInitializerForProperty(): -ReflectionException: Can not use skipLazyInitialization on static property A::$static +ReflectionException: Cannot use skipLazyInitialization() on static property A::$static setRawValueWithoutLazyInitialization(): -ReflectionException: Can not use setRawValueWithoutLazyInitialization on static property A::$static +ReflectionException: Cannot use setRawValueWithoutLazyInitialization() on static property A::$static ## Property [ public $noDefault = NULL ] @@ -238,10 +238,10 @@ getValue(): string(5) "value" ## Property [ public virtual $virtual { get; set; } ] skipInitializerForProperty(): -ReflectionException: Can not use skipLazyInitialization on virtual property A::$virtual +ReflectionException: Cannot use skipLazyInitialization() on virtual property A::$virtual setRawValueWithoutLazyInitialization(): -ReflectionException: Can not use setRawValueWithoutLazyInitialization on virtual property A::$virtual +ReflectionException: Cannot use setRawValueWithoutLazyInitialization() on virtual property A::$virtual ## Property [ $dynamicProp ] @@ -295,10 +295,10 @@ getValue(): string(5) "value" ## Property [ public static $static = 'static' ] skipInitializerForProperty(): -ReflectionException: Can not use skipLazyInitialization on static property A::$static +ReflectionException: Cannot use skipLazyInitialization() on static property A::$static setRawValueWithoutLazyInitialization(): -ReflectionException: Can not use setRawValueWithoutLazyInitialization on static property A::$static +ReflectionException: Cannot use setRawValueWithoutLazyInitialization() on static property A::$static ## Property [ public $noDefault = NULL ] @@ -335,10 +335,10 @@ getValue(): string(5) "value" ## Property [ public virtual $virtual { get; set; } ] skipInitializerForProperty(): -ReflectionException: Can not use skipLazyInitialization on virtual property A::$virtual +ReflectionException: Cannot use skipLazyInitialization() on virtual property A::$virtual setRawValueWithoutLazyInitialization(): -ReflectionException: Can not use setRawValueWithoutLazyInitialization on virtual property A::$virtual +ReflectionException: Cannot use setRawValueWithoutLazyInitialization() on virtual property A::$virtual ## Property [ $dynamicProp ] diff --git a/Zend/tests/lazy_objects/skipLazyInitialization_no_dynamic_prop.phpt b/Zend/tests/lazy_objects/skipLazyInitialization_no_dynamic_prop.phpt index 74e12cb3629f..dbbe88c52a2c 100644 --- a/Zend/tests/lazy_objects/skipLazyInitialization_no_dynamic_prop.phpt +++ b/Zend/tests/lazy_objects/skipLazyInitialization_no_dynamic_prop.phpt @@ -38,6 +38,6 @@ test('Proxy', $obj); ?> --EXPECT-- # Ghost -ReflectionException: Can not use skipLazyInitialization on dynamic property C::$dyn +ReflectionException: Cannot use skipLazyInitialization() on dynamic property C::$dyn # Proxy -ReflectionException: Can not use skipLazyInitialization on dynamic property C::$dyn +ReflectionException: Cannot use skipLazyInitialization() on dynamic property C::$dyn diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index bba42bdfcc1a..3e1e41893e72 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -6039,7 +6039,7 @@ static zend_result reflection_property_check_lazy_compatible( { if (!prop) { zend_throw_exception_ex(reflection_exception_ptr, 0, - "Can not use %s on dynamic property %s::$%s", + "Cannot use %s() on dynamic property %s::$%s", method, ZSTR_VAL(scope->name), ZSTR_VAL(unmangled_name)); return FAILURE; @@ -6047,7 +6047,7 @@ static zend_result reflection_property_check_lazy_compatible( if (prop->flags & ZEND_ACC_STATIC) { zend_throw_exception_ex(reflection_exception_ptr, 0, - "Can not use %s on static property %s::$%s", + "Cannot use %s() on static property %s::$%s", method, ZSTR_VAL(prop->ce->name), ZSTR_VAL(unmangled_name)); return FAILURE; @@ -6055,7 +6055,7 @@ static zend_result reflection_property_check_lazy_compatible( if (prop->flags & ZEND_ACC_VIRTUAL) { zend_throw_exception_ex(reflection_exception_ptr, 0, - "Can not use %s on virtual property %s::$%s", + "Cannot use %s() on virtual property %s::$%s", method, ZSTR_VAL(prop->ce->name), ZSTR_VAL(unmangled_name)); return FAILURE; @@ -6065,7 +6065,7 @@ static zend_result reflection_property_check_lazy_compatible( && !zend_class_can_be_lazy(object->ce) ) { zend_throw_exception_ex(reflection_exception_ptr, 0, - "Can not use %s on internal class %s", + "Cannot use %s() on internal class %s", method, ZSTR_VAL(object->ce->name)); return FAILURE; } diff --git a/ext/reflection/tests/ReflectionProperty_lazy_initialization_errors.phpt b/ext/reflection/tests/ReflectionProperty_lazy_initialization_errors.phpt index bb7bb56c0fa9..8bd82ff64020 100644 --- a/ext/reflection/tests/ReflectionProperty_lazy_initialization_errors.phpt +++ b/ext/reflection/tests/ReflectionProperty_lazy_initialization_errors.phpt @@ -38,14 +38,14 @@ test($obj, 'name'); ?> --EXPECT-- -Can not use setRawValueWithoutLazyInitialization on static property Demo::$myStatic -Can not use skipLazyInitialization on static property Demo::$myStatic +Cannot use setRawValueWithoutLazyInitialization() on static property Demo::$myStatic +Cannot use skipLazyInitialization() on static property Demo::$myStatic -Can not use setRawValueWithoutLazyInitialization on virtual property Demo::$myVirtual -Can not use skipLazyInitialization on virtual property Demo::$myVirtual +Cannot use setRawValueWithoutLazyInitialization() on virtual property Demo::$myVirtual +Cannot use skipLazyInitialization() on virtual property Demo::$myVirtual -Can not use setRawValueWithoutLazyInitialization on dynamic property Demo::$myDynamic -Can not use skipLazyInitialization on dynamic property Demo::$myDynamic +Cannot use setRawValueWithoutLazyInitialization() on dynamic property Demo::$myDynamic +Cannot use skipLazyInitialization() on dynamic property Demo::$myDynamic -Can not use setRawValueWithoutLazyInitialization on internal class ReflectionClass -Can not use skipLazyInitialization on internal class ReflectionClass +Cannot use setRawValueWithoutLazyInitialization() on internal class ReflectionClass +Cannot use skipLazyInitialization() on internal class ReflectionClass diff --git a/ext/reflection/tests/property_hooks/gh17713.phpt b/ext/reflection/tests/property_hooks/gh17713.phpt index c6d4d241bc50..edd0e1204658 100644 --- a/ext/reflection/tests/property_hooks/gh17713.phpt +++ b/ext/reflection/tests/property_hooks/gh17713.phpt @@ -157,7 +157,7 @@ int(43) # Accessing Base->virtualProp from scope Base Must not write to virtual property Base::$virtualProp Must not read from virtual property Base::$virtualProp -Can not use setRawValueWithoutLazyInitialization on virtual property Base::$virtualProp +Cannot use setRawValueWithoutLazyInitialization() on virtual property Base::$virtualProp Must not read from virtual property Base::$virtualProp # Accessing Test->dynamicProp from scope Base int(42) @@ -165,5 +165,5 @@ int(43) # Accessing Test->changedProp from scope Base May not use setRawValue on static properties May not use getRawValue on static properties -Can not use setRawValueWithoutLazyInitialization on static property Test::$changedProp +Cannot use setRawValueWithoutLazyInitialization() on static property Test::$changedProp May not use getRawValue on static properties From 50f10a5875d6601e79c6ccec1aa37ca1117d7f6c Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 19 Jul 2026 10:18:03 -0700 Subject: [PATCH 4/4] `ReflectionClass::setStaticPropertyValue()`: update missing property error Align with other Reflection exceptions for missing class properties (and methods, constants, etc.) by changing the message to "Property %s::$%s does not exist". --- ext/reflection/php_reflection.c | 2 +- .../tests/ReflectionClass_setStaticPropertyValue_001.phpt | 4 ++-- .../tests/ReflectionClass_setStaticPropertyValue_002.phpt | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 3e1e41893e72..ec060cbbf3e9 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -4195,7 +4195,7 @@ ZEND_METHOD(ReflectionClass, setStaticPropertyValue) if (!variable_ptr) { zend_clear_exception(); zend_throw_exception_ex(reflection_exception_ptr, 0, - "Class %s does not have a property named %s", ZSTR_VAL(ce->name), ZSTR_VAL(name)); + "Property %s::$%s does not exist", ZSTR_VAL(ce->name), ZSTR_VAL(name)); RETURN_THROWS(); } diff --git a/ext/reflection/tests/ReflectionClass_setStaticPropertyValue_001.phpt b/ext/reflection/tests/ReflectionClass_setStaticPropertyValue_001.phpt index 2c855a043674..512fb5d6d916 100644 --- a/ext/reflection/tests/ReflectionClass_setStaticPropertyValue_001.phpt +++ b/ext/reflection/tests/ReflectionClass_setStaticPropertyValue_001.phpt @@ -73,5 +73,5 @@ Array ) Set non-existent values from A with no default value: -Class A does not have a property named protectedDoesNotExist -Class A does not have a property named privateDoesNotExist +Property A::$protectedDoesNotExist does not exist +Property A::$privateDoesNotExist does not exist diff --git a/ext/reflection/tests/ReflectionClass_setStaticPropertyValue_002.phpt b/ext/reflection/tests/ReflectionClass_setStaticPropertyValue_002.phpt index 82de2ce0c2a8..2822471a3971 100644 --- a/ext/reflection/tests/ReflectionClass_setStaticPropertyValue_002.phpt +++ b/ext/reflection/tests/ReflectionClass_setStaticPropertyValue_002.phpt @@ -49,6 +49,6 @@ ReflectionClass::setStaticPropertyValue() expects exactly 2 arguments, 0 given ReflectionClass::setStaticPropertyValue() expects exactly 2 arguments, 1 given Deprecated: ReflectionClass::setStaticPropertyValue(): Passing null to parameter #1 ($name) of type string is deprecated in %s on line %d -Class C does not have a property named -Class C does not have a property named 1.5 +Property C::$ does not exist +Property C::$1.5 does not exist ReflectionClass::setStaticPropertyValue(): Argument #1 ($name) must be of type string, array given