PHP 8.1 的新特性:特性、变化、改进等

不久前,PHP 8.0 大张旗鼓地发布了。它带来了许多新特性、性能增强和变化——其中最令人兴奋的是新的 JIT 编译器。

尝试免费演示

技术世界总是在向前发展,PHP 也是如此。

PHP 8.1 快要发布了,它包含了几个令人兴奋的功能。它定于今年晚些时候于 2021 年 11 月 25 日发布。

在本文中,我们将详细介绍 PHP 8.1 中的新增功能。从其新功能和性能改进到重大更改和弃用,我们将深入介绍它们。

稳坐!

PHP 8.1 中的新功能

让我们首先介绍 PHP 8.1 中的所有新功能。这是一个相当多的清单。

注意:随着 PHP 8.1 发布日期的临近,此列表可能会增加或缩小。我们将致力于使其保持最新状态。

纯交集类型

PHP 8.1 添加了对交集类型的支持。它类似于 PHP 8.0 中引入的联合类型,但它们的预期用途恰恰相反。

为了更好地理解它的用法,让我们回顾一下 PHP 中类型声明的工作原理。

本质上,您可以向函数参数、返回值和类属性添加类型声明。这种分配称为类型提示,并确保值在调用时是正确的类型。否则,它会立即抛出一个 TypeError。反过来,这可以帮助您更好地调试代码。

但是,声明单一类型有其局限性。联合类型通过允许您声明具有多种类型的值来帮助您克服这个问题,并且输入必须至少满足声明的类型之一。

另一方面,RFC 将交集类型描述为:

“交集类型”需要一个值来满足多个类型约束而不是单个约束。

…纯交集类型使用语法 T1&T2&… 指定,可用于当前接受类型的所有位置…

请注意使用 & (AND) 运算符来声明交集类型。相反,我们使用 | (OR) 运算符来声明联合类型。

在交集类型中使用大多数标准类型将导致永远无法满足的类型(例如整数和字符串)。因此,交集类型只能包括类类型(即接口和类名)。

以下是如何使用交集类型的示例代码:

A 类 {
    私有 Traversable&Countable $countableIterator;

    公共函数 setIterator(Traversable&Countable $countableIterator): void {
        $this->countableIterator = $countableIterator;
    }

    public function getIterator(): Traversable&Countable {
        return $this->countableIterator;
    }
}

在上面的代码中,我们将变量 countableIterator 定义为两种类型的交集:Traversable 和 Countable。在这种情况下,声明的两种类型是接口。

交集类型也符合已用于类型检查和继承的标准 PHP 变化规则。但是还有两个关于交集类型如何与子类型交互的额外规则。您可以在其 RFC 中阅读有关交集类型差异规则的更多信息。

在某些编程语言中,您可以在同一个声明中组合联合类型和交集类型。但是 PHP 8.1 禁止它。因此,它的实现被称为“纯”交集类型。但是,RFC 确实提到它“留作未来范围”。

枚举

PHP 8.1 终于添加了对枚举(也称为枚举或枚举类型)的支持。它们是用户定义的数据类型,由一组可能的值组成。

编程语言中最常见的枚举示例是布尔类型,其中 true 和 false 作为两个可能的值。它是如此普遍,以至于它被融入到许多现代编程语言中。

根据 RFC,PHP 中的枚举首先将被限制为“单元枚举”:

此 RFC 的范围仅限于“单元枚举”,即枚举本身就是一个值,而不是简单的原始常量的花哨语法,并且不包括附加的相关信息。此功能极大地扩展了对数据建模、自定义类型定义和 monad 样式行为的支持。枚举启用了“使无效状态不可表示”的建模技术,这会导致更健壮的代码,而无需进行详尽的测试。

为了达到这个阶段,PHP 团队研究了许多已经支持枚举的语言。他们的调查发现,您可以将枚举分为三类:花式常量、花式对象和完整代数数据类型 (ADT)。这是一个有趣的阅读!

PHP 实现了“Fancy Objects”枚举,并计划在未来将其扩展到完整的 ADT。它在概念和语义上都模仿了 Swift、Rust 和 Kotlin 中的枚举类型,尽管它没有直接模仿它们中的任何一个。

RFC 使用一副牌中著名的西装类比来解释它是如何工作的:

enum Suit {
  case Hearts;
  案例钻石;
  案例俱乐部;
  案例黑桃;
}

在这里, Suit 枚举定义了四个可能的值:Hearts、Diamonds、Clubs 和 Spades。您可以使用以下语法直接访问这些值:Suit::Hearts、Suit::Diamonds、Suit::Clubs 和 Suit::Spades。

这种用法可能看起来很熟悉,因为枚举是建立在类和对象之上的。它们的行为相似并且具有几乎完全相同的要求。枚举与类、接口和特征共享相同的命名空间。

上面提到的枚举称为纯枚举。

如果您想为任何情况提供标量等效值,您还可以定义支持枚举。但是,支持的枚举只能有一种类型,int 或 string(决不能同时具有)。

枚举套装:字符串 {
  case Hearts=”H”;
  案例钻石=“D”;
  案例俱乐部=“C”;
  案例黑桃=“S”;
}

此外,支持枚举的所有不同情况都必须具有唯一值。你永远不能混合纯枚举和支持枚举。

RFC 进一步深入探讨了枚举方法、静态方法、常量、常量表达式等等。涵盖所有这些超出了本文的范围。您可以参考文档以熟悉它的所有优点。

永不返回类型

PHP 8.1 添加了一个名为 never 的新返回类型提示。在总是抛出或退出的函数中使用非常有用。

根据 RFC,总是退出(显式或隐式)的 URL 重定向函数是其使用的一个很好的例子:

函数重定向(字符串 $uri):从不{
    header(’位置:’ . $uri);
    出口();
}

function redirectToLoginPage(): never {
    redirect(‘/login’);
}

一个从未声明的函数应该满足三个条件:

  • 它不应该显式定义 return 语句。
  • 它不应该隐式定义 return 语句(例如 if-else 语句)。
  • 它必须以退出语句(显式或隐式)结束其执行。

