24、PHP 8.4 新特性 - 属性钩子

作者: 温新

图书: 【PHP 8.4 新特性】

阅读: 161

时间: 2025-01-17 22:52:01

文档

  • 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 钩子)会将 firstNamelastName 组合起来,并返回 "王美丽"。

获取钩子(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 钩子)会将名字拆分为 firstNamelastName。当你访问 $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

使用属性钩子注意事项

  1. 仅适用于对象属性
    • 属性钩子只能应用于对象属性,不能应用于静态属性。这意味着如果一个属性是静态的,它不能有钩子。
  2. 覆盖默认行为
    • 属性钩子会覆盖对属性读取或写入的任何默认行为。这意味着钩子中的自定义逻辑将控制属性如何被访问或修改。
  3. 访问方法和属性
    • 属性钩子可以访问对象的所有方法和属性,无论它们是公有的、私有的还是受保护的。这包括可能有自己的钩子的属性。
  4. 不允许引用
    • 不能在带有钩子的属性上设置引用。这是因为通过引用修改值会绕过为该属性定义的任何设置钩子。
  5. 继承和钩子重定义
    • 在子类中,可以通过重新定义属性及其所需的钩子来重新定义单个属性的钩子。属性的类型和可见性遵循它们自己的规则,每个钩子可以独立地覆盖父类的实现。
请登录后再评论