• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

PHP8.1新特性大讲解之readonly properties只读属性

武飞扬头像
PHP中文网
帮助82

PHP 8.1:只读属性

多年来,用 PHP 编写数据传输对象和值对象变得非常容易。以 PHP 5.6 中的 DTO 为例:

class BlogData
{
    /** @var string */
    private $title;
    
    /** @var Status */
    private $status;
    
    /** @var \DateTimeImmutable|null */
    private $publishedAt;
   
   /**
    * @param string $title 
    * @param Status $status 
    * @param \DateTimeImmutable|null $publishedAt 
    */
    public function __construct(
        $title,
        $status,
        $publishedAt = null
    ) {
        $this->title = $title;
        $this->status = $status;
        $this->publishedAt = $publishedAt;
    }
    
    /**
     * @return string 
     */
    public function getTitle()
    {
        return $this->title;    
    }
    
    /**
     * @return Status 
     */
    public function getStatus() 
    {
        return $this->status;    
    }
    
    /**
     * @return \DateTimeImmutable|null 
     */
    public function getPublishedAt() 
    {
        return $this->publishedAt;    
    }
}

并将其与PHP 8.0的等价物进行比较:

class BlogData
{
    public function __construct(
        private string $title,
        private Status $status,
        private ?DateTimeImmutable $publishedAt = null,
    ) {}
    
    public function getTitle(): string
    {
        return $this->title;    
    }
    
    public function getStatus(): Status 
    {
        return $this->status;    
    }
    
    public function getPublishedAt(): ?DateTimeImmutable
    {
        return $this->publishedAt;    
    }
}

这已经很不一样了,尽管我认为仍然存在一个大问题:所有这些吸气剂。就个人而言,自 PHP 8.0 及其提升的属性以来,我不再使用它们。我只是更喜欢使用公共属性而不是添加 getter:

class BlogData
{
    public function __construct(
        public string $title,
        public Status $status,
        public ?DateTimeImmutable $publishedAt = null,
    ) {}
}

面向对象的纯粹主义者不喜欢这种方法:对象的内部状态不应该直接暴露,并且绝对不能从外部改变。

在我们在 Spatie 的项目中,我们有一个内部风格指南规则,即不应从外部更改具有公共属性的 DTO 和 VO;一种似乎效果很好的做法,我们已经做了很长一段时间了,没有遇到任何问题。

然而,是的;我同意如果语言确保公共属性根本不会被覆盖会更好。好吧,PHP 8.1通过引入readonly关键字解决了所有这些问题:

class BlogData
{
    public function __construct(
        public readonly string $title,
        public readonly Status $status,
        public readonly ?DateTimeImmutable $publishedAt = null,
    ) {}
}

这个关键字基本上就像它的名字所暗示的那样:一旦设置了一个属性,它就不能再被覆盖:

$blog = new BlogData(
    title: 'PHP 8.1: readonly properties', 
    status: Status::PUBLISHED, 
    publishedAt: now()
);
$blog->title = 'Another title';
Error: Cannot modify readonly property BlogData::$title

知道当一个对象被构造时,它不会再改变,在编写代码时提供了一定程度的确定性和平静:一系列不可预见的数据更改根本不会再发生。

当然,您仍然希望能够将数据复制到新对象,并可能在此过程中更改某些属性。我们将在本文后面讨论如何使用只读属性来做到这一点。首先,让我们深入了解一下它们。

您想要了解更多有关 PHP 8.1 的信息吗?有通往 PHP 8.1 的道路。在接下来的 10 天内,您将每天收到一封电子邮件,内容涉及 PHP 8.1 的一个新的和现有的特性;之后您将自动退订,因此不会收到垃圾邮件或后续邮件。 现在订阅!

#仅输入属性

只读属性只能与类型化属性结合使用:

class BlogData
{
    public readonly string $title;
    
    public readonly $mixed;
}

但是,您可以将其mixed用作类型提示:

class BlogData
{
    public readonly string $title;
    
    public readonly mixed $mixed;
}

这种限制的原因是通过省略属性类型,null如果在构造函数中没有提供显式值,PHP 将自动设置属性的值。这种行为与 readonly 相结合,会导致不必要的混乱。

#普通属性和提升属性

您已经看到了两者的示例:readonly可以在普通属性和提升属性上添加:

class BlogData
{
    public readonly string $title;
    
    public function __construct(
        public readonly Status $status, 
    ) {}
}

#无默认值

只读属性不能有默认值:

class BlogData
{
    public readonly string $title = 'Readonly properties';
}

也就是说,除非它们是提升的属性:

class BlogData
{
    public function __construct(
        public readonly string $title = 'Readonly properties', 
    ) {}
}

它之所以被允许提升属性,是因为提升属性的默认值不作为类属性的默认值,但只适用于构造函数的参数。在幕后,上面的代码将转换为:

class BlogData
{
    public readonly string $title;
    
    public function __construct(
        string $title = 'Readonly properties', 
    ) {
        $this->title = $title;
    }
}

您可以看到实际属性如何没有被分配默认值。顺便说一下,不允许只读属性的默认值的原因是它们与该形式的常量没有任何不同。

#遗产

继承期间不允许更改 readonly 标志:

class Foo
{
    public readonly int $prop;
}
class Bar extends Foo
{
    public int $prop;
}

这条规则是双向的:readonly在继承过程中不允许添加或删除标志。

#不允许取消设置

一旦设置了只读属性,您就不能更改它,甚至不能取消它:

$foo = new Foo('value');
unset($foo->prop);

#反射

有一个新方法,以及一个标志。ReflectionProperty::isReadOnly()ReflectionProperty::IS_READONLY

#克隆

因此,如果您无法更改只读属性,并且无法取消设置它们,那么您如何创建 DTO 或 VO 的副本并更改其某些数据?您不能使用clone它们,因为您将无法覆盖其值。实际上有一个想法是clone with在未来添加一个允许这种行为的构造,但这并不能解决我们现在的问题。

好吧,如果您依靠一点反射魔法,您可以复制具有更改的只读属性的对象。通过创建一个对象而不调用它的构造函数(这可以使用反射),然后通过手动复制每个属性——有时覆盖它的值——你实际上可以“克隆”一个对象并更改其只读属性。

我做了一个小包来做到这一点,它是这样的:

class BlogData
{
    use Cloneable;
    public function __construct(
        public readonly string $title,
    ) {}
}
$dataA = new BlogData('Title');
$dataB = $dataA->with(title: 'Another title');

我实际上写了一篇专门的博客文章,解释了所有这些背后的机制,你可以在这里阅读。

所以,这就是关于只读属性的全部内容。如果您正在处理处理大量 DTO 和 VO 的项目,并且需要您仔细管理整个代码中的数据流,我认为它们是一个很棒的功能。具有只读属性的不可变对象在这方面有很大帮助。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanfbkgb
系列文章
更多 icon
同类精品
更多 icon
继续加载