上面的 URL 重定向示例显示了从不返回类型的显式和隐式用法。

从不返回类型与 void 返回类型有许多相似之处。它们都确保函数或方法不返回值。但是,它的不同之处在于执行更严格的规则。例如,声明为 void 的函数仍然可以在没有显式值的情况下返回,但不能对从未声明的函数执行相同操作。

根据经验,当您希望 PHP 在函数调用后继续执行时,请使用 void。当你想要相反的时候,永远不要。

此外, never 被定义为“底部”类型。因此,任何声明为 never 的类方法都不能“永远”将其返回类型更改为其他类型。但是,您可以使用从未声明的方法扩展声明为空的方法。

信息

原始 RFC 将 never return 类型列为 noreturn,这是两个 PHP 静态分析工具(即 Psalm 和 PHPStan)已经支持的返回类型。由于这是由 Psalm 和 PHPStan 的作者自己提出的,因此他们保留了其术语。然而,由于命名约定,PHP 团队对 n​​oreturn 与 never 进行了民意调查,从没有成为永远的赢家。因此,对于 PHP 8.1+ 版本,始终将 noreturn 替换为 never。

纤维

从历史上看,PHP 代码几乎一直是同步代码。代码执行暂停,直到返回结果,即使是 I/O 操作。您可以想象为什么这个过程可能会使代码执行速度变慢。

有多种第三方解决方案可以克服这一障碍,允许开发人员异步编写 PHP 代码,尤其是并发 I/O 操作。一些流行的示例包括 amphp、ReactPHP 和 Guzzle。

但是,在 PHP 中没有处理此类实例的标准方法。此外,在同一个调用堆栈中处理同步和异步代码会导致其他问题。

纤程是 PHP 通过虚拟线程(或绿色线程)处理并行性的方式。它试图通过允许 PHP 函数中断而不影响整个调用堆栈来消除同步和异步代码之间的差异。

以下是 RFC 的承诺:

  • 向 PHP 添加对 Fibers 的支持。
  • 引入一个新的 Fiber 类和对应的反射类 ReflectionFiber。
  • 添加异常类 FiberError 和 FiberExit 来表示错误。
  • Fibers 允许现有接口(PSR-7、Doctrine ORM 等)的透明非阻塞 I/O 实现。那是因为占位符(promise)对象被消除了。相反,函数可以声明 I/O 结果类型,而不是无法指定解析类型的占位符对象,因为 PHP 不支持泛型。

您可以使用 Fibers 开发全栈、可中断的 PHP 函数,然后您可以使用这些函数在 PHP 中实现协作多任务处理。当 Fiber 暂停整个执行堆栈时,您可以放心,因为它不会损害您的其余代码。

图表说明了使用 Fibers 的 PHP 代码执行流程。

为了说明 Fibers 的用法,它的 RFC 使用了这个简单的例子:

$fiber = new Fiber(function (): void {
    $value = Fiber::suspend(‘fiber’);
    echo “Value used to resume fiber: “, $value, “n”;
});

$value = $fiber->start();

回声“来自纤维悬挂的价值:”,$价值,“n”;

$fiber->resume(‘test’);

您在上面的代码中创建了一个“光纤”,并立即用字符串光纤挂起它。echo 语句用作光纤恢复的视觉提示。

您可以从对 $fiber->start() 的调用中检索此字符串值。

然后,您使用字符串“test”恢复光纤,该字符串从对 Fiber::suspend() 的调用返回。完整的代码执行会产生如下输出:

纤维暂停的
价值:纤维用于恢复纤维的价值:测试

这是工作中 PHP Fibers 的准系统教科书示例。这是执行七个异步 GET 请求的另一个 Fibers 示例。

尽管如此,大多数 PHP 开发人员永远不会直接处理 Fibers。RFC 甚至提出了同样的建议:

光纤是大多数用户不会直接使用的高级功能。此功能主要针对库和框架作者,以提供事件循环和异步编程 API。Fibers 允许在任何时候将异步代码执行无缝集成到同步代码中,而无需修改应用程序调用堆栈或添加样板代码。

Fiber API 不应直接在应用程序级代码中使用。Fibers 提供了一个基本的、低级别的流控制 API 来创建更高级别的抽象,然后在应用程序代码中使用这些抽象。

考虑到它的性能优势,您可以期待 PHP 库和框架能够利用这一新功能。看看他们如何在他们的生态系统中实施 Fibers 会很有趣。

只读属性

PHP 8.1 添加了对只读属性的支持。它们只能从它们被声明的作用域初始化一次。初始化后,您永远无法修改它们的值。这样做会引发错误异常。

它的 RFC 写道:

只读属性只能初始化一次,并且只能在声明它的范围内初始化。对该属性的任何其他分配或修改都将导致 Error 异常。

这是一个如何使用它的示例:

class Test {
    public readonly string $kinsta;

    public function __construct(string $kinsta) {
        // 合法的初始化。
        $this->kinsta = $kinsta;
    }
}

一旦初始化,就再也回不去了。将此功能融入 PHP 会大大减少通常用于启用此功能的样板代码。

readonly 属性在类内部和外部都提供了强大的不变性保证。中间运行什么代码并不重要。调用只读属性将始终返回相同的值。

但是,在特定用例中使用 readonly 属性可能并不理想。例如,您只能将它们与类型化属性一起使用,因为没有类型的声明是隐式 null 并且不能是只读的。

此外,设置只读属性不会使对象不可变。readonly 属性将保存相同的对象,但该对象本身可以更改。

此属性的另一个小问题是您无法克隆它。已经有针对此特定用例的解决方法。如有必要,请查看它。

定义最终类常量

从 PHP 8.0 开始,您可以使用其子类覆盖类常量。这是由于在 PHP 中实现继承的方式。

以下是如何覆盖先前声明的常量值的示例:

class Moo
{
    public const M = “moo”;
}

class Meow extends Moo
{
    public const M = “meow”;
}  

现在,如果奶牛想要更严格地控​​制猫的行为(至少在常量方面),他们可以使用 PHP 8.1 的新 final 修饰符来做到这一点。

