骑驴找蚂蚁

全干工程师

image-php-8p3

PHP 8.3的新特性、更改、弃用

PHP 8.3马上要发布了,官方计划于 2023 年 11 月 23 日发布。接下来我将概述下 PHP 8.3 的功能、弃用和可以期待的重要变化。

PHP 8.3 的新特性

PHP 8.3 引入了许多新功能。然而它的功能相比PHP 8.1PHP 8.2相对较少的。 PHP 8.3中需要注意的主要功能包括:

  • 类型化类常量
  • 动态类常量和枚举成员获取支持
  • json_validate() 函数
  • 随机数扩展增强
  • mb_str_pad() 函数
  • #[\Override] 属性
  • php.ini环境变量候选值
  • stream_context_set_options函数

类型化类常量

PHP 8.3 之前的常量无法声明其类型,并且始终根据值进行推断。现在您可以显式声明它们。 这会对接口、抽象类和子类产生影响,因为您现在可以通过继承强制执行类型,从而防止实现或扩展更改类型。

interface Test {
	public string TOKEN = 'token.text';
}

动态类常量和枚举成员获取支持

PHP的所有早期版本中,以下内容均无效:

$constantName = 'SOME_FLAG';      
echo SomeClassDefiningTheConstant::{$constantName}; 
echo SomeEnumDefiningTheMemberName::{$constantName}->value;

访问这些的唯一方法是使用constant()函数:

echo constant("SomeClassDefiningTheConstant::{$constantName}"); 
echo constant("SomeEnumDefiningTheMemberName::${constantName}")->value;

由于可以动态访问属性,因此此功能现在也扩展到类常量和枚举成员。

json_validate() 函数

PHP 8.3之前验证JSON字符串,您需要将其传递给json_decode()并查看是否发出错误或引发异常(取决于您向函数提供的标志)。使用此方法验证大型JSON结构时,您在确定其是否有效之前会面临内存不足的风险。此外,这可能会导致您在实际处理该结构之前达到PHP的内存限制。新函数性能更高且不易出错。

该函数的完整签名是:

 function json_validate(string $json, int $depth = 512, int $flags = 0): bool 
$json = '{"text": "test"}';
if (json_validate($json)) {
	echo "Is json";
} else {
	echo "Not json";
}

随机数扩展

PHP 8.2中添加了“随机”扩展,为需要随机字节的操作提供更灵活和可扩展的系统,特别是那些需要加密安全伪随机数生成 (CSPRNG) 的操作。 PHP 8.3 添加了几个新方法:

  • Random\Randomizer::getBytesFromString(string $string, int $length): string

返回指定长度的随机数序列,该序列仅包含指定字符串中的字节。这对于生成随机短 URL 等内容特别有用。

  • Random\Randomizer::getFloat(float $min, float $max, Random\IntervalBoundary $boundary = Random\IntervalBoundary::ClosedOpen): float and Random\Randomizer::nextFloat(): float

可用于生成随机浮点值。在getFloat()的情况下,该值将在 $min 和 $max 之间生成,包含或仅基于 $boundary 值(IntervalBoundary 是一个新的枚举,定义您可以使用的各种边界条件)。 nextFloat() 始终生成 0 到 1 之间的值,类似于JavaScriptMath.random()函数。

mb_str_pad() 函数

PHP长期以来就有一个str_pad()函数,它允许用“填充”字符串填充字符串的开头、结尾或两侧,直到达到请求的长度。但是,此功能仅适用于单字节字符编码,这消除了 UTF-8 和其他多字节编码的使用。

该函数的完整签名是:

function mb_str_pad(string $string,  int $length,  string $pad_string = " ", int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string

测试对比:

var_dump(str_pad("🎉", 3, "祝", STR_PAD_LEFT))); // string(4) "🎉"

var_dump(mb_str_pad("🎉", 3, "祝", STR_PAD_LEFT))); // string(10) "祝祝🎉"

对比之后可以发现str_pad是以字节为计算长度填充,mb_str_pad是以字符计算长度填充。

#[\Override] 属性

当扩展一个类时,当我们定义一个方法来重写父类的相同方法时,这通常是有意的。然而,在某些情况下,我们可能会引入错误:

  • 如果该方法首先在我们的扩展类中定义,然后才添加到父类中,则它可能不会立即显而易见,特别是如果它们具有相同的签名,但该方法的意图却截然不同。
  • 如果我们在方法名称中出现拼写错误,则可能无法立即清楚这是为了重写父方法,因此不清楚为什么执行父方法而不是重写类。
  • 如果父实现更改了方法名称(例如,因为它实现的接口已更改),我们可能没有意识到需要更改扩展。

