写点什么

一文看懂 PHP 8 的新特性

2020 年 7 月 28 日

一文看懂PHP 8的新特性

本文最初发布于 stitcher 博客, 经原作者授权由 InfoQ 中文站翻译并分享。


2020 年 11 月 26 日,PHP 8 将正式发布。这是一个新的主要版本,它将引入一些重大更改,以及许多新特性和性能改进。PHP 8 目前正处于非常活跃的开发阶段,其第一个 Alpha 版已经于 2020 年 6 月 26 日发布。


因为新版引入了许多重大更改,你很有可能需要对代码进行一些更改才能使其运行在 PHP 8 上。如果你一直都在使用最新版本,那么这一次的升级也应该不会很难,因为多数重大更改都已在之前的 7.*版本中弃用了。不用担心,所有这些弃用的内容都在本文中列出来了。


除了重大更改外,PHP 8 还带来了一组不错的新特性,例如JIT编译器联合类型Attributes等。


新特性

首先,我们来看新特性。请记住 PHP 8 仍在积极开发当中,因此这个列表会随着时间的推移而变长。


联合类型

鉴于 PHP 具有动态类型的性质,在很多情况下联合类型是很有用的。联合类型(Union Types)是两种或多种类型的集合,用户可以使用其中一种。


public function foo(Foo|Bar $input): int|float; 
复制代码


请注意,void永远不能成为联合类型的一部分,因为它表示“根本没有返回值”。此外,可以使用|null或使用现有的?符号来写nullable的联合类型:


public function foo(Foo|null $foo): void; public function bar(?Bar $bar): void; 
复制代码


JIT

JIT(即时)编译器可以显著提升性能,不过,它并不总是在 Web 请求的上下文中。目前还没有准确的基准测试可用,但将来肯定会有的。


如果你想进一步了解 JIT 对 PHP 的作用,可以阅读我在这里写的另一篇文章


Attributes

Attributes在其他语言中通常称为 annotations,它提供了一种向类添加元数据的方法,这种方法无需解析文档块。


下面是 RFC 中提供的 Attributes 示例:


use App\Attributes\ExampleAttribute; @@ExampleAttribute class Foo {     @@ExampleAttribute     public const FOO = 'foo';       @@ExampleAttribute     public $x;       @@ExampleAttribute     public function foo(@@ExampleAttribute $bar) { } } 
复制代码


@@Attribute class ExampleAttribute {     public $value;       public function __construct($value)     {         $this->value = $value;     } } 
复制代码


请注意,这个基本的Attribute在之前的 RFC 中曾称为PhpAttribute,但之后在另一个 RFC 中改成现在的样子。如果你想深入了解 attributes 的工作机制,以及如何构建自己的 Attributes,可以阅读这篇深入解析 Attributes 的博客


Match 表达式

你可以称之为switch表达式的老大哥:match可以返回值,不需要break语句,可以组合条件,使用严格的类型比较,并且不执行任何强制类型转换(type coercion) 。


它是这个样子:


$result = match($input) {     0 => "hello",     '1', '2', '3' => "world", }; 
复制代码


要了解 Match 表达式的细节,请看这里


Constructor property promotion

这个 RFC 添加了语法糖来创建值对象或数据传输对象。现在 PHP 不用再为它们指定类属性和一个构造器,可以将它们组合为一个。


以前是这样做:


class Money  {     public Currency $currency;       public int $amount;       public function __construct(         Currency $currency,         int $amount,     ) {         $this->currency = $currency;         $this->amount = $amount;     } } 
复制代码


现在可以这样:


class Money  {     public function __construct(         public Currency $currency,         public int $amount,     ) {} } 
复制代码


关于 property promotion 的更多信息,可以参考这篇专门介绍它的文章


新的 static 返回类型

虽然现在的 PHP 已经可以返回self,但是直到 PHP 8 中static才是有效的返回类型。考虑到 PHP 动态类型的性质,这个特性对许多开发人员都非常有用。


class Foo {     public function test(): static     {         return new static();     } } 
复制代码


新的 mixed 类型

