25、PHP 8.4 新特性 - Asymmetric Visibility(不对称可见性)

作者: 温新

图书: 【PHP 8.4 新特性】

阅读: 128

时间: 2025-01-18 06:13:50

RFC: https://wiki.php.net/rfc/asymmetric-visibility

文档:https://www.php.net/manual/en/language.oop5.visibility.php#language.oop5.visibility-members-aviz

PHP 8.4 引入了一个非常有趣的特性——Asymmetric Visibility(不对称可见性),它允许为类的属性和方法指定不同的可见性修饰符,以使其在不同的上下文中具有不同的访问权限。这一特性对于一些特殊的场景(例如需要对外公开某些方法,但不希望让其他方法直接访问类中的某些属性)非常有用。

语法

{read visibility} {set visibility}(set) {propertyType} $name;
  • 如果未提供读取可见性,则假定为 public
  • Set (write) 可见性始终后跟字符串 “(set)”。
  • 写入可见性必须等于或低于读取可见性。
  • 当与属性钩子结合使用时,可见性适用于相应的 get 和 set 钩子。
  • 非对称可见性要求属性具有类型声明。
class Foo
{
    public private(set) string $bar;
}

此代码声明了一个属性 $bar,该属性可以从 public 范围读取,但只能从 private 范围进行修改。也可以将其声明为 protected(set) 以允许从任何受保护范围 (即子类) 设置属性。

背景和传统的可见性修饰符

在 PHP 中,类的属性和方法的访问权限通常由以下几个可见性修饰符来控制:

  • public:可以在任何地方访问。
  • protected:只能在当前类和继承的子类中访问。
  • private:只能在当前类内部访问。

这些修饰符适用于类的方法和属性,但是 Asymmetric Visibility 特性允许为方法和属性分配不同的可见性修饰符来控制访问。

基本使用

<?php

declare(strict_types=1);

class Foo
{
    public protected(set) int $bar = 0;

    public function test()
    {
        $bar = &$this->bar;
        $bar++;
    }
}

$foo = new Foo;
$foo->test();
echo $foo->bar;
// PHP Fatal error:  Uncaught Error: Cannot modify protected(set) property Foo::$bar from global scope in
// $foo->bar = 3;

非对称属性可见性

<?php

declare(strict_types=1);

class Book
{
    public function __construct(
        public private(set) string $title,
        public protected(set) string $author,
        protected private(set) int $pubYear,
    ) {}
}

class SpecialBook extends Book
{
    public function update(string $author, int $year): void
    {
        $this->author = $author; // OK
        $this->pubYear = $year; // Fatal Error
    }
}

$b = new Book('西游记', '吴承恩', 2024);

echo $b->title; // 西游记
echo $b->author; // 吴承恩
// echo $b->pubYear; // Fatal Error

$b->title = '红楼梦'; // Fatal Error
$b->author = '曹雪芹'; // Fatal Error
$b->pubYear = 2023; // Fatal Error

关于不对称可见性,有一些注意事项:

  • 只有类型化属性才能具有单独的 set 可见性。
  • 设置的可见性必须与 get 相同或更具限制性。那是 public protected(set)protected protected(set) 是允许的,但受保护的 public(set) 会导致语法错误。
  • 如果属性是 public,则可以省略 main visibility。即 public private(set)private(set) 将得到相同的结果。
  • 具有 private(set) 可见性的属性自动成为 final,并且不能在子类中重新声明。
  • 获取对属性的引用遵循 set visibility,而不是 get。 这是因为引用可用于修改属性值。
  • 同样,尝试写入数组属性时,会同时涉及 getset 操作,因此将遵循 set visibility 的 intent,因为这总是更严格的。

注意: set-visibility 声明中不允许使用空格。private(set) 是正确的。private( set ) 不正确,将导致解析错误。

非对称属性继承

class Book
{
    protected string $title;
    public protected(set) string $author;
    protected private(set) int $pubYear;
}

class SpecialBook extends Book
{
    public protected(set) $title; // OK, as reading is wider and writing the same.
    public string $author; // OK, as reading is the same and writing is wider.
    public protected(set) int $pubYear; // Fatal Error. private(set) properties are final.
}
请登录后再评论