现代 PHP 功能详解 - PHP 8.0 和 8.1
发布日期 作者 Steve McDougall
自 2020 年底发布以来,PHP 8 彻底改变了游戏规则。在本教程中,我将介绍所有最新功能,并以现实世界的例子说明我何时选择使用它们。
我在职业生涯初期就爱上了 PHP 语言,从那时起,我抓住一切机会为它代言。然而,自 8.* 版本发布以来,我不需要夸大任何事情。相反,我能够完全依靠语言本身的事实。让我们来了解 PHP 8.0 版本中的一些突出功能。
构造函数属性提升
这必须是我最常用的 8.0 功能之一,它节省了我很多按键操作。让我们来分解它
// Before PHP 8.0class Client{ private string $url; public function __construct(string $url) { $this->url = $url; }}
// PHP 8.0class Client{ public function __construct( private string $url, ) {}}
我们现在可以直接在构造函数中将属性设置为对象的参数,而不必手动分配它们。我现在几乎一直使用它,因为它节省了精力,而且它也使属性包含在构造函数中 - 因此你可以立即更多地了解你的对象,无需滚动查找。
联合类型
另一个发布的非常棒的功能是联合类型。在这种情况下,类型提示变量或返回类型可以是多种类型之一。这有助于静态分析,因为你可能在一个方法中存在条件返回。让我们看一个例子。
// Before PHP 8.0class PostService{ public function all(): mixed { if (! Auth::check()) { return []; } return Post::query()->get(); }}
// PHP 8.0class PostService{ public function all(): array|Collection { if (! Auth::check()) { return []; } return Post::query()->get(); }}
这种新添加的功能使我们能够在静态分析和自身理解代码的方式上变得非常具体 - 即使只是从表面上看。我们知道 all
方法将返回一个数组或一个集合,这意味着我们的代码更加可预测,我们知道如何处理它。
命名参数
另一个我可能过度使用它的功能。我发现使用命名参数可以使我们的代码具有声明性 - 不必再猜测该函数的第三个参数与你的代码库有何关系。让我们看另一个例子。
// Before PHP 8.0class ProcessImage{ public static function handle(string $path, int $height, int $width, string $type, int $quality, int $compression): void { // logic for handling image processing }} ProcessImage::handle('/path/to/image.jpg', 500, 300, 'jpg', 100, 5);
// PHP 8.0class ProcessImage{ public static function handle(string $path, int $height, int $width, string $type, int $quality, int $compression): void { // logic for handling image processing }} ProcessImage::handle( path: '/path/to/image.jpg', height: 500, width: 300, type: 'jpg', quality: 100, compression: 5,);
如上面的示例所示 - 将高度和宽度弄反将导致与你预期不同的效果。由于类和实现彼此相邻,因此相对容易。现在想象一下这个方法来自你安装的可能没有最佳文档的包 - 使用命名参数使你和任何其他人使用你的代码库的人都可以理解该方法的参数顺序。但是,这仍然应该谨慎使用,因为库作者往往更频繁地更改参数名称,并且不总是被视为重大更改。
匹配表达式
每个人都喜欢并且我谈论过的改进,一项重大改进。过去,我们使用一个带有许多 case 的大型 switch 语句,老实说,它看起来并不美观,也不方便处理。让我们看一个例子。
// Before PHP 8.0switch (string $method) { case 'GET': $method = 'GET'; break; case 'POST': $method = 'POST'; break; default: throw new Exception("$method is not supported yet.");}
// PHP 8.0match (string $method) { 'GET' => $method = 'GET', 'POST' => $method = 'POST', default => throw new Exception( message: "$method is not supported yet.", ),};
match 语句允许更紧凑的语法,并且可读性更高。我不能说这是否会带来任何性能改进,但我确实知道它在现实世界中更容易使用。
在对象上使用 ::class
过去,当你想要将一个类字符串传递给一个方法时,你必须使用类似 get_class
的东西,这总是感觉有点毫无意义。该系统在当时已经知道该类,因为你已经将其自动加载或创建了一个新实例。让我们看一个例子/
// Before PHP 8.0$commandBus->dispatch(get_class($event), $payload);
// PHP 8.0$commandBus->dispatch( event: $event::class, payload: $payload,);
这可能不是一项震撼人心的功能,但它绝对是我使用并且在需要时会一直使用的东西。
不捕获异常的 catch 块
有时在构建应用程序时,你不需要访问可能引发的异常。虽然这对我来说很少见。但是你的情况可能有所不同。让我们看一个例子。
// Before PHP 8.0try { $response = $this->sendRequest();} catch (RequestException $exception) { Log::error('API request failed to send.');}
// PHP 8.0try { $response = $this->sendRequest();} catch (RequestException) { Log::error('API request failed to send.');}
我们不需要捕获异常,因为在这种情况下我们没有使用它。如果我们想要包含异常中的消息,那么请确保你捕获了异常。正如我所说,这不是我使用的东西,因为我通常希望使用引发的异常。
我们都同意 PHP 8.0 是一个我们一直在期待的出色版本。那么 PHP 8.1 呢?它为我们带来了什么?它不可能变得更好,对吧?如果你像我一样这样想,那你就错了。这就是原因。
枚举
可爱的枚举,世界各地无用数据库表和浮动常量的救星。枚举很快成为我最喜欢的 PHP 8.1 功能之一 - 我现在可以将我的角色推送到枚举中,而不是将它们保存在一个永不改变的表中。我可以将 HTTP 方法设置为枚举,而不是常量或我从未真正想要使用的类的公共静态属性。让我们来看一看。
// Before PHP 8.1class Method{ public const GET = 'GET'; public const POST = 'POST'; public const PUT = 'PUT'; public const PATCH = 'PATCH'; public const DELETE = 'DELETE';}
// PHP 8.1enum Method: string{ case GET = 'GET'; case POST = 'POST'; case PUT = 'PUT'; case PATCH = 'PATCH'; case DELETE = 'DELETE';}
上面的示例突出了语法差异,这些差异得到了改进,但实际使用情况如何?让我们快速举例说明一个我通常在 API 集成中使用的 trait。
// Before PHP 8.1trait SendsRequests{ public function send(string $method, string $uri, array $options = []): Response { if (! in_array($method, ['GET', 'POST', 'PATCH', 'PUT', 'DELETE'])) { throw new InvalidArgumentException( message: "Method [$method] is not supported.", ); } return $this->buildRequest()->send( method: $method, uri: $uri, options: $options, ); }}
// PHP 8.1trait SendsRequests{ public function send(Method $method, string $uri, array $options = []): Response { return $this->buildRequest()->send( method: $method->value, uri: $uri, options: $options, ); }}
它允许我的方法从类型角度准确地知道传入的内容 - 并且由于不支持的类型而引发异常的可能性更小。如果我们想扩展支持,现在我们在枚举中添加一个新 case - 而不是添加一个新常量,并且必须重构可能检查支持的所有条件。
解包数组
这个功能是我不确定自己会使用它,直到我真正使用它才发现。以前我们总是必须复制东西或合并数组才能得到我们想要的东西。现在我们可以直接解包数组,行为将相同。我在代码中经常使用 DTO,它们都有一个名为 toArray
的方法,这是一种简单的方法,可以将 DTO 转换为 Eloquent 将为我处理的东西。让我们看一个例子。
// Before PHP 8.1final class CreateNewClient implements CreateNewClientContract{ public function handle(DataObjectContract $client, int $account): Model|Client { return Client::query()->create( attributes: array_merge( $client->toArray(), [ 'account_id' => $account, ], ), ); }}
// PHP 8.1final class CreateNewClient implements CreateNewClientContract{ public function handle(DataObjectContract $client, int $account): Model|Client { return Client::query()->create( attributes: [ ...$client->toArray(), 'account_id' => $account, ], ); }}
如你所见,这只是一个很小的代码更改,但这意味着我不必担心合并数组,而可以简单地在适当的位置解包以构建我需要的数组。它更简洁,更容易管理。我不能对性能发表评论,因为它是一个非常小的操作,但我对任何对这种不同的方法进行过基准测试并发现任何差异的人都很感兴趣。
在构造函数中使用 new
关于新的构造函数,除了你已经想象到的之外,我还能说什么呢?不多,但我还是想尝试一下。在 PHP 8.1 之前,有时你可能不会因为各种原因而将新类的实例传递给构造函数,有时你又会这样做。这就造成了一个情况,即你永远无法确定是否需要传递实例。会有那么一刻,你就会想 - 我就传一个 null 看看会发生什么 - 抱着侥幸心理。感谢 PHP 8.1,它为我们提供了一些防范期限和匆忙决定的保障措施。我说得对吗?让我们来看一个例子。
// Before PHP 8.1class BuyerWorkflow{ public function __construct( private null|WorkflowStepContract $step = null ) { $this->step = new InitialBuyerStep(); }}
// PHP 8.1class BuyerWorkflow{ public function __construct( private WorkflowStepContract $step = new InitialBuyerStep(), ) {}}
因此,至少在我看来,这里的主要优势是代码整洁。使用构造函数中的 new 功能,我们可以不再担心可能会传递 null - 而是让类来处理它。上面的例子有点简单。说实话,这可能是因为我以前没有遇到过这些问题。但是,我知道你们中很多人都会这样做,并且希望能够看到使用这个新功能的好处。
只读属性
我爱死了。我不撒谎。这对我来说是一个巨大的改变。它允许我在不需要降低可见性的情况下轻松地进行不可变编程。以前我必须将我想设置为公有的属性更改为受保护的或私有的 - 这意味着我必须为类添加 getter - 这感觉就像是在添加一些实际上并不需要的样板代码。让我们来看一个例子。
// Before PHP 8.1class Post{ public function __construct() { protected string $title, protected string $content, } public function getTitle(): string { return $this->title; } public function getContent(): string { return $this->content; }}
// PHP 8.1class Post{ public function __construct() { public readonly string $title, public readonly string $content, }}
查看上面的代码示例,你可以因为这个新的语言特性而添加的改进令人印象深刻。很明显可以看出只读属性可以带给你的好处 - 你的代码更简洁,你可以放松对可见性和访问的限制,同时仍然保持不可变性。
当然,这不是一个详尽无遗的清单 - 这只是几个使这些版本脱颖而出的关键要点。PHP 8.0 和 8.1 中添加了许多我没有在这里提到的内容。如果你想更深入地了解所有添加的内容,我强烈推荐你查看 由 Brent Roose 编写的 Stitcher,他对语言更新非常勤奋。
我不会在这篇文章中讨论 PHP 8.2,因为它还没有发布,所以我还没有对新功能形成任何意见 - 但是请关注这个空间,因为它即将到来。更不用说已经计划中的 PHP 8.3 的改进啦!
你最喜欢的现代 PHP 特性是什么?你在未来的版本中想看到什么添加?在 Twitter 上告诉我们!