一旦你将一个常量声明为 final,就意味着。

class Moo
{
    final public const M = “moo”;
}

class Meow extends Moo
{
    public const M = “meow”;
}

// 致命错误:Meow::M 不能覆盖最终常量 Moo::M

您可以在最终的类常量 PHP RFC 中阅读有关它的更多信息。

新的 fsync() 和 fdatasync() 函数

PHP 8.1 添加了两个名为 fsync() 和 fdatasync() 的新文件系统函数。对于那些习惯于 Linux 同名函数的人来说,它们似乎很熟悉。那是因为它们是相关的,只是为 PHP 实现的。

事实上,这一补充早该进行了。PHP 是少数仍未实现 fsync() 和 fdatasync() 的主要编程语言之一——也就是说,直到 PHP 8.1。

fsync() 函数类似于 PHP 现有的 fflush() 函数,但在一个方面有很大不同。fflush() 将应用程序的内部缓冲区刷新到操作系统,而 fsync() 更进一步,确保将内部缓冲区刷新到物理存储。这确保了完整且持久的写入,以便您即使在应用程序或系统崩溃后也可以检索数据。

这是一个如何使用它的示例。

$doc=”kinsta.txt”;

$kin = fopen($doc, ‘ki’);
fwrite($kin, ‘文档信息’);
fwrite($kin, “rn”);
fwrite($kin, ‘更多信息’);

fsync($kin);
fclose($kin);

在最后添加 fsync() 调用可确保 PHP 或操作系统的内部缓冲区中保存的任何数据都被写入存储。在此之前,所有其他代码执行都将被阻止。

其相关函数是fdatasync()。使用它来同步数据,但不一定是元数据。对于元数据不是必需的数据,此函数调用会使写入过程更快一点。

但是,您应该注意 PHP 8.1 尚不完全支持 Windows 上的 fdatasync()。它只是作为 fsync() 的别名。在 POSIX 上,正确实现了 fdatasync()。

新的 array_is_list() 函数

PHP 数组可以保存整数和字符串键。这意味着您可以将它们用于多种用途,包括列表、哈希表、字典、集合、堆栈、队列等等。您甚至可以在数组中包含数组,从而创建多维数组。

您可以有效地检查特定条目是否为数组,但检查它是否有任何缺失的数组偏移量、乱序键等就不是那么容易了。简而言之,您无法快速验证数组是否为列表。

array_is_list() 函数检查数组的键是否从 0 开始按顺序排列,并且没有间隙。如果满足所有条件,它将返回 true。默认情况下,它也为空数组返回 true。

以下是在满足 true 和 false 条件的情况下使用它的几个示例:

// true array_is_list() 示例
array_is_list([]); // true
array_is_list([1, 2, 3]); // true
array_is_list([‘cats’, 2, 3]); // true
array_is_list([‘cats’, ‘dogs’]); // true
array_is_list([0 => ‘cats’, ‘dogs’]); // true
array_is_list([0 => ‘cats’, 1 => ‘dogs’]); // 真的

// false array_is_list() 示例
array_is_list([1 => ‘cats’, ‘dogs’]); // 因为第一个键不是 0
array_is_list([1 => ‘cats’, 0 => ‘dogs’]); // 键乱序
array_is_list([0 => ‘cats’, ‘bark’ => ‘dogs’]); // 非整数键
array_is_list([0 => ‘cats’, 2 => ‘dogs’]); // 键之间的间隙

带有乱序键的 PHP 数组列表是潜在的错误来源。在继续执行代码之前,使用此函数强制严格遵守列表要求是对 PHP 的一个很好的补充。

新的钠 XChaCha20 功能

钠是一个现代的、易于使用的加密库,用于加密、解密、密码散列、签名等。PECL libsodium 包为钠添加了一个包装器,以便 PHP 开发人员可以使用它。

即使是 Facebook、Discord、Malwarebytes 和 Valve 等领先的科技公司也使用 libsodium 来通过快速安全的连接来保护他们的用户。

libsodium 支持 XChaCha20 加密算法对数据进行加密和解密,特别是对流加密。同样,PECL libsodium 扩展已经支持 XChaCha20,但仅支持 Poly1305 消息验证代码。

许多 PHP 应用程序直接使用 XChaCha20 进行流加密。为了让事情变得更简单,从 PHP 8.1 开始,您将拥有三个新函数,无需身份验证即可使用 XChaCha20 加密或解密数据。这种模式称为“分离模式”。

新推出的 XChaCha20 功能是:

  • sodium_crypto_stream_xchacha20_keygen:返回一个安全的随机密钥,用于sodium_crypto_stream_xchacha20。
  • sodium_crypto_stream_xchacha20:将密钥和随机数扩展为伪随机字节的密钥流。
  • sodium_crypto_stream_xchacha20_xor:使用随机数和密钥(无身份验证)加密消息。

此外,在全局命名空间中定义了两个新的 PHP 常量:

  • SODIUM_CRYPTO_STREAM_XCHACHA20_KEYBYTES(分配 32)
  • SODIUM_CRYPTO_STREAM_XCHACHA20_NONCEBYTES(指定为 24)

不过请谨慎使用。由于没有身份验证,解密操作容易受到常见的密文攻击。

您可以在 GitHub 页面上阅读有关其用法和要求的更多信息。

新的 IntlDatePatternGenerator 类

PHP 的底层 ICU 库支持创建本地化的日期和时间格式,但不能完全自定义。

例如,如果您想在 PHP 8.0 之前创建特定于语言环境的数据和时间格式,您可以使用预定义的 IntlDateFormatter 常量以 6 种方式完成:

  • IntlDateFormatter::LONG:更长,例如 2017 年 11 月 10 日或晚上 11:22:33
  • IntlDateFormatter::MEDIUM:短一点,比如 2017 年 11 月 10 日
  • IntlDateFormatter::SHORT:只是数字,比如 10/11/17 或 11:22pm

其中每一个也有自己的 RELATIVE_ 变体,它在当前日期之前或之后的有限范围内设置日期格式。在 PHP 中,值是昨天、今天和明天。

