Loading... # [一文详解 PHP 8 的新特性](https://blog.p2hp.com/archives/7371) PHP 8 将于 2020 年 11 月 26 日发布。这是一个新的主要版本,这意味着它将引入一些突破性的更改,以及许多新功能和性能改进。现在 PHP 8 在功能冻结中,这意味着不能再添加任何新功能了。 由于重大更改,您需要在代码中进行一些更改才能在 PHP 8 上运行。但是,如果您已经跟上最新版本,升级应该不会太难,因为大多数重大更改以前在 7.* 版本中已弃用。别担心,所有这些弃用都列在这篇文章中。 除了突破性变化外,PHP 8 还带来了一系列不错的新功能,如 JIT 编译器、联合类型、attributes等。 新特性 联合类型rfc 鉴于 PHP 的动态类型性质,在很多情况下,联合类型可能很有用。联合类型是两种或更多类型的集合,指示可以使用其中一种类型。 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 rfc JIT (just in time:即时编译) 编译器承诺显著改进性能,尽管并不总是在 Web 请求的上下文中。我已经在真实的Web应用程序上做了我自己的基准测试,而且似乎JIT在这类PHP项目上并没有产生太大的影响。 最重大的特性非 JIT 莫属。该特性的研发历时多年,熬过了 PHP 5 与 7 大版本系列(PHP 6 项目流产),直到去年 3 月份才最终通过投票确认将进入 PHP 8。 JIT 是一种编译器策略,它将代码表述为一种中间状态,在运行时将其转换为依赖于体系结构的机器码,并即时执行。在 PHP 中,这意味着 JIT 将为 Zend VM 生成的指令视为中间表述,并以依赖于体系结构的机器码执行,也就是说托管代码的不再是 Zend VM,而是更为底层的 CPU。 虽然自 PHP 7.0 以来,通过优化核心数据结构 HashTable、强化 Zend VM 中某些操作码与持续改进 OPCache 的 Optimizer 组件等具体措施,PHP 性能得到了显著提升,但是实际上这些优化似乎已经走到极限了。现在 JIT 从底层着手,被认为是目前提升 PHP 性能的最佳出路。 关于引入 JIT 后的性能对比(以及 PHP 8 整体性能),可以参考一下 Phoronix 的基准测试(注:用的是 5 月底的源码构建版进行的测试)。 下面是 PHP 开发团队提供的相关对比数据: 如果您想详细了解 JIT 可以为 PHP 做什么,您可以阅读我在这里写的另一篇帖子。 null安全操作符 rfc 如果您熟悉null coalescing operator,您已经熟悉了它的缺点:它无法处理方法调用。相反,您需要中间检查,或者依赖于某些框架提供的可选帮助程序: $startDate = $booking->getStartDate(); $dateAsString = $startDate ? $startDate->asDateTimeString() : null; 通过添加 null安全运算符,我们现在可以在方法上具有空合并(null coalescing-like)一样的行为! $dateAsString = $booking->getStartDate()?->asDateTimeString(); 命名参数 rfc 命名参数允许您通过指定值名称将值传递给函数,这样您就不必考虑它们的顺序,还可以跳过可选参数! function foo(string $a, string $b, ?string $c = null, ?string $d = null) { /* … */ } foo( b: 'value b', a: 'value a', d: 'value d', ); 您可以在此帖子中深入阅读它们。 Attributes注解 rfc 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。 另请注意,attribute语法仍然可以更改,它尚未决定。 Match表达式 rfc 你可以称它为switch表达式的大哥:match可以返回值,不需要break语句,可以组合条件,使用严格的类型比较,并且不执行任何类型的强制。 如下所示: $result = match($input) { 0 => "hello", '1', '2', '3' => "world", }; 您可以在此处详细阅读match表达式。 Constructor property promotion(构造函数属性提升) rfc 此 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 返回类型 rfc 虽然已经可以返回self,但static在 PHP 8 之前不是有效的返回类型。鉴于PHP的动态类型性质,这功能对许多开发人员都很有用。 class Foo { public function test(): static { return new static(); } } 新的 mixed 类型 rfc 有些人可能称之为必要的邪恶:mixed类型导致许多人有复杂的感觉。不过, 有一个很好的论据可以提出: 一个缺少的类型在 Php 中可能意味着很多事情: 函数不返回任何东西或返回null 我们期待几种类型之一 我们期待一个类型,不能在PHP中进行类型提示 Because of the reasons above, it's a good thing the mixed type is added. mixed itself means one of these types: 由于上述原因,添加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 此 RFC 将throw 从语句更改为表达式,从而有可能在许多新位置引发异常: $triggerError = fn () => throw new MyError(); $foo = $bar['offset'] ?? throw new OffsetDoesNotExist('offset'); 继承私有方法 rfc 以前,PHP 用于对public、protected和private方法应用相同的继承检查。换句话说:private方法应遵循与protected方法和public方法相同的方法签名规则。这没有意义,因为子类无法访问私有方法。 此 RFC 更改了该行为,以便不再对私有方法执行这些继承检查。此外,使用final private function也没有意义,这样做现在将触发警告: Warning: Private methods cannot be final as they are never overridden by other classes Weak maps rfc 基于 PHP 7.4 中添加的weakrefs RFC,在 PHP 8 中添加了WeakMap实现。WeakMap保留对对象的引用,这些引用不会阻止这些对象被垃圾回收。 以 ORM 为例,它们通常实现缓存,这些缓存保存对实体类的引用,以提高实体之间的关系性能。这些实体对象不能被垃圾回收,只要此缓存具有对它们的引用,即使缓存是唯一引用它们的对象。 如果此缓存层使用弱引用和映射代替,PHP 将垃圾收集这些对象当再没有别的引用他们了。特别是在 ORM 的情况下,它可以管理请求中的数百个,如果不是数千个实体;weak maps可以提供更好、更资源友好的处理这些对象的方法。 下面是weak maps的外观,来自 RFC 的示例: class Foo { private WeakMap $cache; public function getSomethingWithCaching(object $obj): object { return $this->cache[$obj] ??= $this->computeSomethingExpensive($obj); } } 允许 ::class 在对象上 rfc 一个小的,但有用的,新功能:现在可以使用::class在对象上,而不必使用get_class() 。它的工作方式与get_class()一样。 $foo = new Foo(); var_dump($foo::class); Non-capturing catches rfc 每当你想捕获到一个异常在PHP 8之前,你必须将它存储到一个变量中,不管你是否使用该变量。用non-capturing 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"作为捕获类型。 参数列表中的尾随逗号 rfc 在调用函数时,参数列表中仍然缺少尾随逗号支持。这现在允许在PHP 8中,这意味着您可以执行以下操作: public function( string $parameterA, int $parameterB, Foo $objectfoo, ) { // … } 旁注:在闭包的use列表中也支持尾随逗号,这是一个疏忽,现在通过单独的 RFC 添加。 从interface创建 DateTime 对象 您已经可以使用DateTime::createFromImmutable($immutableDateTime) 创建DateTime 对象从DateTimeImmutable对象,但另一方面却很棘手。通过添加DateTime::createFromInterface()和DatetimeImmutable::createFromInterface() 现在有一种通用的方式将DateTime 和DateTimeImmutable 互转换。 DateTime::createFromInterface(DateTimeInterface $other); DateTimeImmutable::createFromInterface(DateTimeInterface $other); 新的 Stringable interface rfc Stringable接口可用于任何字符串的类型提示或实现__toString()。此外,每当类实现__toString(),它会自动实现幕后的接口,并且无需手动实现它。 class Foo { public function __toString(): string { return 'foo'; } } function bar(Stringable $stringable) { /* … */ } bar(new Foo()); bar('abc'); 新的 str_contains() 函数 rfc 有些人可能会说它早就该有了,但我们终于不必再依赖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() 函数 rfc 另外两个早就应该有了,这两个函数现在添加到核心中。 str_starts_with('haystack', 'hay'); // true str_ends_with('haystack', 'stack'); // true 新的 fdiv() 函数 pr 新的fdiv() 函数执行类似于fmod()和intdiv() 函数,允许除以0。而不是错误地得到INF, -INF 或 NAN,具体取决于情况。 新的 get_debug_type() 函数 rfc get_debug_type()返回变量的类型。听起来像是gettype()会做的吗?get_debug_type()返回数组、字符串、匿名类和对象的更有用的输出。 例如,在类\Foo\Bar上调用gettype()将返回object。使用get_debug_type() 将返回类名。 可以在 RFC 中找到get_debug_type()和gettype()之间的差异的完整列表。 新的 get_resource_id() 函数 pr 资源是 PHP 中的特殊变量,指的是外部资源。一个示例是 MySQL 连接,另一个是文件句柄。 这些资源中的每个资源都会被分配一个 ID,尽管以前知道 ID 的唯一方法就是将资源强制转换到int: $resourceId = (int) $resource; PHP 8 添加了get_resource_id() 函数,使此操作更加明显且类型安全: $resourceId = get_resource_id($resource); traits 中的抽象方法改进 rfc Traits 可以指定抽象方法,这些方法必须由使用它们的类实现。但有一个警告:在PHP8之前,这些方法实现的签名没有经过验证。以下有效: trait Test { abstract public function test(int $input): int; } class UsesTrait { use Test; public function test($input) { return $input; } } PHP 8 将在使用trait并实现其抽象方法时执行正确的方法签名验证。这意味着您需要如下写法: class UsesTrait { use Test; public function test(int $input): int { return $input; } } 对象实现的 token_get_all() rfc token_get_all()函数返回数组值。此 RFC 添加了一个俱有PhpToken::getAll()方法的PhpToken类。此实现适用于对象而不是纯值。它消耗的内存更少,更易于阅读。 变量语法调整 rfc 在统一变量语法RFC解决了一些在PHP的变量语法的不一致。该RFC旨在解决少数被忽视的情况。 内部函数的类型注解 externals 很多人投入,以添加适当的类型注释到所有内部函数。这是一个长期的问题,最终可以解决与以前版本中对PHP所做的所有更改。这意味着内部函数和方法在反射中将具有完整的类型信息。 ext-json 扩展始终可用 rfc 以前,在没有启用 JSON 扩展的情况下编译 PHP 是可能的,现在这不再可能了。由于 JSON 被广泛使用,因此最好开发人员始终可以依赖它的存在,而不必首先确保扩展存在。 重大更改 如前所述:这是一个主要版本更新,因此会有重大的变化。最好的做法是查看升级文档中的中断更改的完整列表。 不过,这些重大更改中有许多在以前的 7.* 版本中已被弃用,因此,如果您多年来一直保持最新状态,升级到 PHP 8 应该不会那么困难。 一致的类型错误 rfc PHP 中的用户定义的函数将引发TypeError,但内部函数没有,它们宁愿发出警告并返回null。自PHP 8起,内部函数的行为已经保持一致。 重新分类的引擎警告 rfc 以前只触发警告或通知的很多错误已转换为正确的错误。以下警告已更改。 未定义的变量:Error异常而不是通知 未定义的数组索引:警告而不是通知 按零划分:除零错误而不是警告而划分 尝试增加/递增非对象的"%s"属性:错误异常而不是警告 尝试修改非对象的属性"%s":错误异常而不是警告 尝试分配非对象的属性"%s":错误异常而不是警告 从空值创建默认对象:错误异常而不是警告 尝试获取非对象的属性"%s":警告而不是通知 未定义属性: %s::$%s:警告而不是通知 无法将元素添加到数组,因为下一个元素已被占用:错误异常而不是警告 无法在非数组变量中取消设置偏移量:错误异常而不是警告 不能将标量值用作数组:错误异常而不是警告 只能解包数组和可遍历:类型错误异常而不是警告 为每例提供无效参数():类型错误异常而不是警告 非法偏移类型:类型错误异常而不是警告 非法偏移类型设置或空:类型错误异常而不是警告 未设置的非法偏移类型:类型错误异常而不是警告 数组到字符串转换:警告而不是通知 资源 ID#%d 用作偏移量,强制转换为整数 (%d):警告而不是通知 出现字符串偏移转换:警告而不是通知 未初始化字符串偏移量:%d:警告而不是通知 无法将空字符串分配给字符串偏移量:错误异常而不是警告 提供的资源不是有效的流资源:TypeError 异常而不是警告 @ 操作符不再抑制致命错误 此更改可能会揭示 PHP 8 之前再次隐藏的错误。请确保在生产环境设置display_errors=Off! 默认的错误报告级别 现在E_ALL代替一切, 除了E_NOTICE 和 E_DEPRECATED。这意味着许多错误可能会弹出,这些错误以前被静默忽略,尽管在PHP 8之前可能已经存在。 默认PDO的错误模式 rfc 从 RFC:PDO 的当前默认错误模式为silent。这意味着,当发生 SQL 错误时,除非开发人员实现自己的显式错误处理,否则不会发出任何错误或警告,也未引发异常。 此 RFC 更改默认错误将更改为PDO::ERRMODE_EXCEPTION 在PHP 8 中。 串联优先级 rfc 虽然在 PHP 7.4 中已弃用,但此更改现已生效。如果您要写类似的东西: echo "sum: " . $a + $b; PHP 以前会这样解释: echo ("sum: " . $a) + $b; PHP 8 将使其被解释为: echo "sum: " . ($a + $b); 对算术和位运算符进行更严格的类型检查 rfc 在 PHP 8 之前,可以对数组、资源或对象应用算术或位运算符。这不再可能了,并且会引发TypeError: [] % [42]; $object + 4; 命名空间名称成为一个单个token rfc PHP 用于将命名空间的每个部分(用反斜杠\分隔 )解释为令牌序列。此 RFC 改变了该行为,这意味着保留名称现在可以在命名空间中使用 更合理的数字字符串 rfc PHP 的类型系统尝试在遇到字符串中的数字时执行许多智能操作。这种 RFC 使这种行为更加一致和清晰。 更理智的字符串与数字比较 rfc 此 RFC 修复了 PHP 中非常奇怪的情况,其中0 == "foo"结果为true。还有其他一些边缘情况,此 RFC 修复它们。 反射方法签名更改 反射类的三个方法签名已更改: ReflectionClass::newInstance($args); ReflectionFunction::invoke($args); ReflectionMethod::invoke($object, $args); 现在已成为: ReflectionClass::newInstance(...$args); ReflectionFunction::invoke(...$args); ReflectionMethod::invoke($object, ...$args); 升级指南指定,如果扩展这些类,并且仍希望同时支持 PHP 7 和 PHP 8,则允许以下签名: ReflectionClass::newInstance($arg = null, ...$args); ReflectionFunction::invoke($arg = null, ...$args); ReflectionMethod::invoke($object, $arg = null, ...$args); 稳定排序 rfc 在PHP 8之前,排序算法是不稳定的。这意味着无法保证等值元素的顺序。PHP 8 将所有排序函数的行为更改为稳定的排序。 不兼容的方法签名的致命错误 rfc 从 RFC:由于不兼容的方法签名导致的继承错误当前引发致命错误或警告,具体取决于错误的原因和继承层次结构。 其它弃用和更改 在 PHP 7.* 开发期间,添加了几个弃用,这些弃用现在在 PHP 8 中完成。 Deprecations in PHP 7.2 Deprecations in PHP 7.3 Deprecations in PHP 7.4 Locale-independent float to string cast 附件: The match operator in PHP 8 Named arguments in PHP 8 Property promotion in PHP 8 The latest PHP version — 如何管理现代 PHP 版本 Attributes in PHP 8 — 仔细查看attributes,也称为注释 2020年的PHP PHP7.4新特性 PHP 7.3新特性 PHP中文站 最后修改:2023 年 08 月 11 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