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
最后更新于 2020年12月5日