PHP 8.3 添加了一个新属性#[\Override]。开发人员可以将此属性添加到方法中,以证明他们打算让该方法重写父方法。如果存在,PHP 引擎现在将检查以确保该方法存在于父级或正在实现的接口中,并具有相同的签名,如果不存在,则引发编译时错误。 这种功能对开发人员来说是一个巨大的福音,因为它可以防止升级代码时出现错误。

关于添加#[\Override] 属性的完整说明 >>

stream_context_set_options函数

PHP 有一个stream_context_set_option函数,支持两个函数签名。它可以接受为一个或多个上下文或包装器设置的一组选项,也可以接受单个包装器名称、选项名称或其值。

新的stream_context_set_options函数:

/**
 * @param resource $context The stream or context resource to apply the options to
 * @param array $options The options to set for `stream_or_context`
 * @return bool Returns true success or false on failure.
 */
function stream_context_set_options($context, array $options): bool
// Allowed in PHP >= 8.3
class_alias('stdClass', 'MyNewClass'); 
class_alias('Traversable', 'NewTraversableInterface');

PHP 8.3 中的更改和弃用

PHP 8.3 还将引入许多弃用和更改,包括:

  • class_alias()支持 PHP 内置别名
  • php.ini环境变量候选值
  • unserialize()现在发出 E_WARNING 而不是 E_NOTICE
  • gc_status() 扩展信息
  • PHP CLI Lint (php -l) 支持同时检查多个文件
  • SQLite3 扩展中默认使用异常
  • 更合适的日期与时间异常
  • 改进 range() 参数的语义
  • 改进 array_sum() 和 array_product() 在提供不可用值时的行为
  • 断言行为弃用
  • 克隆期间的只读行为
  • 允许动态静态值初始化
  • 递增/递减运算符改进
  • 弃用

class_alias()支持 PHP 内置别名

class_alias函数为提供的类创建别名。别名类的行为与原始类完全相同。 在 PHP 8.3 之前,尝试为内置 PHP 类添加别名会导致 ValueError异常:

// Not allowed in PHP < 8.3
class_alias('stdClass', 'MyNewClass'); 
class_alias('Traversable', 'NewTraversableInterface');

ValueError: class_alias(): Argument #1 ($class) must be a user-defined class name, internal class name given

PHP 8.3及更高版本开始,可以为内部类和接口添加别名。上面的代码片段是允许的,并且 class_alias 也正确地为内部类添加别名:

php.ini环境变量候选值

PHP 支持使用 PHP 的字符串插值语法用环境变量替换php.ini的值。如果指定的环境变量不可用,INI解析器将使用空字符串。在 PHP 8.3 中,此语法已扩展为支持在未设置环境变量时声明候选值。

在此这这前:

session.name = ${SESSION_NAME}
sendmail_from = "${MAIL_FROM_USER}@${MAIL_FROM_DOMAIN}"

在 PHP 8.3 及更高版本中,可以选择使用 :- 符号声明候选值,后跟候选值。现在可以使用候选值设置上面代码片段中声明的相同 INI 值:

session.name = ${SESSION_NAME:-Foo}
sendmail_from = "${MAIL_FROM_USER:-info}@${MAIL_FROM_DOMAIN:-example.com}"

unserialize()现在发出E_WARNING而不是E_NOTICE

在 PHP 8.3 之前,向unserialize()传递无效字符串(通常)会发出 E_NOTICE(某些情况从 PHP 7.4 开始发出 E_WARNING,特别是超出unserialize_max_depth设置的结构)。从 PHP 8.3 开始,这些现在都会发出 E_WARNING。因此,您可能会注意到在生产中捕获的新日志以前不存在。我们强烈建议评估这些错误,确定导致问题的原因并纠正导致问题的任何序列化。这些很有可能会在 9.0 中更新为抛出异常。

gc_status() 扩展信息

函数 gc_status() 返回一个关联数组。在 PHP 8.3 之前,数组包含以下内容:

| Field | Type | Description | |-----------|---------|--------------------------------------------------------------------------| | runs | Integer | 垃圾收集器运行的次数。 | | collected | Integer | 收集的对象数量。 | | threshold | Integer | 缓冲区中将触发垃圾收集的根数。 | | roots | Integer | 缓冲区中当前根的数量。 |

