PHP 类中可以动态设置和获取没有声明过的类属性。这些属性不遵循具体的规则,并且需要使用 __get()
和 __set()
魔术方法对动态属性如何读写进行有效控制。
class User {
private int $uid;
}
$user = new User();
$user->name = 'Foo';
上述代码中,User
类并没有声明 name 属性,不过因为允许使用动态属性,PHP 中可以这样设置。
虽然动态属性在创建类中(比如,非严格类声明中的值对象)提供了更多灵活性,同时也为潜在 bug 和非预期行为提供了可能性。比如,代码中如果出现拼写错误,可能导致设置了不需要的属性,这样的失误可能会因为允许动态属性而被忽视。
PHP 8.2 以上的版本,对未经声明的类属性进行设置将被废弃,会在应用执行的生命周期中发出废弃通知。
class User {
private int $uid;
}
$user = new User();
$user->name = 'Foo';
Deprecated: Creation of dynamic property User::$name is deprecated in ... on line ...
在类中设置属性同样也会发出废弃通知:
class User {
public function __construct() {
$this->name = 'test';
}
}
new User();
Deprecated: Creation of dynamic property User::$name is deprecated in ... on line ...
当然也有动态属性的合法用例,比如来源于动态 JSON 响应的值对象或来源于允许任意值配置项的值对象。
理想的做法是,类中声明动态属性,避免废弃通知。这些属性不需要声明属性类型。
动态属性豁免模式
对于这类废弃,有三种例外。使用以下方式其中一种都可以避免动态属性废弃通知。
- 类使用
#[AllowDynamicProperties]
注解 -
stdClass
类及它的子类 - 带
__get
和__set
魔术方法的类
带有 #[AllowDynamicProperties]
注解的类
PHP 8.2 在全局命名空间中引入 #[AllowDynamicProperties]
注解。带有这一注解的类,会通知 PHP 不要在该类对象设置动态属性时发出废弃通知。
子类可以从父类中继承
#[AllowDynamicProperties]
注解。
下面的代码中用 #[AllowDynamicProperties]
注解声明了 User
类,即使动态设置属性也不会发出废弃通知。
+ #[AllowDynamicProperties]
class User {
private int $uid;
}
$user = new User();
$user->name = 'Foo';
stdClass
类及其子类
PHP 在解码 JSON 对象将数据强制转换成对象时,使用 stdClass
类作为基类。在 PHP 8.2 中,#[AllowDynamicProperties]
注解被添加到 stdClass
类。这意味着在 stdClass
类或者它的子类中设置动态属性,不会发出动态属性废弃通知。
$object = new stdClass();
$object->foo = 'bar';
class User extends stdClass {}
$object = new User();
$object->foo = 'bar';
以上的代码片段都不会发出废弃通知,因为 stdClass
类内部有 #[AllowDynamicProperties]
注解。
带 __get
和 __set
魔术方法的类
声明 __set
魔术方法的类也不包含在动态属性废弃中。实际使用中,或许也需要与之对应的 __get
方法使其实际可用。
class User {
public function __set(string $name, mixed $value): void {
}
}
$user = new User();
$user->name = 'test';
上面的代码段不会发出废弃通知,因为 User 类执行了 __set
魔术方法。
注意,在 __set
方法中设置动态属性,仍然会被废弃:
class User {
public function __set(string $name, mixed $value): void {
$this->{$name} = $value;
}
}
$user = new User();
$user->name = 'test';
Deprecated: Creation of dynamic property User::$name is deprecated in ... on line ...
将对象数据与 WeakMap
关联
PHP 8.0 中引入了 WeakMap
。需要设置动态属性作为辅助数据的,可以考虑使用 WeakMap
。这能使代码更加清晰、更好维护,而又能避免动态属性废弃通知。
比如,下面的代码存储了一个叫 processed
的动态属性。这个属性没有在 Event
类中声明,并且也不属于 Event
类。
$event = new Event();
$event->processed = true; // <-- dynamic property
$is_processed = !empty($event->processed);
使用 WeakMap
,可以将数据关联到 WeakMap
。当 Event
对象脱离作用域后,关联的数据也会被自动移除。
$event = new Event();
+ $processed_events = new WeakMap();
- $event->processed = true; // <-- dynamic property
+ $processed_events[$event] = true;
- $is_processed = !empty($event->processed);
+ $is_processed = !empty($processed_events[$event]);
向后兼容性影响
PHP 8.2 中,会发出废弃通知。到 PHP 9.0,动态属性则会导致致命错误。