假设您想使用年份的长版本和月份的短版本,例如 10/11/2017。从 PHP 8.0 开始,你不能。

在 PHP 8.1+ 中,您可以使用新的 IntlDatePatternGenerator 类指定日期、月份和时间使用的格式。您可以将这些组件的确切顺序留给格式化程序。

需要注意的是,虽然这个类中只有Date这个词,但它和ICU的DateTimePatternGenerator是一致的。这意味着您还可以使用它来创建灵活的时间格式。为了简化命名,PHP 团队选择使用较短的 IntlDatePatternGenerator 术语。

这是直接来自其 RFC 的示例:

$skeleton = “YYYYMMdd”;

$today = DateTimeImmutable::createFromFormat(‘Ym-d’, ‘2021-04-24’);

$dtpg = new IntlDatePatternGenerator(“de_DE”);
$pattern = $dtpg->getBestPattern($skeleton);
echo “de:”, IntlDateFormatter::formatObject($today, $pattern, “de_DE”), “n”;

$dtpg = new IntlDatePatternGenerator(“en_US”);
$pattern = $dtpg->getBestPattern($skeleton), “n”;
echo “en: “, IntlDateFormatter::formatObject($today, $pattern, “en_US”), “n”;

/*
de: 24.04.2021
en: 04/24/2021
*/

在上面的代码中,骨架变量定义了要使用的特定日期或时间格式。但是,格式化程序处理最终结果的顺序。

支持 AVIF 图像格式

AVIF 或 AV1 图像文件格式,是一种基于 AV1 视频编码格式的相对较新的免版税图像格式。除了提供更高的压缩率(因此文件更小)之外,它还支持多种功能,例如透明度、HDR 等。

AVIF 格式最近才标准化(2021 年 6 月 8 日)。这为 Chrome 85+ 和 Firefox 86+ 等浏览器增加了对 AVIF 图像的支持铺平了道路。

PHP 8.1 的图像处理和 GD 扩展增加了对 AVIF 图像的支持。

但是,要包含此功能,您需要编译具有 AVIF 支持的 GD 扩展。您可以通过运行以下命令来执行此操作。

对于 Debian/Ubuntu:

apt 安装 libavif-dev

对于 Fedora/RHEL:

dnf 安装 libavif-devel

这将安装所有最新的依赖项。接下来,您可以通过使用 ./configure 脚本运行 –with-avif 标志来编译 AVIF 支持。

./buildconf –force
./configure –enable-gd –with-avif

如果您要从头开始一个新环境,您还可以在此处启用其他 PHP 扩展。

安装后,您可以通过在 PHP 终端中运行以下命令来测试是否启用了 AVIF 支持:

php -i | grep AVIF

如果您已正确安装 AVIF,您将看到以下结果:

AVIF 支持 => 启用

您还可以使用 gd_info() 调用来检索 GD 功能列表,包括是否启用了 AVIF 支持功能。

这个更新的 PHP 8.1 GD 扩展还添加了两个用于处理 AVIF 图像的新函数:imagecreatefromavif 和 imageavif。它们的工作方式与 JPEG 和 PNG 对应物类似。

imagecreatefromavif 函数从给定的 AVIF 图像返回一个 GdImage 实例。然后,您可以使用此实例来编辑或转换图像。

另一个 imageavif 函数输出 AVIF 图像文件。例如,您可以使用它将 JPEG 转换为 AVIF:

$image = imagecreatefromjpeg(‘image.jpeg’);
imageavif($image, ‘image.avif’);

您可以在其 GitHub 页面上阅读有关此新功能的更多信息。

新的 $_FILES:目录上传的 full_path 键

PHP 维护了大量预定义变量来跟踪各种事物。其中之一是 $_FILES 变量保存通过 HTTP POST 方法上传的项目的关联数组。

大多数现代浏览器都支持使用 HTML 文件上传字段上传整个目录。甚至 PHP <8.1 也支持此功能,但有一个很大的警告。您无法上传具有确切目录结构或相对路径的文件夹,因为 PHP 没有将此信息传递给 $_FILES 数组。

这在 PHP 8.1 中发生了变化,在 $_FILES 数组中添加了一个名为 full_path 的新键。使用这些新数据,您可以在服务器上存储相对路径或复制确切的目录结构。

您可以通过使用 var_dump($_FILES); 输出 $FILES 数组来测试此信息。命令。

但是,如果您正在使用此功能,请谨慎操作。确保您防范标准文件上传攻击。

对字符串键控数组的数组解包支持

PHP 7.4 添加了对使用数组展开运算符 (…) 进行数组解包的支持。它可以作为使用 array_merge() 函数的更快替代方法。但是,此功能仅限于数字键数组,因为在合并具有重复键的数组时,解包字符串键数组会导致冲突。

但是,PHP 8 增加了对命名参数的支持,消除了这个限制。因此,数组解包现在也支持使用相同语法的字符串键数组:

$array = […$array1, …$array2];

这个 RFC 示例说明了如何在 PHP 8.1 中处理合并具有重复字符串键的数组:

$array1 = [“a” => 1];
$array2 = [“a” => 2];
$array = [“a” => 0, …$array1, …$array2];
var_dump($array); // [“a” => 2]

在这里,字符串键“a”在通过数组解包合并之前出现了三次。但只有属于 $array2 的最后一个值获胜。

显式八进制数字表示法

PHP 支持各种数字系统,包括十进制 (base-10)、二进制 (base-2)、八进制 (base-8) 和十六进制 (base-16)。十进制数字系统是默认值。

如果你想使用任何其他数字系统,那么你必须在每个数字前加上一个标准前缀:

  • 十六进制:0x 前缀。(例如 17 = 0x11)
  • 二进制:0b 前缀。(例如 3 = 0b11)
  • 八进制:0 前缀。(例如 9 = 011)

您可以看到八进制数字系统的前缀与其他系统的前缀有何不同。为了标准化这个问题,许多编程语言增加了对显式八进制数字符号的支持:0o 或 0O。

从 PHP 8.1 开始,您可以在八进制数字系统中将上述示例(即以 10 为基数的数字 9)编写为 0o11 或 0O11。

