【PHP】PHP8.1新特性大讲解之readonly properties只读属性
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 的项目,并且需要您仔细管理整个代码中的数据流,我认为它们是一个很棒的功能。具有只读属性的不可变对象在这方面有很大帮助。
猜你喜欢
- 【PHP】thinkphp5支付宝服务商手机网站支付(新版sdk)
- public function pay() { Vendor('alipay.wappay.service.AlipayTradeService'); Vendor('alipay.wappay.buildermodel.AlipayTradeWapPayContentBuilder'); &
- 【PHP】PHP接入微信官方支付(native·APIv3)
- 一、项目介绍两个文件实现微信官方支付(native·APIv3)的发起支付和回调应答功能二、准备资料商户号:需要使用到营业执照注册商户appid:小程序或者订阅号的appidAPIv3秘钥:32位秘钥,APIv2秘钥为16位,不要混淆证书序号:apiclient_key.pem文件中的秘钥,需要将该文件改为txt后缀,然后获取其中的秘钥三、支付代码1.index.php文件<?php //支付配置 $mchid = '';//微信支付商户号 P
- 【PHP】php有哪些爬虫模块类型
- hp爬虫模块类型有cURL、Simple HTML DOM、Goutte、PhantomJS、Selenium等等。详细介绍:1、cURL,可以模拟浏览器行为轻松地获取网页内容;2、Simple HTML DOM,可以通过CSS选择器或XPath表达式来定位和提取HTML元素,方便地从网页中提取所需的数据;3、Goutte可以发送HTTP请求、处理Cookie、处理表单等等。本教程操作系统:Windows10系统、PHP8.1.3版本、Dell G3电脑。PHP作为一种流行的编程语言,具有强大
- 【PHP】php开发的办公软件都有哪些
- php开发的办公软件有WordPress、Drupal、Joomla、ownCloud、SuiteCRM、EspoCRM、Feng Office、LimeSurvey、phpMyAdmin、InvoicePlane等等常用办公软件。详细介绍:1、WordPress,一款开源的内容管理系统,用于创建和管理博客、网站和在线商店;2、Drupal,适用于构建复杂的网站和应用程序等等。本教程操作系统:windows10系统、PHP8.1.3版本、Dell G3电脑。PHP作为一种流行的服务器端
- 【PHP】PHP8 新特性 match 表达式详解
- PHP8 alpha2发布了,最近引入了一个新的关键字:match, 这个关键字的作用跟switch有点类似。这个我觉得还是有点意思,match这个词也挺好看,那么它是干啥的呢?在以前我们可能会经常使用switch做值转换类的工作,类似:function convert($input) { switch ($input) {
- 【PHP】PHP面试题
- 1、什么事面向对象?主要特征是什么?面向对象是程序的一种设计方式,它利于提高程序的重用性,使程序结构更加清晰。主要特征:封装、继承、多态。2、SESSION 与 COOKIE的区别是什么,请从协议,产生的原因与作用说明?A、http无状态协议,不能区分用户是否是从同一个网站上来的,同一个用户请求不同的页面不能看做是同一个用户。B、SESSION存储在服务器端,COOKIE保存在客户端。Session比较安全,cookie用某些手段可以修改,不安全。Session依赖于cookie进行传递。禁用c
- 【PHP】TP使用Intervention\Image在图片上绘制矩形、文字
- 1. 在图片上绘制矩形use Intervention\Image\ImageManagerStatic as Image; public function drawRectangle() { $image = Image::make('path/to/your/image.jpg'); // 替换为你的图片路径 &
- 【PHP】php魔术常量、超全局变量和魔术方法汇总
- 一、魔术常量(8个)PHP中的常量大部分都是不变的,但是有8个常量会随着他们所在代码位置的变化而变化,这8个常量被称为魔术常量。LINE:文件中 本常量所在行的 行号(即处于第几行)。FELE:本文件的完整路径和文件名。如果被用在 被包含文件中,则返回被包含文件的文件名。本常量总是包含一个绝对路径(如果是符号链接,则是解析后的绝对路径)DIR:本文件所在目录。如果被用在 被包含文件中,则返回被包含文件的所在目录。它等价于 dirname(FILE)。除非是根目录,否则目录名中不包含末尾的斜杠。F