24、PHP 8.4 新特性 - 属性钩子
文档
https://www.php.net/manual/zh/migration84.new-features.php#migration84.new-features.core.property-hooks
https://wiki.php.net/rfc/property-hooks
在 PHP 8.4 中,PHP 引入了一个新特性——属性钩子(Property Hooks),这使得开发者能够在对象的属性访问、赋值和删除时执行特定的钩子方法。通过这个特性,开发者可以在操作对象的属性时插入额外的逻辑,例如验证、转换、缓存等。
什么是属性钩子
属性钩子允许你在对象的属性被读取、修改或者删除时,自动触发一些回调操作。在 PHP 中,我们通常使用魔术方法(如 __get
、__set
)来实现类似的功能,但属性钩子提供了更为细粒度的控制,并且更符合现代面向对象编程的设计理念。
在最基本的情况下,语法如下所示:
class User
{
public string $email {
get { /* expression */ }
set ($value) { /* expression */ }
}
}
get 钩子
获取钩子(get 钩子)用于控制访问属性时发生的行为。例如,假设你有一个 Person
类,并且你希望确保每当访问 fullName
属性时,它返回的是将名字和姓氏组合后的结果。
<?php
declare(strict_types=1);
class Person
{
public string $fullName {
get {
return $this->firstName . $this->lastName;
}
}
public function __construct(
public string $firstName,
public string $lastName
) {}
}
$person = new Person('王', '美丽');
echo $person->fullName; // 王美丽
在这个例子中,当访问 $person->fullName
时,获取钩子(get 钩子)会将 firstName
和 lastName
组合起来,并返回 "王美丽"。
获取钩子(get 钩子)返回的值的类型必须与属性声明的类型匹配。如果类型不匹配,PHP 会尝试自动转换该值,除非启用了严格类型检查。如果启用了严格类型检查,你需要确保返回的值完全匹配属性的类型,否则将发生错误。
set 钩子
设置钩子(set 钩子)用于控制当给属性赋值时发生的行为。例如,假设希望确保分配给 Person
类的任何名字都以大写形式存储。
<?php
declare(strict_types=1);
class Person
{
public string $name {
set(string $value) {
$this->name = strtoupper($value);
}
}
public function __construct(string $name)
{
$this->name = $name;
}
}
$person = new Person('wang meili');
echo $person->name; // WANG MEILI
与获取钩子(get 钩子)类似,传递给设置钩子(set 钩子)的值的类型必须与属性的类型兼容。如果类型不匹配,并且启用了严格类型检查,PHP 会抛出一个错误。
使用 set 和 get 钩子
可以对同一个属性同时使用获取钩子(get 钩子)和设置钩子(set 钩子),以控制该属性的访问和修改方式。例如,我们可以创建一个 Person
类,在设置 fullName
属性时自动将其拆分为名字和姓氏,在访问 fullName
属性时则将名字和姓氏组合起来。
<?php
declare(strict_types=1);
class Person
{
public string $fullName {
get {
return $this->firstName . ' ' . $this->lastName;
}
set(string $value) {
[$this->firstName, $this->lastName] = explode(' ', $value);
}
}
private string $firstName;
private string $lastName;
public function __construct(string $fullName)
{
$this->fullName = $fullName;
}
}
$person = new Person('Wang meili');
echo $person->fullName . PHP_EOL; // Wang meili
$person->fullName = 'Zhao Lili';
echo $person->fullName . PHP_EOL; // Zhao Lili
在这个例子中,当你将 $person->fullName
设置为 "Wang meili" 时,设置钩子(set 钩子)会将名字拆分为 firstName
和 lastName
。当你访问 $person->fullName
时,获取钩子(get 钩子)会将这些部分组合起来并返回 "Wang lili"。
只写和只读属性
通过属性钩子,可以创建只写或只读的属性。
- 只写属性:只能给属性赋值但不能读取它。这发生在定义了设置钩子(set 钩子)但没有定义获取钩子(get 钩子)时。
- 只读属性:只能读取属性的值但不能给它赋值。这发生在定义了获取钩子(get 钩子)但没有定义设置钩子(set 钩子)时。
只写案例
这里有一个例子,只能设置密码,但不能读取它:
<?php
declare(strict_types=1);
class User
{
public string $password {
set(string $value) {
$this->hashedPassword = password_hash($value, PASSWORD_DEFAULT);
}
}
private string $hashedPassword;
}
$user = new User();
$user->password = 'secret';
// echo $user->password; // Error: Cannot access write-only property
只读案例
这里有一个例子,只能读取计算值,但不能直接设置它:
<?php
declare(strict_types=1);
class Circle
{
public float $radius;
public float $area {
get {
return pi() * $this->radius ** 2;
}
}
public function __construct(float $radius)
{
$this->radius = $radius;
}
}
$circle = new Circle(5);
echo $circle->area; // 78.5398...
// $circle->area = 100; // Error: Cannot set read-only property
使用属性钩子注意事项
-
仅适用于对象属性:
- 属性钩子只能应用于对象属性,不能应用于静态属性。这意味着如果一个属性是静态的,它不能有钩子。
-
覆盖默认行为:
- 属性钩子会覆盖对属性读取或写入的任何默认行为。这意味着钩子中的自定义逻辑将控制属性如何被访问或修改。
-
访问方法和属性:
- 属性钩子可以访问对象的所有方法和属性,无论它们是公有的、私有的还是受保护的。这包括可能有自己的钩子的属性。
-
不允许引用:
- 不能在带有钩子的属性上设置引用。这是因为通过引用修改值会绕过为该属性定义的任何设置钩子。
-
继承和钩子重定义:
- 在子类中,可以通过重新定义属性及其所需的钩子来重新定义单个属性的钩子。属性的类型和可见性遵循它们自己的规则,每个钩子可以独立地覆盖父类的实现。