0o16 === 14; // 真
0o123 === 83; // 真的

0O16 === 14; // 真
0O123 === 83; // 真的

016 === 0o16;// 真
016 === 0O16; // 真的

此外,这个新特性也适用于 PHP 7.4 中引入的下划线数字文字分隔符。

在其 RFC 中阅读有关此新 PHP 8.1 功能的更多信息。

MurmurHash3 和 xxHash 哈希算法支持

PHP 8.1 添加了对 MurmurHash3 和 xxHash 散列算法的支持。它们不是为加密用途而设计的,但它们仍然提供令人印象深刻的输出随机性、分散性和唯一性。

这些新的散列算法比大多数 PHP 现有的散列算法都快。事实上,其中一些散列算法的变体比 RAM 吞吐量更快。

订阅时事通讯

想知道我们是如何将流量增加超过 1000% 的吗?

加入 20,000 多名其他人,他们会收到我们的每周时事通讯,其中包含 WordPress 内幕技巧!

现在订阅

由于 PHP 8.1 还增加了对声明特定于算法的 $options 参数的支持,您可以对这些新算法执行相同的操作。这个新参数的默认值为 []。因此,它不会影响我们现有的任何哈希函数。

您可以在他们的 GitHub 页面上阅读有关这些 PHP 8.1 新功能的更多信息:MurmurHash3、xxHash、特定于算法的 $options。

DNS-over-HTTPS (DoH) 支持

DNS-over-HTTPS (DoH) 是一种通过 HTTPS 协议进行 DNS 解析的协议。DoH 使用 HTTPS 加密客户端和 DNS 解析器之间的数据,通过防止中间人攻击来提高用户隐私和安全性。

从 PHP 8.1 开始,您可以使用 Curl 扩展来指定 DoH 服务器。它需要使用 libcurl 7.62+ 版本编译 PHP。对于大多数流行的操作系统(包括 Linux 发行版)来说,这不是问题,因为它们通常包含 Curl 7.68+。

您可以通过指定 CURLOPT_DOH_URL 选项来配置 DoH 服务器 URL。

