1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

GH-17927: Indicate virtual properties and hooks in reflection output (#19297)

This commit is contained in:
Daniel Scherzer
2025-07-31 17:32:09 -07:00
committed by GitHub
parent 105c1e9896
commit 63f9e4945d
7 changed files with 291 additions and 6 deletions

3
NEWS
View File

@@ -2,6 +2,9 @@ PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
?? ??? ????, PHP 8.5.0beta1
- Reflection:
. Fixed bug GH-17927 (Reflection: have some indication of property hooks in
`_property_string()`). (DanielEScherzer)
31 Jul 2025, PHP 8.5.0alpha4

View File

@@ -394,6 +394,11 @@ PHP 8.5 UPGRADE NOTES
. The output of ReflectionClass::toString() for enums has changed to
better indicate that the class is an enum, and that the enum cases
are enum cases rather than normal class constants.
. The output of ReflectionProperty::__toString() for properties with
hooks has changed to indicate what hooks the property has, whether those
hooks are final, and whether the property is virtual. This also affects
the output of ReflectionClass::__toString() when a class contains hooked
properties.
- Session:
. session_start is stricter in regard to the option argument.

View File

@@ -227,7 +227,7 @@ getValue(): NULL
setRawValueWithoutLazyInitialization():
getValue(): string(5) "value"
## Property [ public $hooked = NULL ]
## Property [ public $hooked = NULL { get; set; } ]
skipInitializerForProperty():
getValue(): NULL
@@ -235,7 +235,7 @@ getValue(): NULL
setRawValueWithoutLazyInitialization():
getValue(): string(5) "value"
## Property [ public $virtual ]
## Property [ public virtual $virtual { get; set; } ]
skipInitializerForProperty():
ReflectionException: Can not use skipLazyInitialization on virtual property A::$virtual
@@ -324,7 +324,7 @@ getValue(): NULL
setRawValueWithoutLazyInitialization():
getValue(): string(5) "value"
## Property [ public $hooked = NULL ]
## Property [ public $hooked = NULL { get; set; } ]
skipInitializerForProperty():
getValue(): NULL
@@ -332,7 +332,7 @@ getValue(): NULL
setRawValueWithoutLazyInitialization():
getValue(): string(5) "value"
## Property [ public $virtual ]
## Property [ public virtual $virtual { get; set; } ]
skipInitializerForProperty():
ReflectionException: Can not use skipLazyInitialization on virtual property A::$virtual

View File

@@ -1037,6 +1037,9 @@ static void _property_string(smart_str *str, zend_property_info *prop, const cha
if (prop->flags & ZEND_ACC_READONLY) {
smart_str_appends(str, "readonly ");
}
if (prop->flags & ZEND_ACC_VIRTUAL) {
smart_str_appends(str, "virtual ");
}
if (ZEND_TYPE_IS_SET(prop->type)) {
zend_string *type_str = zend_type_to_string(prop->type);
smart_str_append(str, type_str);
@@ -1054,6 +1057,26 @@ static void _property_string(smart_str *str, zend_property_info *prop, const cha
smart_str_appends(str, " = ");
format_default_value(str, default_value);
}
if (prop->hooks != NULL) {
smart_str_appends(str, " {");
const zend_function *get_hooked = prop->hooks[ZEND_PROPERTY_HOOK_GET];
if (get_hooked != NULL) {
if (get_hooked->common.fn_flags & ZEND_ACC_FINAL) {
smart_str_appends(str, " final get;");
} else {
smart_str_appends(str, " get;");
}
}
const zend_function *set_hooked = prop->hooks[ZEND_PROPERTY_HOOK_SET];
if (set_hooked != NULL) {
if (set_hooked->common.fn_flags & ZEND_ACC_FINAL) {
smart_str_appends(str, " final set;");
} else {
smart_str_appends(str, " set;");
}
}
smart_str_appends(str, " }");
}
}
smart_str_appends(str, " ]\n");

View File

@@ -0,0 +1,165 @@
--TEST--
Using ReflectionClass::__toString() with hooked properties (GH-17927)
--FILE--
<?php
interface IHookedDemo {
public mixed $getOnly { get; }
public mixed $setOnly { set; }
public mixed $both { get; set; }
}
abstract class HookedDemo {
abstract public mixed $getOnly { get; }
abstract public mixed $setOnly { set; }
abstract public mixed $both { get; set; }
}
class WithHooks {
public mixed $getOnly {
get => "always this string";
}
public mixed $setOnly {
set => strtolower($value);
}
public mixed $both {
get => $this->prop3;
set => strtolower($value);
}
}
class WithFinalHooks {
public mixed $getOnly {
final get => "always this string";
}
public mixed $setOnly {
final set => strtolower($value);
}
public mixed $both {
final get => $this->prop3;
final set => strtolower($value);
}
}
class WithMixedHooks {
public mixed $getIsFinal {
final get => "always this string";
set => strtolower($value);
}
public mixed $setIsFinal {
get => $this->setIsFinal;
final set => strtolower($value);
}
}
$classes = [
IHookedDemo::class,
HookedDemo::class,
WithHooks::class,
WithFinalHooks::class,
WithMixedHooks::class,
];
foreach ( $classes as $clazz ) {
echo new ReflectionClass( $clazz );
}
?>
--EXPECTF--
Interface [ <user> <iterateable> interface IHookedDemo ] {
@@ %s %d-%d
- Constants [0] {
}
- Static properties [0] {
}
- Static methods [0] {
}
- Properties [3] {
Property [ abstract public virtual mixed $getOnly { get; } ]
Property [ abstract public virtual mixed $setOnly { set; } ]
Property [ abstract public virtual mixed $both { get; set; } ]
}
- Methods [0] {
}
}
Class [ <user> <iterateable> abstract class HookedDemo ] {
@@ %s %d-%d
- Constants [0] {
}
- Static properties [0] {
}
- Static methods [0] {
}
- Properties [3] {
Property [ abstract public virtual mixed $getOnly { get; } ]
Property [ abstract public virtual mixed $setOnly { set; } ]
Property [ abstract public virtual mixed $both { get; set; } ]
}
- Methods [0] {
}
}
Class [ <user> <iterateable> class WithHooks ] {
@@ %s %d-%d
- Constants [0] {
}
- Static properties [0] {
}
- Static methods [0] {
}
- Properties [3] {
Property [ public virtual mixed $getOnly { get; } ]
Property [ public mixed $setOnly { set; } ]
Property [ public mixed $both { get; set; } ]
}
- Methods [0] {
}
}
Class [ <user> <iterateable> class WithFinalHooks ] {
@@ %s %d-%d
- Constants [0] {
}
- Static properties [0] {
}
- Static methods [0] {
}
- Properties [3] {
Property [ public virtual mixed $getOnly { final get; } ]
Property [ public mixed $setOnly { final set; } ]
Property [ public mixed $both { final get; final set; } ]
}
- Methods [0] {
}
}
Class [ <user> <iterateable> class WithMixedHooks ] {
@@ %s %d-%d
- Constants [0] {
}
- Static properties [0] {
}
- Static methods [0] {
}
- Properties [2] {
Property [ public mixed $getIsFinal { final get; set; } ]
Property [ public mixed $setIsFinal { get; final set; } ]
}
- Methods [0] {
}
}

View File

@@ -0,0 +1,89 @@
--TEST--
Using ReflectionProperty::__toString() with hooked properties (GH-17927)
--FILE--
<?php
interface IHookedDemo {
public mixed $getOnly { get; }
public mixed $setOnly { set; }
public mixed $both { get; set; }
}
abstract class HookedDemo {
abstract public mixed $getOnly { get; }
abstract public mixed $setOnly { set; }
abstract public mixed $both { get; set; }
}
class WithHooks {
public mixed $getOnly {
get => "always this string";
}
public mixed $setOnly {
set => strtolower($value);
}
public mixed $both {
get => $this->prop3;
set => strtolower($value);
}
}
class WithFinalHooks {
public mixed $getOnly {
final get => "always this string";
}
public mixed $setOnly {
final set => strtolower($value);
}
public mixed $both {
final get => $this->prop3;
final set => strtolower($value);
}
}
class WithMixedHooks {
public mixed $getIsFinal {
final get => "always this string";
set => strtolower($value);
}
public mixed $setIsFinal {
get => $this->setIsFinal;
final set => strtolower($value);
}
}
$classes = [
IHookedDemo::class,
HookedDemo::class,
WithHooks::class,
WithFinalHooks::class,
WithMixedHooks::class,
];
foreach ( $classes as $clazz ) {
echo "$clazz:\n";
$ref = new ReflectionClass( $clazz );
foreach ( $ref->getProperties() as $prop ) {
echo $prop;
}
echo "\n";
}
?>
--EXPECT--
IHookedDemo:
Property [ abstract public virtual mixed $getOnly { get; } ]
Property [ abstract public virtual mixed $setOnly { set; } ]
Property [ abstract public virtual mixed $both { get; set; } ]
HookedDemo:
Property [ abstract public virtual mixed $getOnly { get; } ]
Property [ abstract public virtual mixed $setOnly { set; } ]
Property [ abstract public virtual mixed $both { get; set; } ]
WithHooks:
Property [ public virtual mixed $getOnly { get; } ]
Property [ public mixed $setOnly { set; } ]
Property [ public mixed $both { get; set; } ]
WithFinalHooks:
Property [ public virtual mixed $getOnly { final get; } ]
Property [ public mixed $setOnly { final set; } ]
Property [ public mixed $both { final get; final set; } ]
WithMixedHooks:
Property [ public mixed $getIsFinal { final get; set; } ]
Property [ public mixed $setIsFinal { get; final set; } ]

View File

@@ -31,12 +31,12 @@ Class [ <user> <iterateable> abstract class Demo ] {
}
- Properties [2] {
Property [ abstract public $a ]
Property [ abstract public virtual $a { get; } ]
Property [ public $b = NULL ]
}
- Methods [0] {
}
}
Property [ abstract public $a ]
Property [ abstract public virtual $a { get; } ]
Property [ public $b = NULL ]