PHP 8.1有哪些变化:新特性、改变及弃用等

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

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

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

在本文中,我们将详细介绍PHP 8.1将带来哪些新的东西。从其新特性和性能改进到重大更改和弃用,我们将深入介绍它们。

  1. PHP 8.1中的新特性
  2. PHP 8.1中的变化
  3. PHP 8.1中的弃用
  4. 其他小改动

PHP 8.1中的新特性

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

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

  1. 纯交集类型
  2. 枚举
  3. 永不返回类型
  4. Fibers
  5. 新的只读属性
  6. 定义最终类常量
  7. 新的fsync()和fdatasync()函数
  8. 新的array_is_list()函数
  9. 新的Sodium XChaCha20函数
  10. 新的IntlDatePatternGenerator类
  11. 支持AVIF图像格式
  12. 新的$_FILES:目录上传的full_path键
  13. 对字符串键控数组的数组解包支持
  14. 显式八进制数字表示法
  15. MurmurHash3和xxHash哈希算法支持
  16. DNS-over-HTTPS (DoH) 支持
  17. 使用CURLStringFile从字符串上传文件
  18. 新的MYSQLI_REFRESH_REPLICA常量
  19. 使用继承缓存提高性能
  20. 一流的可调用语法

纯交集类型

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

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

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

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

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

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

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

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

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

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

复制
class A {
private Traversable&Countable $countableIterator;
public function 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终于添加了对枚举(也称为枚举或枚举类型)的支持。它们是用户定义的数据类型,由一组可能的值组成。

编程语言中最常见的枚举示例是布尔类型,具有truefalse两个可能的值。它是如此普遍,以至于它融入了许多现代编程语言。

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

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

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

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

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

复制
enum Suit {
case Hearts;
case Diamonds;
case Clubs;
case Spades;
}

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

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

上面提到的枚举称为Pure Enums。

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

复制
enum Suit: string {
case Hearts = 'H';
case Diamonds = 'D';
case Clubs = 'C';
case Spades = 'S';
}

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

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

never 返回类型

PHP 8.1添加了一个名为never. 在 alwaysthrowexit.

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

复制
function redirect(string $uri): never {
header('Location: ' . $uri);
exit();
}
function redirectToLoginPage(): never {
redirect('/login');
}

一个never-declared函数应满足三个条件:

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

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

never返回类型共享与许多相似之处void返回类型。它们都确保函数或方法不返回值。但是,它的不同之处在于执行更严格的规则。例如,void-declared 函数仍然可以return没有显式值,但您不能对never-declared 函数执行相同的操作。

根据经验,void当您希望 PHP 在函数调用后继续执行时,请继续执行。never当你想要相反的时候就去吧。

此外,never定义为“bottom”类型。因此,任何声明的类方法never都“never”不能将其返回类型更改为其他类型。但是,您可以使用void-declared 方法扩展never-declared 方法。

info: 原始RFC将never返回类型列为noreturn,这是两个PHP静态分析工具(即Psalm和PHPStan)已经支持的返回类型。由于这是由Psalm和PHPStan的作者自己提出的,因此他们保留了其术语。但是,由于命名约定,PHP团队对noreturn vs never进行了民意调查,never最终成为永远的赢家。因此,对于PHP 8.1+版本,始终使用never代替noreturn.

Fibers

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

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

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

Fibers是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();
echo "Value from fiber suspending: ", $value, "\n";
$fiber->resume('test');

你在上面的代码中创建了一个“fiber”,并立即用字符串挂起它fiber。该echo声明用作fiber恢复的视觉提示。

您可以通过调用$fiber->start()检索此字符串值。

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

复制
Value from fiber suspending: fiber
Value used to resume fiber: test

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

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

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

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

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

新的readonly属性

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

RFC写道:

一个只读属性只能使用一次,并且只能从已申报范围的初始化。对该属性的任何其他分配或修改都将导致Error异常。

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

复制
class Test {
public readonly string $kinsta;
public function __construct(string $kinsta) {
// Legal initialization.
$this->kinsta = $kinsta;
}
}

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

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

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

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

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

定义final类常量

从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";
}
// Fatal error: Meow::M cannot override final constant 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, 'doc info');
fwrite($kin, "\r\n");
fwrite($kin, 'more info');
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空数组。