$doh = curl_init(‘https://kinsta.com’);
curl_setopt($doh, CURLOPT_DOH_URL, ‘https://dns.google/dns-query’);
curl_exec($doh);

在上面的示例中,我们使用了 Google 的公共 DNS 服务器。另外,请注意在所有使用的 URL 中使用 https://。确保完美地配置它,因为在 Curl 中没有可以回退的默认 DNS 服务器。

您还可以从 Curl 文档中包含的公共 DoH 服务器列表中进行选择。

此外,Curl 文档的 CURLOPT_DOH_URL 参考解释了如何彻底使用其各种参数。

使用 CURLStringFile 从字符串上传文件

PHP Curl 扩展支持带有文件上传的 HTTP(S) 请求。它使用 CURLFile 类来实现这一点,该类接受 URI 或文件路径、mime 类型和最终文件名。

但是,使用 CURLFile 类,您只能接受文件路径或 URI,而不能接受文件本身的内容。如果您已经将文件上传到内存中(例如处理过的图像、XML 文档、PDF),您必须使用 data:// URI 和 Base64 编码。

但是 libcurl 已经支持一种更简单的方式来接受文件的内容。新的 CURLStringFile 类增加了对此的支持。

您可以阅读其 GitHub 页面以了解有关如何在 PHP 8.1 中实现的更多信息。

新的 MYSQLI_REFRESH_REPLICA 常量

PHP 8.1 的 mysqli 扩展添加了一个名为 MYSQLI_REFRESH_REPLICA 的新常量。它等效于现有的 MYSQLI_REFRESH_SLAVE 常量。

这个变化在 MySQL 8.0.23 中受到欢迎,以解决技术词汇中的种族不敏感问题(最常见的例子包括“奴隶”和“主人”)。

您应该注意到旧的常量没有被删除或弃用。开发人员和应用程序可以继续使用它。对于希望抛弃此类术语的开发人员和公司而言,此新常量只是一种选择。

使用继承缓存提高性能

继承缓存是 opcache 的新增功能,可消除 PHP 类继承开销。

PHP 类由 opcache 单独编译和缓存。但是,它们已经在运行时针对每个请求进行了链接。这个过程可能涉及几个兼容性检查和从父类和特征借用方法/属性/常量。

因此,即使每个请求的结果都相同,这仍需要相当长的时间来执行。

继承缓存链接所有唯一的依赖类(父类、接口、特征、属性类型、方法)并将结果存储在 opcache 共享内存中。由于这种情况现在只发生一次,因此继承需要较少的指令。

此外,它消除了对不可变类的限制,例如未解析的常量、类型化属性和协变类型检查。因此,存储在 opcache 中的所有类都是不可变的,进一步减少了所需的指令数量。

总而言之,它有望带来显着的性能优势。这个补丁的作者 Dimitry Stogov 发现它在基础 Symfony “Hello, World!” 上有 8% 的改进。程序。我们迫不及待地想在我们的以下 PHP 基准测试中测试它。

一流的可调用语法

PHP 8.1 添加了一流的可调用语法来取代使用字符串和数组的现有编码。除了创建更清晰的闭包之外,静态分析工具还可以访问这种新语法并尊重声明的范围。

以下是一些取自 RFC 的示例:

$fn = Closure::fromCallable(‘strlen’);
$fn = strlen(…);

$fn = Closure::fromCallable([$this, ‘method’]);
$fn = $this->method(…)

$fn = Closure::fromCallable([Foo::class, ‘method’]);
$fn = Foo::method(…);

这里,所有的表达式对都是等价的。三点 (…) 语法类似于参数解包语法 (…$args)。除了这里,参数尚未填写。

PHP 8.1 中的变化

PHP 8.1 还包括对其现有语法和功能的更改。让我们来讨论它们:

PHP Interactive Shell 需要 readline 扩展

PHP 的 readline 扩展支持交互式 shell 功能,例如导航、自动完成、编辑等。虽然它与 PHP 捆绑在一起,但默认情况下并未启用。

您可以使用 PHP CLI 的 -a 命令行选项访问 PHP 交互式 shell:

php -a

交互式外壳

php >
php > echo “你好”;
你好
php > function test() {
php { echo “Hello”;
php { }
php > 测试();
你好

在 PHP 8.1 之前,即使没有启用 readline 扩展,您也可以使用 PHP CLI 打开交互式 shell。正如预期的那样,shell 的交互功能不起作用,使得 -a 选项毫无意义。

在 PHP 8.1 CLI 中,如果您没有启用 readline 扩展,交互式 shell 会退出并显示错误消息。

php -a
交互式 shell (-a) 需要 readline 扩展。

MySQLi 默认错误模式设置为异常

在 PHP 8.1 之前,MySQLi 默认为静默错误。这种行为通常会导致代码不遵循严格的错误/异常处理。开发人员必须实现自己的显式错误处理功能。

PHP 8.1 通过将 MySQLi 的默认错误报告模式设置为抛出异常来更改此行为。

致命错误:未捕获的 mysqli_sql_exception:连接被拒绝…:…

由于这是一个重大更改,对于 PHP <8.1 版本,您应该在建立第一个 MySQLi 连接之前使用 mysqli_report 函数显式设置错误处理模式。或者,您可以通过实例化 mysqli_driver 实例来选择错误报告值来执行相同的操作。

RFC 遵循 PHP 8.0 中引入的类似更改。

CSV 写入功能的可定制行尾

在 PHP 8.1 之前,PHP 的内置 CSV 写入函数 fputcsv 和 SplFileObject::fputcsv 被硬编码为在每行末尾添加 n(或换行符)。

PHP 8.1 为这些函数添加了对名为 eol 的新参数的支持。您可以使用它来传递可配置的行尾字符。默认情况下,它仍然使用 n 字符。因此,您可以继续在现有代码中使用它。

标准字符转义规则适用于使用行尾字符。如果要将 r、n 或 rn 用作 EOL 字符,则必须将它们括在双引号中。

这是跟踪此新更改的 GitHub 页面。

新 version_compare 运算符限制

PHP 的 version_compare() 函数比较两个版本号字符串。此函数接受称为 operator 的可选第三个参数来测试特定关系。

尽管文档中没有明确说明,但在 PHP 8.1 之前,您可以将此参数设置为部分值(例如 g、l、n)而不会出现错误。

PHP 8.1 对 version_compare() 函数的 operator 参数添加了更严格的限制以克服这种情况。您现在可以使用的唯一运算符是:

需要一个为您提供竞争优势的托管解决方案?Kinsta 为您提供令人难以置信的速度、最先进的安全性和自动缩放功能。查看我们的计划

  • ==、= 和 eq
  • !=、<> 和 ne
  • > 和 gt
  • >= 和 ge
  • < 和 lt
  • <= 和 le

没有更多的部分运算符值。

ENT_QUOTES | ENT_SUBSTITUTE 用于 HTML 编码和解码功能

HTML 实体是字符的文本表示,否则会被解释为 HTML。想想诸如 < 和 > 之类的用于定义 HTML 标签的字符(例如 <a>、<h3>、<script>)。

< 的 HTML 实体是 <(小于符号),> 是 >(大于符号)。您可以在 HTML 文档中安全地使用这些 HTML 实体,而无需触发浏览器的渲染引擎。

例如,<script> 将在浏览器中显示为 <script>,而不是被解释为 HTML 标签。

在 PHP 8.1 之前, htmlspecialchars() 和 htmlentities() 函数将“、<、> 和 &”等符号转换为它们各自的 HTML 实体。但默认情况下,他们没有将单引号字符 (‘) 转换为其 HTML 实体。此外,如果文本中存在格式错误的 UTF-8,它们将返回一个空字符串。

在 PHP 8.1. 中,这些 HTML 编码和解码函数(及其相关函数)也会默认将单引号字符转换为它们的 HTML 实体。

如果给定的文本包含无效字符,函数将用 Unicode 替换字符 ( ) 替换它们,而不是返回空字符串。PHP 8.1 通过将这些函数的签名更改为 ENT_QUOTES | 来实现这一点。默认情况下是 ENT_SUBSTITUTE 而不是 ENT_COMPAT。

大多数框架已经使用 ENT_QUOTES 作为默认标志值。因此,由于此更改,您不会看到太大差异。然而,新的 ENT_SUBSTITUTE 标志并没有被广泛使用。PHP 8.1 将导致无效的 UTF-8 字符被替换为 字符而不是返回空字符串。

关于非法紧凑函数调用的警告

PHP 的 compact() 函数非常方便。您可以使用它来创建一个数组,其中包含使用名称和值的变量。

例如,考虑以下代码:

$animal=”猫”;
$sound = ‘喵’;
$region = ‘伊斯坦布尔’;
紧凑(’动物’,’声音’,’区域’);
// [‘animal’ => “Cat”, ‘sound’ => “Meow”, ‘region’ => “Istanbul”]

紧凑函数的文档说明它只接受字符串参数或带有字符串值的数组值。但是,在 PHP 7.3 之前,任何未设置的字符串都将被悄悄跳过。

PHP 7.3 修改了 compact() 函数以在您使用未定义的变量时抛出一个通知。PHP 8.1 更进一步,并发出警告。

您可以阅读其 GitHub 页面以了解此更改是如何发生的。

从资源到类对象的新迁移

PHP 的长期目标之一是从资源转向标准类对象。

由于历史原因,资源对象在 PHP 应用程序中被广泛使用。因此,资源向类对象的迁移需要尽可能减少破坏性。PHP 8.1 迁移了五个这样的资源:

file_info 资源迁移到 finfo 对象

PHP 的 finfo 类为 fileinfo 函数提供了一个面向对象的接口。但是,使用 finfo 函数返回具有 file_info 类型的资源对象,而不是 finfo 类本身的实例。

PHP 8.1 修复了这个异常。

IMAP 资源到 IMAPConnection 类对象

根据资源到对象的迁移目标,当 PHP 最终修改类的实现细节时,新的 IMAPConnection 类最大限度地减少了潜在的破坏性更改。

这个新类也被声明为 final,所以你不能扩展它。

在其 GitHub 页面上阅读有关其实现的更多信息。

FTP 连接资源是 FTPConnection 类对象

在 PHP <8.1 中,如果您使用 ftp_connect() 或 ftp_ssl_connect() 函数创建 FTP 连接,您将返回一个 ftp 类型的资源对象。

PHP 8.1 添加了新的 FTPConnection 类来解决这个问题。和 IMAPConnection 类一样,它也被声明为 final 以防止它被扩展。

在其 GitHub 页面上阅读有关其实现的更多信息。

字体标识符是 GdFont 类对象

PHP 的 GD 扩展提供了 imageloadfont() 函数来加载用户定义的位图并返回其字体标识符资源 ID(一个整数)。

在 PHP 8.1 中,此函数将改为返回 GdFont 类实例。此外,为了使迁移轻松自如,以前从 imageloadfont() 接受资源 ID 的所有函数现在都将采用新的 GdFont 类对象。

在其 GitHub 页面上阅读有关此迁移的更多信息。

LDAP 资源迁移到对象

LDAP 或轻量级目录访问协议,用于访问“目录服务器”。就像硬盘目录结构一样,它是一个独特的数据库,以树状结构保存数据。

PHP 包含一个 LDAP 扩展,它接受或返回 PHP 8.1 之前的资源对象。但是,它们现在都已无缝迁移到新的类实例。已经过渡的资源类型有:

  • ldap 链接资源到 LDAPConnection 类对象
  • ldap 结果资源到 LDAPResult 类对象
  • ldap 结果条目资源到 LDAPResultEntry 类对象

浏览其 GitHub 页面以更好地了解此迁移。

Pspell 资源现在是类对象

PHP 的 Pspell 扩展允许您检查拼写和单词建议。

PHP <8.1 使用带有整数标识符的 pspell 和 pspell 配置资源对象类型。这两个资源对象现在替换为 PSpellDictionary 和 PSpellConfig 类对象。

与之前的迁移一样,之前接受或返回资源对象标识符的所有 Pspell 函数都将采用新的类对象实例。

有关更多信息,请参阅其 GitHub 页面。

PHP 8.1 中的弃用

PHP 8.1 弃用了许多以前的功能。以下列表简要概述了 PHP 8.1 弃用的功能:

不能将 null 传递给不可为 Null 的内部函数参数

从 PHP 8.0 开始,它的内部函数即使对于不可为 null 的参数也默默地接受 null 值。这不适用于用户定义的函数——它们只接受可空参数的 null。

例如,考虑这种用法:

var_dump(str_contains(“foobar”, null));
//布尔(真)

此处,空值被静默转换为空字符串。因此,结果返回 true。

该 RFC 旨在通过在 PHP 8.1 中抛出弃用警告来同步内部函数的行为。

var_dump(str_contains(“foobar”, null));
// 不推荐使用:不推荐将 null 传递给字符串类型的参数

在下一个主要的 PHP 版本(即 PHP >=9.0)中,弃用将成为 TypeError,使内部函数的行为与用户定义的函数保持一致。

受限制的 $GLOBALS 使用

PHP 的 $GLOBALS 变量提供对其内部符号表的直接引用。支持此功能很复杂,并且会影响阵列操作性能。另外,它很少被使用。

根据 RFC,不再允许间接修改 $GLOBALS。此更改向后不兼容。

这种变化的影响相对较低:

在前 2k 个 composer 包中,我发现了 23 个使用 $GLOBALS 而不直接取消引用它的案例。根据粗略的检查,只有两种情况没有以只读方式使用 $GLOBALS。

但是,$GLOBALS 的只读用法继续照常工作。不再支持写入整个 $GLOBALS。因此,您可以预期性能会有轻微的提升,尤其是在使用普通 PHP 数组时。

内部函数的返回类型声明

PHP 8.0 允许开发人员为大多数内部函数和方法声明参数和返回类型。这要归功于各种 RFC,例如内部函数的一致类型错误、联合类型 2.0 和混合类型 v2。

但是,在很多情况下可能会丢失类型信息。其中一些包括具有资源的类型、通过引用传递的参数、非最终方法的返回类型以及不根据一般规则解析参数的函数或方法。您可以在其 RFC 中阅读确切的详细信息。

此 RFC 仅解决非最终方法的返回类型的问题。然而,PHP 团队并没有立即完全淘汰它,而是提供了一个渐进的迁移路径,以使用相关的方法返回类型更新您的代码库。

非最终的内部方法返回类型(如果可能)在 PHP 8.1 中暂时声明,它们将在 PHP 9.0 中强制执行。这意味着在 PHP 8.x 版本中,当内部方法以返回类型不兼容的方式被覆盖时,在继承检查期间会引发“弃用”通知,而 PHP 9.0 会使这些成为致命错误。

如果您在更新到 PHP 8.1 后看到此弃用通知,请确保更新您的方法的返回类型。

不推荐使用可序列化接口

PHP 7.4 引入了自定义对象序列化机制和两个新的魔法方法:__serialize() 和 __unserialize()。这些新方法旨在最终替换损坏的 Serializable 接口。

该 RFC 提议通过制定最终删除 Serializable 的计划来最终确定该决定。

在 PHP 8.1 中,如果你实现了 Serializable 接口而没有实现 __serialize() 和 __unserialize() 方法,PHP 会抛出“Deprecated”警告。

不推荐使用:不推荐使用 Serializable 接口。代替(或者另外,如果需要支持旧的 PHP 版本)在 … 在线 …

如果您支持 PHP <7.4 和 PHP >=7.4,您应该同时实现 Serializable 接口和新的魔法方法。在 PHP >=7.4 版本上,魔术方法将优先。

不兼容的 float 到 int 转换已弃用

PHP 是一种动态类型语言。因此,在很多情况下自然会发生类型强制。大多数这些强制是无害的,而且超级方便。

但是,当浮点数转换为整数时,通常会涉及数据丢失。例如,当浮点数 3.14 转换为整数 3 时,它会丢失其小数值。

当浮点数超出平台整数范围或浮点数字符串转换为整数时,也会发生同样的情况。

PHP 8.1 纠正了这种行为,并使其动态类型强制更符合大多数现代编程语言。目标是使这种强制可预测和直观。

在 PHP 8.1 中,当不兼容的 float 被隐式强制转换为 int 时,您将看到弃用通知。但是什么构成了整数兼容的浮点数?RFC 对此做出了回答:

如果具有以下特征,则称浮点数与整数兼容:

  • 是一个数字(即不是 NaN 或无穷大)
  • 在 PHP 整数范围内(取决于平台)
  • 没有小数部分

此弃用通知将在下一个主要 PHP 版本(即 PHP 9.0)中升级为 TypeError。

mysqli::get_client_info 方法和 mysqli_get_client_info($param) 已弃用

MySQL 客户端 API 定义了两个常量:client_info(一个字符串)和 client_version(一个 int)。MySQL Native Driver (MySQLnd) 是官方 PHP 源代码的一部分,并将这些常量与 PHP 版本挂钩。在 libmysql 中,它们代表编译时的客户端库版本。

在 PHP 8.1 之前,mysqli 以 4 种方式公开这些常量:mysqli_driver 属性、mysqli 属性、mysqli_get_client_info() 函数和 mysqli::get_client_info 方法。但是,client_version 没有方法。

MySQLnd 以两种方式向 PHP 公开这些常量:常量和函数调用。为了统一使用这两个选项的 mysqli 访问方法,PHP 8.1 弃用了其他两个选项:

  • mysqli 类中的 get_client_info 方法。相反,您可以只使用 mysqli_get_client_info() 函数。
  • mysqli_get_client_info() 函数与参数。不带任何参数调用函数以避免弃用通知。

在其 GitHub 页面上阅读有关此弃用的更多信息。

mhash*() 函数(散列扩展)已弃用

PHP 5.3 将 mhash*() 函数集成到 ext/hash 作为 ext/mhash 的兼容层。后来,PHP 7.0 删除了 ext/mhash。

与 hash_*() 函数不同,mhash*() 函数并不总是可用。您必须在配置 PHP 时单独启用它们。

在 PHP 7.4 中,散列扩展与 PHP 捆绑在一起,使其成为 PHP 的默认扩展。但是,出于兼容性原因,它仍然支持启用 –enable-mhash 选项。

PHP 团队决定在 PHP 8.1 中弃用 mhash*() 函数,并在 PHP 9.0 中完全删除它们。不推荐使用的函数是 mhash()、mhash_keygen_s2k()、mhash_count()、mhash_get_block_size() 和 mhash_get_hash_name()。您可以使用标准的 ext/hash 功能代替它们。

filter.default 和 filter.default_options INI 设置已弃用

PHP 的 filter.default INI 设置允许您将过滤器应用于所有 PHP 超级全局变量 — 即 GPCRS 数据($_GET、$_POST、$_COOKIE、$_REQUEST 和 $_SERVER)。

例如,您可以设置 filter.default=magic_quotes 或 filter.default=add_slashes(基于 PHP 版本)来复活 PHP 有争议且不安全的魔术引号功能(在 PHP 5.4 中删除)。

filter.default INI 设置通过允许更多过滤器来提供附加功能,使其更糟。例如,它的另一个选项 — filter.default=special_chars — 仅对 HTML 启用魔术引号。对这些设置的了解要少得多。

如果 filter.default 设置为 unsafe_raw(默认值)以外的任何值,PHP 8.1 将抛出弃用警告。您不会看到单独的 filter.default_options 弃用通知,但 PHP 9.0 将删除这两个 INI 设置。

作为替代方案,您可以开始使用 filter_var() 函数。它使用指定的过滤器过滤变量。

在 false 上弃用 autovivification

PHP 允许自动激活(从假值自动创建数组)。如果变量未定义,此功能非常有用。

尽管如此,当值为 false 或 null 时自动创建数组并不理想。

此 RFC 不允许从错误值自动激活。但是,请注意,仍然允许来自未定义变量和 null 的自动激活。

在 PHP 8.1 中,附加到 false 类型的变量将发出弃用通知:

已弃用:不推荐将 false 自动转换为数组

PHP 9.0 同样会抛出致命错误,这与其他标量类型相同。

不推荐使用 mysqli_driver->driver_version 属性

MySQLi 扩展的 mysqli_driver->driver_version 属性已经 13 年没有更新了。尽管此后对驱动程序进行了许多更改,但它仍然返回旧的驱动程序版本值,使该属性变得毫无意义。

在 PHP 8.1 中,不推荐使用 mysqli_driver->driver_version 属性。

其他小改动

PHP 8.1 中有更多的弃用。将它们全部列出在这里将是一项令人筋疲力尽的工作。我们建议您直接查看 RFC 以了解这些较小的弃用情况。 4

PHP 的 GitHub 页面还包括一​​个 PHP 8.1 升级说明指南。它列出了在升级到 PHP 8.1 之前您应该考虑的所有重大更改。

概括

PHP 8.1 离我们不远了。并且它已经承诺将其前身提升一倍,这是一项不小的壮举。当它启动时,您可以放心,Kinsta 将支持 PHP 8.1 用于实时和登台环境(包括在 DevKinsta 上)。

我们认为最令人兴奋的 PHP 8.1 特性是枚举、纤维、纯交集类型及其许多性能改进。我们迫不及待地想让 PHP 8.1 步入正轨,并对各种 PHP 框架和 CMS 进行基准测试。

请务必将此博文加入书签,以备将来参考。

您最喜欢哪个 PHP 8.1 功能?在下面的评论部分与社区分享您的想法。

通过以下方式节省时间、成本并最大限度地提高站点性能:

  • 来自 WordPress 托管专家的即时帮助,24/7。
  • Cloudflare 企业集成。
  • 全球受众覆盖全球 28 个数据中心。
  • 使用我们内置的应用程序性能监控进行优化。

所有这些以及更多,在一个没有长期合同、协助迁移和 30 天退款保证的计划中。查看我们的计划或与销售人员交谈以找到适合您的计划。

相关文章