在 PHP 8.3 中,gc_status 函数返回八个附加字段:

| Field | Type | Description | |------------------|---------|-------------------------------------------------------------------------------------------------------------------| | running | Boolean | 如果垃圾收集器正在运行,则为 true。否则为假。 | | protected | Boolean | 如果垃圾收集器受到保护并且禁止添加根,则为 true。否则为假。 | | full | Boolean | 如果垃圾收集器缓冲区大小超过 GC_MAX_BUF_SIZE,则为 true。当前设置为1024³ | | buffer_size | Integer | 当前垃圾收集器缓冲区大小。 | | application_time | Float | 总应用时间,以秒为单位(包括collector_time)。 | | collector_time | Float | 收集周期所花费的时间,以秒为单位(包括 destructor_time 和 free_time)。| | destructor_time | Float | 在循环收集期间执行析构函数所花费的时间(以秒为单位)。| | free_time | Float | 在循环收集期间释放值所花费的时间(以秒为单位)。|

PHP CLI Lint (php -l) 支持同时检查多个文件

以前,它只允许您一次检查一个文件,这意味着如果您想检查整个项目,则必须为每个应用程序文件调用一次它。从 PHP 8.3 开始,它现在允许您传递多个文件:

php -l src/**/*.php

SQLite3 扩展中默认使用异常

虽然 PDO 和几个数据库扩展已经在 PHP 8 中默认发出异常,但 SQLite3 扩展还没有。 PHP 8.3 引入了 SQLite3Exception,并在调用 SQLite3::enableExceptions(true) 时更改扩展的行为以发出此特定于扩展的异常(而不是通用异常)。此外,调用 SQLite3::enableExceptions(false) 现在将引发 E_DEPRECATED 错误。有关此更改的完整说明

更合适的日期与时间异常

日期/时间扩展一直在抛出异常和错误,但使用最通用的异常和错误类型。从 PHP 8.3 开始,它将使用更细粒度的方法,在不同情况下引发以下任一情况:

  • Error (主要用于序列化问题)
  • ValueError (当提供无效的国家代码和日出/日落值时)
  • TypeError
  • DateError (一种新类型,用于特定于扩展的不可恢复的错误)
  • DateRangeError(也是一种新类型,主要用于报告超出扩展支持范围的整数时间值)
  • DateException 是运行时异常的新接口

DateInvalidTimeZeonException DateInvalidOperationException DateMalformedStringException DateMalformedIntervalStringException DateMalformedPeriodStringException

阅读有关更合适的日期/时间例外的完整说明>>

改进range()参数的语义

range()函数允许使用步长间隔创建起始值和结束值之间的值数组。开始值和结束值可以是整数、浮点数,甚至是字符串序列(通常用于生成网格序列,类似于您在电子表格中看到的内容)。不幸的是,它有许多挥之不去的问题,主要是由于对于如何处理具有数字内容的字符串具有未定义的语义;起始值和/或结束值既不是字符串、整数也不是浮点数;对于所提供的开始/结束值无效的步长值(例如,当存在字符串开始和结束值时,为浮点步长值)。

PHP 8.3 对range()函数的行为进行了许多更改来解决这些问题。要了解对代码的全面影响,我们强烈建议您阅读 RFC。需要注意的主要事情是,在无法使用所提供的值的情况下,函数现在可以分别抛出 TypeError 和 ValueError,并且对于已知边缘情况可能导致最终意外行为的问题,可能会发出 E_WARNING。

改进array_sum()和array_product()在提供不可用值时的行为

在 8.3 之前,当使用 array_sum() 或 array_product() 时,这些函数在执行计算时将跳过任何非数字值。这允许它们工作,但也意味着用户可能不知道他们正在操作的数组包含不可用的值。特别是,表示数值(例如 GMP)的对象会导致在计算中省略它们的值,而使用布尔值、常量或资源等内容会导致极其不可预测的结果。

在 PHP 8.3 中,这些函数已更新为:

  1. 如果一个对象实现了数字转换(这仅适用于内部类或扩展提供的类),它们将转换为该值进行计算
  2. 所有其他不能转换为整数或浮点数的值将继续被跳过,但现在也会发出 E_WARNING。

断言行为弃用

