PHP 8.3的新特性、更改、弃用
PHP 8.3马上要发布了,官方计划于 2023 年 11 月 23 日发布。接下来我将概述下 PHP 8.3
的功能、弃用和可以期待的重要变化。
PHP 8.3 的新特性
PHP 8.3 引入了许多新功能。然而它的功能相比PHP 8.1
或PHP 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 之间的值,类似于JavaScript
的Math.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 引擎现在将检查以确保该方法存在于父级或正在实现的接口中,并具有相同的签名,如果不存在,则引发编译时错误。 这种功能对开发人员来说是一个巨大的福音,因为它可以防止升级代码时出现错误。
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_NOTICEgc_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 中,这些函数已更新为:
- 如果一个对象实现了数字转换(这仅适用于内部类或扩展提供的类),它们将转换为该值进行计算
- 所有其他不能转换为整数或浮点数的值将继续被跳过,但现在也会发出 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 的迁移文档。可以在这里找到它。