有些人可能称其为必要之恶:mixed类型会让很多人感到困惑。不过,加入它的决定也是有理由的:在 PHP 中,缺少某种类型可能有很多后果:


  • 函数不返回任何内容或返回 null

  • 我们期望的是某种类型

  • 我们期望的类型在 PHP 中无法被类型提示


由于上述原因,增加mixed类型是一件好事。mixed本身是以下类型之一:


  • array

  • bool

  • callable

  • int

  • float

  • null

  • object

  • resource

  • string


注意,mixed也可以用作参数或属性类型,而不仅仅是返回类型。


另外请注意,由于mixed已经包含null,因此不允许将其设置为nullable。以下内容将触发错误:


// Fatal error: Mixed types cannot be nullable, null is already part of the mixed type. function bar(): ?mixed {} 
复制代码


Throw 表达式

该 RFC 将throw从语句变为表达式,这样就可以在许多新场景中抛出异常:


$triggerError = fn () => throw new MyError(); $foo = $bar['offset'] ?? throw new OffsetDoesNotExist('offset'); 
复制代码


用私有方法继承

以前,PHP 曾经对公共、保护和私有方法应用相同的继承检查。换句话说:私有方法应遵循与保护方法和公共方法相同的方法签名规则。这是没有道理的,因为子类将无法访问私有方法。


该 RFC 更改了这个行为,因此不再对私有方法执行这些继承检查。此外,使用final private function也没有意义,因此,现在它将触发警告:


Warning: Private methods cannot be final as they are never overridden by other classes 
复制代码


Weak maps

在 PHP 7.4 中添加的weakrefs RFC的基础上,PHP 8 添加了WeakMap实现。WeakMap保存对对象的引用,这不会阻止这些对象被垃圾回收。


以 ORM 为例,它们通常会实现缓存,其缓存保存对实体类的引用,以提高实体之间关系的性能。只要该缓存具有对这些实体对象的引用,就不能对其进行垃圾回收,即使该缓存是唯一引用它们的对象也是如此。


如果该缓存层使用了弱引用和映射,则 PHP 将在没有其他引用时对这些对象进行垃圾回收。尤其是对于 ORM,它可以管理一个请求中的数百个(乃至数千个)实体。Weak maps(弱映射)可以提供一种更好,对资源更友好的方式来处理这些对象。


这是 RFC 中 Weak maps 的示例:


class Foo  {     private WeakMap $cache;       public function getSomethingWithCaching(object $obj): object     {         return $this->cache[$obj]            ??= $this->computeSomethingExpensive($obj);     } } 
复制代码


在对象上允许::class

一个小的但有用的新特性:现在可以在对象上使用::class,而不必使用get_class()。它的工作方式与get_class()相同。


$foo = new Foo(); var_dump($foo::class); 
复制代码


非捕获 catches

在 PHP 8 之前,每当你想捕获一个异常时都必须将其存储在一个变量中,不管你是否使用这个变量。现在使用非捕获 catches,你也可以忽略变量。以前是这样:


