一文详解 PHP 8 的新特性

 

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 做什么,您可以阅读我在这里写的另一篇帖子


 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() 现在有一种通用的方式将DateTimeDateTimeImmutable 互转换。

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 中完成。


 原创翻译,转载请注明来自lenix的博客,地址https://blog.p2hp.com/archives/7371

一文详解 PHP 8 的新特性
标签: