PHP 8 中的新功能:您需要知道的一切!
已发表: 2021-01-06我们将在2020 年 11 月 26 日与 PHP8 取得联系,当然,有很多关于此版本中即将推出的功能的文章。
由于它是一个主要版本,因此会有重大变化和新功能,因此了解所有变化非常重要。 考虑 PHP8 将如何影响您的应用程序以及需要采取哪些措施来确保您可以方便地升级而不会发生意外,这将使您更容易思考。
我们经历了一些最重要的更改,以确定我们将在下一个 PHP 版本中获得什么以及值得批评的地方。
让我们从概述开始吧!
PHP 8 概述
如上所述,PHP 8 为该语言引入了一组新特性、改进、功能和弃用。 其中,讨论最多的特性是 JIT 编译器。 然而,诸如 JIT 之类的性能改进特性值得关注,但语法改进预计将对 PHP 从业者产生更多真正的影响,我们会说,“至少在短期内”。
变化的历史
一些 PHP 更改被提出、辩论、实施,并在短期内得到进一步批准。 它们很受欢迎,没有争议,并且有一种自然的方法来实现它们。
在那之后,在我们最终接受它们之前,那些经过多次尝试、失败和返回的。 有几次,实现要花很长时间来梳理,有时想法本身只是半生不熟,有时社区本身还没有对这个想法热身——“还不是时候”。
学分完全属于类型类别。 它们于 2016 年首次针对 PHP 7.1 提出。 然而,他们遇到了顽固的抵抗,并以巨大的优势输掉了接受投票。 现在,快进四年,一个非常相似的提案,如果稍微缩小范围,只有一个反对者通过。 这显然是一个时机已经到来的想法!
旧代码有什么问题?
由于 PHP 8 是一个巨大的新版本,我们应该期望旧代码不再兼容。 然而,大多数可能带来复杂性的更改已经在7.2 、 7.3和7.4版本中传达。 现在让我们谈谈最后的变化。 他们是:
魔术报价遗产
- 真实的类型
- FILTER_SANITIZE_MAGIC_QUOTES过滤器
- 从非静态闭包中解绑$this
- array_key_exists()与对象
- mb_strrpos()编码作为第三个参数
- 反射export()方法
- convert_cyr_string()函数
- implode()参数顺序混合
- restore_include_path()函数
- hebrevc()函数
- money_format()函数
- allow_url_include ini 指令
- ezmlm_hash()函数
PHP 8 中的新函数
str_contains
如果一个字符串包含另一个字符串,那么有很多方法可以找出答案。
通常,您将使用 strpos()。 如您所知, strpos() 在您想要寻找的针旁边放了一个干草堆。 它返回一个整数,显示您看到针的第一个位置。
现在,当它返回另一个字符串的位置时,您根本无法检查 strpos() 是否发现了它; 如果它返回“0”(位置是零索引并且从 0 而不是 1 开始),那么条件会将其视为假值,并指示它没有找到。
这意味着什么?
你必须写有条件的——“strpos($haystack, $needle) !== false。” False 表示找不到字符串的位置。 这是一种在字符串中搜索字符串的不透明且深奥的方法! 好吧,不是那么混乱。
为了避免这种情况,PHP 8 带来了 str_contains()。 str_contains() 的工作是返回一个简单的布尔值,显示针是否存在于大海捞针中。 这更容易编写,除此之外,作为维护代码的人来理解,不是吗?
if (str_contains( 'Foo Bar Baz' , 'Foo' )) { // FOUND }
PHP 8:引擎特性和变化
PHP 8 中有一些新的引擎特性和变化。毫无疑问,最重要的特性是新的 JIT 编译器。
JIT 编译器
- LSP 执行
- 不兼容的方法签名上的致命错误
- 资源“类”
- XML-RPC 现在在 PECL 中
- 断言行为
- 反射变化
出于本博客的核心目的,我们将重点关注 JIT 编译器、资源“类”,最后是反射 API 的变化。
即时编译器 ( RFC )
由于 PHP7 发布之前的速度改进,Just-In-Time(或 JIT)编译器诞生了。 它被引入 PHP8 是因为在不使用 JIT 的情况下几乎没有更多的速度改进。 这个想法是它将提高 PHP 性能。
PHP 代码在执行时被翻译成字节码,这些字节码被进一步用于执行程序中的步骤。
PHP 将分析您执行的代码,这就是 JIT 的含义。 此外,它可以在您执行代码时做出实时决策,而不是对代码进行性能改进。 它将在 CPU 密集型应用程序中高度可用,而不仅仅是在基于 Web 的场景中使用。
这反映了服务器端 PHP 应用程序肯定会在 PHP 内置 JIT 系统中更加普遍。
如果要使用它,首先需要激活 JIT。 在我们的测试系统(Ubuntu 20.04)上,我们已经安装了 PHP opcache 模块,我们安装了核心 PHP8 包。 我们已经在 /etc/php/8.0/cli/conf.d/10-opcache.ini 的文件中进行了配置。
现在,你们都准备好激活 JIT 了,对吧?
- 启用 opcache
您可以将内存存储分配给opcache.jit_buffer_size设置。 您的文件将在我的系统上显示为这样。
- zend_extension=opcache.so
- opcache.enable_cli=1
- ; php opcache 模块的配置
- opcache.jit_buffer_size=256M
此外使用 opcache_get_status() 函数来确保其是否处于活动状态。 将您的目光移到这个数组的“jit”部分,并获取有关 JIT 当前状态的信息。
var_dump(opcache_get_status()['jit']);
如果 JIT 已被完美激活,这应该会打印出来,如下所示。
- 数组(7){
- [“启用”]=>
- 布尔(真)
- [“开”]=>
- 布尔(真)
- [“善良”]=>
- 整数(5)
- [“选择级别”]=>
- 整数(4)
- [“opt_flags”]=>
- 整数(6)
- [“缓冲区大小”]=>
- 整数(268435440)
- [“buffer_free”]=>
- 整数(268432880)
- }
那么它更快吗? 总之,我们会发出热情的“是”。
我们注意到一些人在 mandelbrot 集的帮助下进行基准测试,因此我们决定使用我们不久前创建的一个库,该库在 PHP 中绘制了一些不同类型的分形。 我们所做的只是生成三个分形并跟踪使用 microtome() 函数所花费的时间。 我们在下面显示了 PHP 7.4.8 的结果。
- Burningship – 84.20269203186
- 曼德尔布洛特 – 21.552599906921
- 三角 - 32.685042858124
当我们在 PHP8 上运行相同的代码时,它的速度要快得多。 数字会说明一切。 .
- Burningship – 15.272277116776
- 曼德勒布洛特 -3.7528541088104
- 三角 -4.4957919120789
这种巨大的速度提升非常有趣。 我们在这里使用的代码创建了巨大的分形,但我们记得在创建代码时,我们大部分时间都花在等待分形的生成上。
这个添加对我们来说很有趣,因为我们在某些情况下(除了生成分形)将 PHP 推向了它的边界。 我们可以注意到这对 PHP 的未来有很大的好处,并且允许在常规网站语言之外的情况下选择该语言。
我们还没有查看 WordPress 或 Drupal 等应用程序的速度增量,但从我们所读到的内容来看,这些应用程序肯定没有什么不同。 将来,我们将在这些平台上运行基准测试,以确定 JIT 在那里标记出什么样的差异。
联合类型 ( RFC )
自 PHP7 以来,规定什么样的返回值和参数类型已经成为可能。 如果您传递的参数类型与预期类型不同,这将允许 PHP 抛出错误。 确保函数在 PHP 等松散类型语言中接收和生成完美类型的值至关重要。

在 PHP8 中,现在可以为参数和返回值规定各种类型,由管道字符分隔。
我们在下面展示了一个能够接受浮点值或整数值的函数。
function addNumbers(int|float $number1, int|float $number2) : int|float { return $number1 + $number2; }
以前,需要在没有任何类型提示的情况下生成与此相同的函数,因为 PHP 可以在传递的参数不正确的情况下静默显示类型。 这意味着如果我们将参数类型设置为整数,PHP 会将任何浮点值显示为整数。 如果您不进行单元测试,它肯定会导致一些棘手的错误。
我们只是像其他任何人一样调用它来使用上述功能
echo addNumbers(1, 1); // prints 2 echo addNumbers(1.1, 1.1); // prints 2.2
如果我们尝试将字符串传递给相同的函数:
echo addNumbers('one', 'two');
我们将收到一个 PHP 致命错误,提示我们需要将浮点数或“int”传递给函数。
您不能将 void 类型用作联合类型,因为它规定该函数将不返回任何内容。 简单来说,你不能惊呼一个函数将返回一个 void 或一个整数。 你会收到一个 PHP 致命错误。
虽然我们可以看到一个正常的变化是这个特性被使用了一点,因为它之前只能在注释中规定各种类型的值,这导致文档块注释变得比代码更详细。
Nullsafe 运算符 (RFC)
除了 null 合并运算符之外,还可以直接从方法中检测 null 返回值。 如果您不知道,null 合并运算符可以让您获取一个值,并且您不必测试该值是否存在,除了在第一个值为 null 的情况下返回不同的值。
因此,我们可以这样做以从“$_GET superglobal”中获取一个值。 如果该值不存在,则为“0”。
1. $page = $_GET['page'] ?? 0;
2. 回显 $page;
null 安全运算符的工作方式是相同的,但允许您创建一个方便的快捷方式并在尝试使用该值之前从某种方式测试 null 返回。
我们注意到这最终将在 Drupal 中有用,我们倾向于编写大量检查代码以确保“方法的返回”或“对象属性”在使用它们之前包含它们。 这是强制性的,因为 Drupal 中对象的上下文状态是因为它们包含的内容。 这种变化肯定会简化一些检查。
命名参数 ( RFC )
命名参数允许您规定不同的参数顺序和调用函数。 采用以下具有两个参数的普通函数。 它将数组填充到给定的长度。
function fillArray(array $arrayToFill, int $number) : array { for ($i = 0 $i < $number; ++$i) { $arrayToFill[$i] =1; } return $arrayToFill; }
我们可以在它们定义的排列中传递参数,可以以正常方式调用此技术。
$newArray = fillArray([], 2);
从 PHP8 开始,您现在可以在将参数传递给函数时为其命名。 它还允许您以我们想要的任何顺序发送参数。
$newArray = fillArray(number: 2, arrayToFill: []);
一个普通的例子,但它也可以使代码更具可读性。
这种技术适用于 PHP 中的所有函数,而不仅仅是用户定义的函数。 在 PHP 语言中,数组和字符串函数的参数顺序不同。 所以,这是一个非常受欢迎的补充。
属性 V2 ( RFC1 RFC2 RFC3 )
属性提供了一种将元数据与 PHP 类、函数、类属性、函数参数和常量相连接的机制。 它们不能通过代码直接访问,您需要借助 PHP 内置的反射类将它们拉出来。
ReflectionClass 类从 PHP5 开始就在 PHP 中; 但是,getAttribute() 方法是 PHP8 的新方法。 此方法返回一系列 ReflectionAttribute 对象,这些对象将包含有关属性的信息。
这个添加面临一些变化(我们可以从上面的多个 RFC 中注意到)。 如果您实例化该类,则可以使用 ReflectionClass 并打印出包含在类级别上的属性信息。 PHP8 中有很多属性,因此我们建议通读 RFC 并熟悉它们的实际含义以及如何将它们集成到代码中。
匹配表达式 ( RFC )
在 PHP 8 中,我们可以将新的匹配表达式与简写 switch 语句进行比较。
它看起来有点像一个函数声明,它将根据传入的值返回一个值。
当您希望在不包含多个if
语句的情况下检查相同表达式的条件时,PHP 中的 switch 语句非常棒。
在这里,我们对相同的表达式进行基本的if-else比较。
<?php if ($i == 'apple') { echo 'i is apple'; } elseif ($i == 'cake') { echo 'i is cake'; } else { echo 'i is pizza'; }
这就是我们前面示例的等效switch
语句的样子
<?php switch ($i) { case 'apple': echo 'i is apple'; break; case 'cake': echo 'i is cake'; break; default: echo 'i is pizza'; }
资源“类”
资源“类”出现在 PHP 8 的主要更改列表中,并作为给定资源类型的不可实例化替代品。 请参阅下文以了解可用的替代品包括:
- CurlHandle — curl_init() 现在返回 CurlHandle,关联一个 curl 资源。
- Socket / AddressInfo — 由套接字扩展提供; socket_*() 函数的数量返回一个 Socket,而 socket_address_info_lookup() 函数返回一个 AddressInfo 实例。
- GdImage — 它代表一个 GD 资源,由众多 imagecreatefrom*() 函数恢复。
此外,重要的是要注意资源不会被 curl_close() 等函数破坏。 相反,您必须 unset() 实例以取消引用它,因为它现在是一个类实例。
您可以在函数和方法中将类指定为类型提示。
早些时候,我们必须返回无类型的值或保留资源参数并通过注释记录它们,但现在,您可以拥有显式类型,这不仅使您的代码更具可读性,而且也更加类型安全。
这里有什么取舍?
如果您想使用 unset() 代替之前用于销毁资源的函数来销毁资源,您现在需要更新您的代码。 这通常可以通过搜索和替换来完成。
反射 API 更改
PHP 8 中的另一个小但重要的变化与反射 API 有关。 在使用属性系统时,您可以方便地通过反射 API 在任何反射类上检索这些属性。
随着混合和联合类型的添加,ReflectionParameter 技术 getClass()、isCallable() 和 isArray() 现在已弃用。
之所以如此,是因为使用 getType() 会好得多,并且您可以获得特定参数满足的类型的完整列表。
PHP 8 语法改进
正如我们所注意到的,JIT 占据了头条。 PHP 8 语法改进为 PHP 开发人员提供了巨大的生活质量优势。
无论是消除提升的构造函数参数中的通用样板,还是改进的异常和错误处理,开发人员都会感到兴奋。
- “混合”伪类型
- 联合类型
- 类构造函数属性提升
- 从表达式中抛出异常
- 属性
- ::类无处不在
- 仅按类型捕获
- 匹配表达式
为了维护本博客的目的,我们将重点放在联合类型、属性和匹配表达式上。
联合类型
联合类型表明一个值是两个或多个指定排序之一。 它是通过在每个允许的类型之间放置一个垂直条来完成的。 对于一些深入研究 PHP 注释以返回值或指定参数的开发人员来说,您很可能已经在这样做了。
对于接受很多不同类型或返回很多不同类型的人来说,联合类型会带来很多复杂性和理解困难。
尽管如此,这在某些情况下还是非常有用的。 例如,如果您可以接受实现新 Stringable 接口的对象(“string|Stringable”)或字符串,或者如果您可以接受实现 Traversable 接口的对象(“array|Traversable”)或数组.
匹配表达式
匹配表达式切断了与确定在给定 switch case 内中断是否是故意相关的猜测。 它还简化了基于匹配分配值的正常模式。
使用时,我们传递给 match() 的值将直接与左侧的表达式进行比较。 无论是表达式还是值,传递给 match() 的值都应该匹配它才能被选择。
匹配时,估计右边的表达式,并返回它的返回值,而表达式只能是 Lambda 或 callables 函数,不允许多行闭包。
属性
PHP 8 也集成了语言级别的属性。 虽然属性通过 docblock 注释存在超过 15 年,但将它们内置到语言中可以提供更好的性能,当然,更强大的功能和更高的一致性。 我们可能会看到属性在工具和快速应用程序的开发中得到高度使用。
结论
当然,PHP8 有一些变化。 尽管如此,看起来大部分被弃用的代码都是针对我们最近没有看到使用的旧功能。
我们非常感兴趣地注意到 JIT 引擎将对我们使用的代码库以及更广泛的 PHP 社区产生什么影响——我们强烈建议您扫描您的代码库以查找与 PHP8 的不兼容性并运行单元测试以确保您的 PHP在对升级说“是”之前,应用程序可以完美运行。