try {     // Something goes wrong } catch (MySpecialException $exception) {     Log::error("Something went wrong"); } 
复制代码


现在可以执行以下操作:


try {     // Something goes wrong } catch (MySpecialException) {     Log::error("Something went wrong"); } 
复制代码


请注意,你必须始终指定类型,不允许使用空catch。如果要捕获所有的异常和错误,可以使用Throwable作为捕获类型。


参数列表中的尾部逗号

现在的 PHP,虽然可以调用函数时在尾部加逗号,但参数列表中仍然缺少对尾部逗号的支持。PHP 8 现在允许使用它,也就是说你可以执行以下操作:


public function(     string $parameterA,     int $parameterB,     Foo $objectfoo, ) {     // 注意上面最后一个逗号… } 
复制代码


从接口创建DateTime对象

你已经可以使用DateTime::createFromImmutable($immutableDateTime)DateTimeImmutable对象创建DateTime对象,但反过来就很麻烦。


PHP 8 添加了DateTime::createFromInterface()DatetimeImmutable::createFromInterface(),所以现在有一种通用的方法可以将DateTimeDateTimeImmutable对象彼此转换。


DateTime::createFromInterface(DateTimeInterface $other); DateTimeImmutable::createFromInterface(DateTimeInterface $other); 
复制代码


新的Stringable接口

Stringable接口可用于类型提示任何字符串或实现__toString()的内容。此外,每当一个类实现__toString()时,它就会自动实现幕后接口,而无需手动实现。


class Foo {     public function __toString(): string     {         return 'foo';     } } function bar(Stringable $stringable) { /* … */ } bar(new Foo()); bar('abc'); 
复制代码


新的str_contains()函数

有人可能会说它早就该来了,总之我们终于不必再依赖strpos来知道一个字符串是否包含另一个字符串了。


以前是这样做:


if (strpos('string with lots of words', 'words') !== false) { /* … */ } 
复制代码


现在,你可以这样:


if (str_contains('string with lots of words', 'words')) { /* … */ } 
复制代码


新的str_starts_with()str_ends_with()函数

另外两个早就该做的函数,现在已加入核心。


str_starts_with('haystack', 'hay'); // true str_ends_with('haystack', 'stack'); // true 
复制代码


新的fdiv()函数

新的fdiv()函数与fmod()intdiv()函数的功能相似,允许被 0 除。根据情况你会得到INF-INFNAN,而不是错误。


新的get_debug_type()函数

get_debug_type()返回一个变量的类型。听起来像gettype()的功能?get_debug_type()为数组、字符串、匿名类和对象返回更有用的输出。


例如,在类\Foo\Bar上调用gettype()将返回object。使用get_debug_type()将返回类名称。


可以在 RFC 中找到get_debug_type()gettype()之间差异的完整列表。


新的get_resource_id()函数

Resources 是 PHP 中的特殊变量,指的是外部资源。一个例子是 MySQL 连接,另一个是文件句柄。


这些资源中每一个都分配了一个 ID,但以前唯一知道该 ID 的方法是将资源转换为int


$resourceId = (int) $resource; 
复制代码


PHP 8 添加了get_resource_id()函数,让这个操作更加明显易懂,且类型安全:


$resourceId = get_resource_id($resource); 
复制代码


raits 改进中的抽象方法

Traits 可以指定抽象方法,这些方法必须由使用它们的类实现。需要注意的是:在 PHP 8 之前,这些方法实现的签名没有被验证。以下写法是有效的:


trait Test {     abstract public function test(int $input): int; } class UsesTrait {     use Test;     public function test($input)     {         return $input;     } } 
复制代码


在 PHP 8 中,当使用一个 trait 并实现其抽象方法时,PHP 8 将执行正确的方法签名验证。这意味着你需要编写以下代码:


class UsesTrait {     use Test;     public function test(int $input): int     {         return $input;     } } 
复制代码


token_get_all()的对象实现

token_get_all()函数返回一个值数组。该 RFC 使用PhpToken::getAll()方法添加了PhpToken类。此实现适用于对象而不是普通值。它消耗的内存更少,并且更容易阅读理解。


可变语法调整

根据 RFC:“统一变量语法 RFC 解决了 PHP 变量语法中的许多不一致之处。而本 RFC 旨在解决一小部分被忽略的情况。”


内部函数的类型注解

许多人开始为所有内部函数添加适当的类型注释。这个问题历史很久了,而 PHP 之前版本所做的一系列更改终于为解决它铺平了道路。这意味着内部函数和方法将反映出完整的类型信息。


ext-json始终可用

以前,可以在不启用 JSON 扩展的情况下编译 PHP,以后就不行了。现在,开发人员知道 JSON 是一直能用的,而不需要提前确认扩展是否可用。由于 JSON 非常流行,所以这个改进很方便。


重大更改

如前所述:PHP 8 是一个重大更新,因此会有很多重大更改。最好在UPGRADING文档中查看重大更改的完整列表。


但许多重大更改在以前的 7.*版本中已经弃用,因此如果你多年来一直紧跟新版,那么升级到 PHP 8 并不会有什么困难。


一致的类型错误

现在 PHP 的用户定义函数会抛出TypeError,但内部函数并不会,而是发出警告并返回null。从 PHP 8 开始,内部函数的行为也是一样了。


重新分类的引擎警告

以前,许多仅触发警告或通知的错误已转换为合适的错误类型。以下警告已更改。


  • Undefined 变量:Error异常取代了通知

  • Undefined 数组索引:警告取代了通知

  • 除以零:DivisionByZeroError异常取代了警告

  • 尝试增加/减少非对象的’%s’属性:Error异常取代了警告

  • 尝试修改非对象的’%s’属性:Error异常取代了警告

  • 尝试分配非对象的’%s’属性:Error异常取代了警告

  • 从空值创建默认对象:Error异常取代了警告

  • 试图获取非对象的’%s’属性:警告取代了通知

  • 未定义的属性:%s::$%s:警告取代了通知

  • 由于下一个元素已被占用,无法将元素添加到数组:Error异常取代了警告

  • 无法取消设置非数组变量中的偏移量:Error异常取代了警告

  • 无法将标量值用作数组:Error异常取代了警告

  • 只能解包数组和TraversablesTypeError异常取代了警告

  • 为 foreach()提供了无效参数:TypeError异常取代了警告

  • 偏移量类型非法:TypeError异常取代了警告

  • isset 中的偏移量类型非法或为空:TypeError异常取代了警告

  • unset 中的偏移量类型非法:TypeError异常取代了警告

  • 数组到字符串的转换:警告取代了通知

  • Resource ID#%d 用作偏移量,转换为整数(%d):警告取代了通知

  • 发生字符串偏移量转换:警告取代了通知

  • 未初始化的字符串偏移量:%d:警告取代了通知

  • 无法将空字符串分配给字符串偏移量:Error异常取代了警告

  • 提供的资源不是有效的流资源:TypeError异常取代了警告


@运算符不再让致命错误静默

此更改可能会揭示出 PHP 8 之前隐藏的错误。请确保在生产服务器上设置display_errors=Off


默认错误报告级别

现在是E_ALL,而不是E_NOTICEE_DEPRECATED。这意味着新版可能会弹出许多错误,这些错误在 PHP 8 以前会被静默忽略。


默认 PDO 错误模式

根据 RFC:当前,PDO 的默认错误模式为静默。换句话说,当发生 SQL 错误时,除非开发人员实现自己的显式错误处理,否则不会发出错误或警告,也不会引发异常。


此 RFC 更改后,默认错误将更改为PDO::ERRMODE_EXCEPTION


串联优先级

这一更改在 PHP 7.4 中已弃用,现在正式移除。如果你要编写这样的内容:


echo "sum: " . $a + $b; 
复制代码


PHP 以前会这样解释它:


echo ("sum: " . $a) + $b; 
复制代码


PHP 8 将改为这种解释:


echo "sum: " . ($a + $b); 
复制代码


对算术和按位运算符进行更严格的类型检查

在 PHP 8 之前,可以在数组、资源或对象上应用算术或按位运算符。现在就不行了,新版将抛出TypeError


[] % [42]; $object + 4; 
复制代码


反射方法签名更改

反射类的三个方法签名已更改:


ReflectionClass::newInstance($args); ReflectionFunction::invoke($args); ReflectionMethod::invoke($object, $args); 
复制代码


现在变为:


ReflectionClass::newInstance(...$args); ReflectionFunction::invoke(...$args); ReflectionMethod::invoke($object, ...$args); 
复制代码


升级指南提到,如果你扩展这些类,并且仍要同时支持 PHP7 和 PHP 8,则允许以下签名:


ReflectionClass::newInstance($arg = null, ...$args); ReflectionFunction::invoke($arg = null, ...$args); ReflectionMethod::invoke($object, $arg = null, ...$args); 
复制代码


稳定的排序

在 PHP 8 之前,排序算法是不稳定的。这意味着不能保证相等元素的顺序。PHP 8 将所有排序函数的行为更改为稳定排序。


不兼容方法签名的致命错误

根据 RFC:由于不兼容的方法签名而导致的继承错误现在会引发致命错误或警告,具体取决于错误原因和继承层次结构。


其他弃用和更改

在 PHP7.*开发过程中加入了几个弃用,这些弃用现已在 PHP 8 中正式移除。



英文原文 :


https://stitcher.io/blog/new-in-php-8


2020 年 7 月 28 日 09:582828
用户头像

发布了 538 篇内容, 共 196.3 次阅读, 收获喜欢 1167 次。

关注

评论

发布
暂无评论
发现更多内容

就像TM做梦一样:金三突击面试,成功斩获阿里、美团等多个大厂offer

互联网架构师小马

Java 编程 程序员 面试 软件开发

IPFS项目是不是真的?IPFS国家认可吗?

投资矿机v:IPFS1234

IPFS国家认可吗 IPFS项目是不是真的

全网下载量过亿!12万字阿里内部Java面试手册有多强?

Java架构追梦

Java 架构 面试 成长笔记 阿里巴巴内部资料

C统计量/ C statistic

Geek_Goldensikaiqi

五面蚂蚁、三面拼多多、字节跳动最终拿offer入职拼多多(Java岗)

程序员改bug

Java 架构 程序员面试 Java面经

如何完成日千万级别以上的订单对账(二)

谙忆

太厉害了!腾讯T4大牛把《数据结构与算法》讲透了,带源码笔记

Crud的程序员

编程 程序员 数据结构与算法

ThreadLocal超深度源码解读,为什么要注意内存泄漏?不要道听途说,源码底下见真知!

徐同学呀

ThreadLocal Java源码

IPFS矿机1T每天能挖多少?IPFS矿机多少钱一台?

投资矿机v:IPFS1234

IPFS矿机多少钱一台 IPFS矿机1T每天能挖多少

百度搜索与推荐引擎的云原生改造 | Geek大咖说第一期

百度Geek说

Chrome浏览器远程代码执行0Day漏洞风险通告——POC已公开

Machine Gun

腾讯 网络安全 HTTP

金三拿到5个offer,全靠这份Alibaba内部Java面试指南

云流

Java 编程 程序员 架构 面试

Mokito 单元测试与 Spring-Boot 集成测试

Zhang

Java 单元测试 集成测试 Mokito Spring boot starter test

DNS原理及其应用

赖猫

c++ 后台开发 网络编程 DNS 服务器开发

PHPStorm 安装Xdebug插件开启单步调试

慢慢de

win10 Xdebug PHPStorm

思码逸Merico 完成 A 轮融资,发布企业版 3.0 新产品,拓展研发效能边界

InfoQ 的朋友们

InfoQ 的朋友们

翻译:《实用的Python编程》09_00_Overview

codists

Python

BOE(京东方)2020年报发布:营收1355.53亿元  净利润大幅增长162.46%

爱极客侠

智慧党建平台搭建,组织部干部任免系统开发

13823153121

2021年金三银四跳槽季,呕心沥血整理出Java10W字面经,首次公布!

Java架构之路

Java 程序员 架构 面试 编程语言

龙归科技|邀您参与全球「身份管理日」

龙归科技

Kubenav: 使用手机管理你的 K8S 集群

郭旭东

Kubernetes k8s多集群管理

阿里内部疯传的《JDK源码剖析手册》!在GitHub上已高达百万访问量!

Java架构之路

Java 程序员 架构 面试 编程语言

阿里P8总结的1530页Java编程核心思想笔记,Github访问破百万!

Java架构之路

Java 程序员 架构 面试 编程语言

全靠这份阿里巴巴Java面试参考指南(泰山版),已让我成功在金三拿到8个Offer

神奇小汤圆

Java 编程 程序员 架构 面试

消息队列(如 Kafka 等)的应用场景

五分钟学大数据

kafka 消息队列 4月日更

线上500万数据查询时间在37秒,作者将问题解决了,我却看到了更大的坑

谙忆

如何完成日千万级别以上的订单对账(一)

谙忆

复习一周,字节跳动三场技术面+HR面,不小心拿了offer

Crud的程序员

Java 编程 架构 Java工程师

4.16-17 | 阿里云技术大咖分享新内容新交互时代下的新技术、新机会

阿里云视频云

阿里云 WebRTC 直播架构 音视频开发

Flume拦截器实战

大数据技术指南

flume 4月日更

一文看懂PHP 8的新特性-InfoQ