以下是在同时满足truefalse条件的情况下使用它的几个示例:

复制
// true array_is_list() examples
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']); // true 
// false array_is_list() examples 
array_is_list([1 => 'cats', 'dogs']); // as first key isn't 0
array_is_list([1 => 'cats', 0 => 'dogs']); // keys are out of order
array_is_list([0 => 'cats', 'bark' => 'dogs']); // non-integer keys
array_is_list([0 => 'cats', 2 => 'dogs']); // gap in between keys

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

新的Sodium XChaCha20函数

Sodium是一个现代的、易于使用的加密库,用于加密、解密、密码散列、签名等。该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:更长,例如November 10, 2017 or 11:22:33pm
  • IntlDateFormatter::MEDIUM:短一点,比如November 10, 2017
  • IntlDateFormatter::SHORT:只是数字,比如10/11/17 or 11:22pm

其中每一个也有自己的RELATIVE_变体,将日期格式设置在当前日期之前或之后的有限范围内。在 PHP 中,值是yesterday, today和tomorrow。

假设您想使用年份的长版本和月份的短版本,例如10/11/2017。但在PHP 8.0,你不能这样做。

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

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

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

复制
$skeleton = "YYYYMMdd";
$today = \DateTimeImmutable::createFromFormat('Y-m-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
*/

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

支持AVIF图像格式

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

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

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

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

对于Debian/Ubuntu:

复制
apt install libavif-dev

对于Fedora/RHEL:

复制
dnf install libavif-devel

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

复制
./buildconf --force
./configure --enable-gd --with-avif

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

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

复制
php -i | grep AVIF

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

复制
AVIF Support => enabled

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

这个更新的PHP 8.1 GD扩展还添加了两个用于处理AVIF图像的新函数:imagecreatefromavifimageavif. 它们的工作方式与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.1full_path$_FILES阵列。使用这些新数据,您可以在服务器上存储相对路径或复制确切的目录结构。

您可以通过$FILES使用var_dump($_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

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

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

复制
0o16 === 14; // true
0o123 === 83; // true
0O16 === 14; // true
0O123 === 83; // true
016 === 0o16; // true
016 === 0O16; // true

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

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

MurmurHash3和xxHash哈希算法支持

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

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

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

您可以在他们的GitHub页面上阅读有关这些PHP 8.1新功能的更多信息:MurmurHash3、xxHash、Algorithm-specific $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://www.wbolt.com');
curl_setopt($doh, CURLOPT_DOH_URL, 'https://dns.google/dns-query');
curl_exec($doh);

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

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

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

使用CURLStringFile从字符串上载文件

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

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

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

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

新的MYSQLI_REFRESH_REPLICA常数

PHP 8.1的mysqli扩展添加了一个名为MYSQLI_REFRESH_REPLICA. 它相当于现有的MYSQLI_REFRESH_SLAVE常数。

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

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

使用继承缓存提高性能

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

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

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

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

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

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

First-Class可调用语法

PHP 8.1添加了First-Class可调用语法来取代使用字符串和数组的现有编码。除了创建更干净的Closure 之外,静态分析工具也可以访问这种新语法并尊重声明的范围。

以下是一些取自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 还包括对其现有语法和功能的更改。让我们来讨论它们:

  1. PHP Interactive Shell需要readline扩展
  2. MySQLi默认错误模式设置为异常
  3. CSV写入函数的可定制行尾
  4. 新version_compare运算符限制
  5. HTML编码和解码函数现在使用ENT_QUOTES | ENT_SUBSTITUTE
  6. 关于非法紧凑函数调用的警告
  7. 新的从资源到类对象的迁移

PHP Interactive Shell需要readline扩展

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

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

复制
php -a
Interactive shell
php >
php > echo "Hello";
Hello
php > function test() {
php { echo "Hello";
php { }
php > test();
Hello

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

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

复制
php -a
Interactive shell (-a) requires the readline extension.

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

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

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

复制
Fatal error: Uncaught mysqli_sql_exception: Connection refused in ...:...

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

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

CSV写入函数的可定制行尾

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

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

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

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

新的 version_compare 运算符限制

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

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

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

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

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

HTML编码和解码函数改用 ENT_QUOTES | ENT_SUBSTITUTE

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

HTML实体<& lt;(小于符号)和>& gt;(大于符号)。

注意:删除“&”和“amp”之间的空格。

您可以在HTML文档中安全地使用这些HTML实体,而无需触发浏览器的渲染引擎。

例如,& lt;script& gt;将在浏览器中显示为<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 = 'Cat';
$sound = 'Meow';
$region = 'Istanbul';
compact('animal', 'sound', 'region');
// ['animal' => "Cat", 'sound' => "Meow", 'region' => "Istanbul"]

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

PHP7.3修改了compact()函数,以便在使用未定义变量时发出通知。PHP 8.1更进一步,并发出警告。

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

新的从资源到类对象迁移

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

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

file_info资源迁移到finfo对象

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

PHP 8.1修复了这个异常。

迁移到IMAP\Connection类对象的IMAP资源

根据资源到对象的迁移目标,当PHP最终修改类的实现细节时,新的IMAP\Connection类将潜在的破坏性更改降至最低。

这个新类也被声明final,所以你不被允许extend

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

FTP连接资源现在为FTP\Connection类对象

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

PHP 8.1添加了新的FTP\Connection类来纠正这个问题。和IMAP\Connection类一样,它也被声明final为防止它被扩展。

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

字体标识符迁移到GdFont类对象

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

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

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

LDAP资源迁移到对象

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

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

  • ldap link资源到\LDAP\Connection类对象
  • ldap result资源到\LDAP\Result类对象
  • ldap result entry资源到\LDAP\ResultEntry类对象

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

Pspell资源现在为类对象

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

PHP<8.1使用pspellpspell config带有整数标识符的资源对象类型。这两个资源对象现在替换为PSpell\DictionaryPSpell\Config类对象。

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

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

PHP 8.1中的弃用

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

  1. 不能将null传递给不可为Null的内部函数参数
  2. 受限制的$GLOBALS使用
  3. 内部函数的返回类型声明
  4. 不推荐使用可序列化接口
  5. 不兼容的float到int转换已弃用
  6. mysqli::get_client_info方法和mysqli_get_client_info($param) 已弃用
  7. 不推荐使用所有mhash*() 函数(散列扩展)
  8. filter.default和filter.default_options INI设置已弃用
  9. 在false上弃用autovivification
  10. 不推荐使用mysqli_driver->driver_version属性

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

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

例如,考虑这种用法:

复制
var_dump(str_contains("foobar", null));
// bool(true)

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

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

复制
var_dump(str_contains("foobar", null));
// Deprecated: Passing null to argument of type string is deprecated

在下一个主要的 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。

但是,在很多情况下可能会丢失类型信息。其中一些包括具有资源型,out pass-by-ref 参数,非final方法返回类型,函数或不按一般规则解析参数的方法。您可以在其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接口,PHP将抛出“Deprecated”警告。

在PHP8.1中,如果在没有实现__serialize()__unserialize()方法的情况下实现Serializable接口,PHP将抛出“Deprecated”警告。

复制
Deprecated: The Serializable interface is deprecated. Implement __serialize() and __unserialize() instead (or in addition, if support for old PHP versions is necessary) in ... on line ...

如果你支持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_infomysqli_get_client_info($param)方法已过时

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

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

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

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

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

不推荐使用所有mhash*()函数(散列扩展)

PHP5.3将mhash*()函数集成到ext/hash中,作为ext/mhash的兼容层。后来,PHP7.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.defaultfilter.default_optionsINI设置已过时

PHP的filter.defaultINI设置允许您将过滤器适用于所有的PHP超全局-即GPCR的数据($_GET$_POST$_COOKIE$_REQUEST,和$_SERVER)。

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

filter.default的INI设置提供了额外的功能,允许使用更多的过滤器,使情况变得更糟。例如,它的另一个选项-filter.default=special_chars -只为HTML启用魔法引号。人们对这些设置的了解要少得多。

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

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

弃用autovivification的false

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

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

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

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

复制
Deprecated: Automatic conversion of false to array is deprecated in

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

mysqli_driver->driver_version属性已弃用

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

在PHP 8.1中,mysqli_driver->driver_version属性已弃用。

其他小改动

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

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

小结

PHP 8.1离我们不远了。并且它已经承诺将其前身提升一倍,这是不小的壮举。

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

posted @ 2022-12-30 12:06  程序bug生  阅读(1223)  评论(0编辑  收藏  举报