【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】PHP高并发处理中的线程池优化方案
- 随着互联网的快速发展和用户需求的不断增长,高并发成为了现代Web应用开发中的一个重要问题。在PHP中,由于其单线程的特性,处理高并发请求是一项挑战。为了解决这个问题,引入线程池的概念是一个有效的优化方案。线程池是一种可重复利用的线程集合,用于执行大量的并发任务。它的基本思想是将线程的创建、销毁和管理分离出来,通过复用线程来减少系统开销。在PHP中,我们可以利用多进程扩展来实现线程池。下面让我们来看一下如何使用线程池优化高并发处理。首先,我们需要安装pthreads扩展,它是PHP的一个多线程扩展
- 【PHP】php单例模式的应用场景有哪些
- php单例模式的应用场景有数据库连接、缓存管理、日志记录、配置管理、对象工厂和全局状态管理等。详细介绍:1、数据库连接,在一个PHP应用程序中,通常需要与数据库进行交互,为了避免频繁地创建和销毁数据库连接,可以使用单例模式来创建一个数据库连接类,并确保只有一个数据库连接实例存在,这样可以减少资源的消耗,并提高数据库操作的效率;2、缓存管理,缓存是一种常见的性能优化手段等等。本教程操作系统:windows10系统、PHP 8.1.3版本、DELL G3电脑。单例模式是一种常见的设计模式,
- 【PHP】Thinkphp8 配置异常全局捕捉处理
- 封装异常处理配置先创建自己的 BaseException 类<?php namespace app\exception; use app\enums\StatusCodeEnum; class BaseException extends \Exception { public $success = false; &nbs
- 【PHP】构建一个在线视频网站
- PHP是一种广泛应用的开发语言,被用于构建许多各种类型的网站和应用程序。在本文中,将介绍使用PHP开发一个在线视频网站的流程。第一步:需求分析在开始开发之前,首先需要进行需求分析。这包括确定网站的主要功能和特性,例如用户注册、视频上传、视频播放等。还需要确定网站的目标用户和受众群体,以及预期的规模和流量。通过深入了解需求,可以为后续的开发工作提供指导。第二步:数据库设计在构建一个在线视频网站时,一个关键的步骤是设计数据库。数据库将存储用户信息、视频信息、评论、标签等相关数据。需要确定表
- 【PHP】php 判断是否是数组
- 在PHP中,判断一个变量是否是数组可以使用多种方式。以下将介绍四种判断方法:1. is_array()函数is_array()函数是PHP中判断变量是否是数组的最常用方法。该函数接受一个变量作为参数,如果参数是一个数组,则返回true;反之,返回false。下面是一个使用is_array()函数的例子:$arr = array("apple","banana","cherry"); if(is_array(
- 【PHP】php amr格式转化mp3
- 在音频文件处理中,有时候我们需要将AMR格式的音频文件转换成MP3格式。本文将介绍如何使用PHP语言来完成AMR格式转化MP3。一、AMR格式简介AMR全称 Adaptive Multi-Rate,是一种压缩音频格式。由于AMR格式文件体积小,网络传输速度快,因此被广泛应用于手机铃声、语音留言、移动通讯等领域。二、MP3格式简介MP3全称 MPEG Audio Layer-3,是一种常用的音频格式。由于MP3格式具有音质高、可压缩、体积小等特点,因此被广泛应用于音乐播放器、电影播放器等
- 【PHP】PHP函数array_map()
- 在PHP的函数库中,有一款非常实用的函数,那就是array_map()函数。它可以将一个数组中的数据传递给某个函数进行处理,最终返回一个新的数组。array_map()函数可以极大地方便我们数据的处理,下面我们来详细介绍一下它的使用。一、array_map()函数的基本用法array_map()的基本语法格式为:array_map(callable $callback, array ...$arr)其中,$callback参数表示将要被调用的函数或方法,它和数组中的每一个元素一一对应。而$arr
- 【PHP】PHP面试题之算法题
- hp面试题中也会经常出现算法题,本文主要和大家分享PHP面试题之算法题,希望能帮助到大家。面试题——算法题:1、插入排序(一维数组) 基本思想:每次将一个待排序的数据元素,插入到前面已经排好序的数列中的适当位置,使数列依然有序;直到待排序数据元素全部插入完为止。 示例:[初始关键字] [49] 38 65 97 76 13 27 49J=2(38) [38 49] 65 97 76 13 27 49J=3(65) [38 49 65] 97 76 13 27 49J=4(97) [38 49