PHP 8 中引入了对断言系统的许多更改,但与旧断言范例相关的许多 INI 设置并未删除。因此,熟悉 PHP 7 断言系统的用户可能会以不再有效的方式设置旧的 INI 设置,但不会从引擎获得任何它们不起作用的信息。

PHP 8.3 进行了以下更改来解决此问题:

  • assert_options() 现在发出 E_DEPRECATED
  • 现在不推荐使用assert.active和ASSERT_ACTIVE 常量,并且弃用消息指向zend_assertions设置作为替代。
  • 以下 INI 值将导致引擎在启动时发出 E_DEPRECATED.

assert.warning, assert.bail, assert.callback, assert.exception

  • 现在,assert() 方法被标记为返回void而不是bool

了解有关断言行为弃用的更多信息 >>

克隆期间的只读行为

PHP 8.1 中引入了只读属性,PHP 8.2 中引入了只读类。这些问题出现的一个问题是克隆对象时:如果在原始对象中设置了该值,则任何在克隆中覆盖该值的尝试都会导致错误,除非使用反射 API 来覆盖只读属性。

使用 PHP 8.3,您可以在 __clone() 方法(或在 __clone() 中调用的方法)内操作时重新声明或取消设置只读属性。

了解有关克隆过程中只读行为更改的更多信息 >>

允许动态静态值初始化

在函数声明中,您可以将变量声明为静态变量;当您这样做时,函数调用期间对该值的任何更改都将被保留,并且对该函数的下一次调用将使用上一次调用中的值。静态变量的声明允许为其分配一个初始值,该初始值仅在第一次调用时使用。

声明静态变量时,您只能将其声明为常量表达式。这意味着,如果您想根据另一个函数调用来初始化该值,则需要执行以下操作:

 
     function operation(...string $values): string 
     { 
          static $message = null; 
          if (null === $message) { 
              $message = someFunctionProvidingADefaultMessage(); 
          } 
       
          // do some additional work, e.g., concatenating something to $message 
       
          return vsprintf($message, $values); 
      }

在 PHP 8.3 中,您现在可以使用任何表达式来定义静态变量。 然而,这对 ReflectionFunction::getStaticVariables() 有影响。由于变量可以从表达式分配,因此该信息可能仅在运行时可用(例如,如果您在表达式中使用函数参数),如果从未调用该函数,这可能会成为问题。因此,如果编译器无法解析表达式,它将分配一个空值。了解有关变更的更多信息>>

递增/递减运算符改进

PHP 允许对任何类型使用自增 (++) 和自减 (--) 运算符。当类型不是 int 或 float 时,有时会导致令人惊讶的行为。数组和资源会导致类型错误,布尔值不会更改,字符串值将转换为 int 或 float(如果是数字)。对于 deprement 操作,null 和非数字、非空字符串保持不变,而空字符串结果为 -1。对于递增操作,null 和空字符串结果为 1,而非空字符串将根据 PERL 字符串递增规则递增字符串。如果对象实现了 do_operation() 句柄,则可以递增和递减;但是,某些内部对象定义 _IS_NUMBER 时没有 do_operation() 句柄,并且运算符将无法使用它们。

PHP 8.3 进行了以下更改:

  • 引入 str_increment() 和 str_decrement() 函数,提供遵循 PERL 字符串递增和递减规则的对称字符串递增和递减操作。非数字字符串的递增和递减操作现在将发出 E_DEPRECATED,用户将被引导至新函数。
  • 仅定义 _IS_NUMBER 的对象的递增和递减,没有 do_operation() 句柄。
  • 对于没有定义行为的操作(例如,具有 null 和布尔值),运算符将发出 E_WARNING。

了解有关改进的更多信息>>

弃用

一项提议被接受,旨在弃用大量 PHP 功能,主要是针对内部函数的无效参数、不再使用/有效的常量、过时(因此很危险!)或被其他算法取代的加密功能。阅读完整提案以了解详细信息。

升级或迁移到 PHP 8.3 时的注意事项

考虑到 PHP 8.3 中引入的弃用和更改的数量,PHP 团队在确定迁移或升级范围时将希望仔细检查其代码库。对于开发人员来说,好消息是 PHP 通常会发布概述从先前版本升级的文档。一旦发布了从 PHP 8.2.x 迁移到 8.3.x 的文档,您将有一个很好的迁移工作起点。PHP 社区发布了 PHP 8.2.x 到 8.3.x 的迁移文档。可以在这里找到它

留言