和往年一样,今年我们也将迎来新版本的 PHP,即 PHP 8.5。这是 PHP 8 系列的一个次要版本,将在今年晚些时候发布。让我们来讨论 PHP 8.5 目前已经添加的所有新特性。有些特性在之前的文章有详细介绍。这篇我们直接介绍全部将会在 8.5 实现的功能
新特性概览
- • 管道操作符
- • 新的数组方法
- • #[\NoDiscard] 属性
- • Final 属性提升
- • 常量上的属性
- • 改进的 Directory 类
- • 致命错误回溯
- • 持久化 cURL 共享句柄改进
- • 常量表达式中的一等可调用对象
- • 常量表达式中使用闭包
- • 错误和异常处理函数
- • 静态属性的非对称可见性
- • 新的字形集群 levenshtein 函数
- • 新的 INI diff 选项
管道操作符
PHP 8.5 将引入管道操作符 (|>),它允许你以无点风格编写代码,限制不必要的中间变量的使用。
所以,以下代码...
$value = "hello world";
$result1 = function3($value);
$result2 = function2($result1);
$result = function1($result2);
...可以使用管道操作符重写为:
$value = "hello world";
$result = $value
|> function3(...)
|> function2(...)
|> function1(...);
|> 操作符,或称为"管道",接受右侧的单参数可调用对象,并将左侧的值传递给它,计算结果为可调用对象的返回值。管道 (|>) 从左到右求值,将左侧的值(或表达式结果)作为第一个也是唯一的参数传递给右侧的可调用对象。
这里是一个更真实的例子:
$fullName = 'Fred Flintstone';
$result = $fullName
|> fn($x) => explode(' ', $x) // 产生一个离散单词的数组
|> fn($x) => implode('_', $x) // 用 _ 连接这些单词
|> strtolower(...) // 全部转为小写
;
// $result === 'fred_flintstone'
新的数组方法
PHP 8.5 将引入两个新函数,array_first() 和 array_last(),它们使获取数组的第一个和最后一个元素变得更容易,而不会影响内部指针,也不使用键。
$array = [1, 2, 3, 4, 5];
$first = array_first($array); // 1
$last = array_last($array); // 5
$array = ['a' => 1, 'b' => 2, 'c' => 3];
$first = array_first($array); // 1
$last = array_last($array); // 3
对于空数组,两个函数都返回 null 而不是抛出错误。
$first = array_first([]); // null
$last = array_last([]); // null
#[\NoDiscard] 属性
PHP 8.5 引入了新的 #[\NoDiscard] 属性,它允许开发者指示函数或方法的返回值不应被忽略。如果开发者调用标记为 #[\NoDiscard] 的函数而不对返回值做任何处理,PHP 将发出警告。
#[\NoDiscard("as the operation result is important")]
function performOperation(): int {
// 执行某些操作
return 1; // 1 表示成功,0 表示失败
}
// 调用函数但不使用返回值
// Warning: The return value of function performOperation() is expected to be consumed, as the operation result is important in test.php on line 10
performOperation();
// 调用函数并使用返回值
// 这不会触发警告
$status = performOperation();
这是确保开发者不会意外忽略重要返回值的好方法,特别是在返回值对操作成功或失败至关重要的情况下。
Final 属性提升
PHP 8.5 将允许你在构造函数属性提升语法中将属性声明为 final。这在以前是不可能的。final 关键字意味着属性不能在子类中被重写。
class Example {
public function __construct(
final string $id
) {}
}
属性 $id 现在既是提升的(通过构造函数创建和初始化)又是 final 的(不能被重写)。
如果你使用 final,你不必指定可见性(如 public 或 private);它默认为 public,但如果需要,你仍然可以将 final 与可见性结合使用。
这使构造函数属性提升语法保持一致,并且需要编写的样板代码更少。
常量上的属性
如你所知,属性是使用特殊语法向代码元素(如类、方法、属性等)添加元数据的方法。例如:
#[MyAttribute]
class MyClass {}
现在,在 PHP 8.5 中,除了类、方法和类常量等,你还可以向全局(非类)常量添加属性。
你现在可以向编译时非类常量添加属性,如:
#[MyAttribute]
const MY_CONST = 42;
需要记住的一点是,这不适用于用 define() 定义的常量,只适用于用 const 声明的常量。
你现在可以使用 ReflectionConstant::getAttributes() 在运行时读取常量上的属性。此外,#[\Deprecated] 属性已更新为允许针对常量;应用时,常量被标记为 CONST_DEPRECATED。
#[Deprecated(reason: 'Use NEW_API_URL instead. This will be removed in v2.0.')]
const OLD_API_URL = 'https://old-api.example.com';
const NEW_API_URL = 'https://api.example.com';
// 使用示例
function fetchData() {
// IDE 和静态分析器现在可以警告使用 OLD_API_URL
$data = file_get_contents(OLD_API_URL);
return $data;
}
改进的 Directory 类
PHP 8.5 改变了 PHP 的 Directory 类,使其表现得像一个严格的、不可修改的资源对象。本质上,PHP 中的 Directory 类用于表示目录句柄,通常由 dir() 函数创建。历史上,它表现得像一个常规类,但这可能导致错误和误用(例如,用 new Directory() 创建无效的 Directory 对象)。
所以,为了解决这个问题,PHP 8.5 使 Directory 类表现得像一个真正的"资源对象"(有时称为"不透明对象")。
这意味着:
- • Final 类:你不能扩展(继承)Directory 类。
- • 不能直接实例化:你不能用
new Directory()创建 Directory 对象。只有dir()函数可以创建有效的实例。 - • 不能克隆:你不能克隆 Directory 对象。
- • 不能序列化:你不能序列化或反序列化 Directory 对象。
- • 没有动态属性:你不能在运行时向 Directory 对象添加新属性。
现在,当你尝试使用 new Directory() 创建 Directory 对象时,它会抛出错误:
$dir = new Directory(); // 抛出错误
$dir = dir('/tmp'); // 获取 Directory 对象的正确方法
致命错误回溯
PHP 8.5 将为 PHP 致命错误消息添加自动堆栈跟踪,使调试变得更容易。
今天,在 PHP 中,当发生致命错误(如执行时间或内存耗尽)时,错误消息不包括回溯(导致错误的函数调用列表)。没有回溯,很难找出错误的原因,特别是在大型或复杂的代码库中。
为了缓解这个问题,PHP 8.5 引入了一个新的 INI 设置:fatal_error_backtraces。
当启用时(可能默认启用),PHP 将为致命错误打印堆栈跟踪。堆栈跟踪显示导致错误的函数调用序列,类似于异常中看到的内容。
以下是回溯以前的样子:
Fatal error: Maximum execution time of 1 second
exceeded in example.php on line 7
启用新的 INI 设置后,它将看起来像这样:
Fatal error: Maximum execution time of 1 second exceeded
in example.php on line 6
Stack trace:
#0 example.php(6): usleep(100000)
#1 example.php(7): recurse()
#2 example.php(7): recurse()
...
#11 {main}
关于新错误回溯的关键点:
- • 只有致命错误(不是警告或通知)获得回溯,以避免性能和内存问题。
- • 回溯尊重隐私设置(如 SensitiveParameter 属性)。
- • 回溯也可以通过关闭函数中的
error_get_last()以编程方式获得。
持久化 cURL 共享句柄改进
PHP 8.5 改进了 PHP 管理持久化 cURL 共享句柄的方式,使它们更安全、更易于使用。
本质上,cURL 共享句柄让多个 cURL 请求共享数据(如 DNS 缓存)以提高效率。PHP 最近添加了对持久化 cURL 共享句柄的支持,这些句柄可以在多个 PHP 请求中重用。原始设计存在一些风险和可用性问题,特别是在共享 cookie 和管理持久化 ID 方面。
PHP 8.5 引入了一个新的 curl_share_init_persistent() 函数,它创建一个持久化 cURL 共享句柄。这个函数比之前的 curl_share_init() 更安全、更一致。你不再需要提供自定义持久化 ID;PHP 根据你传递的选项自动管理这个。如果你用相同的选项再次调用该函数,你会得到相同的句柄(它被重用)。
$options = [CURL_LOCK_DATA_DNS];
$shareHandle = curl_share_init_persistent($options);
// 在你的 cURL 请求中使用 CURLOPT_SHARE 使用 $shareHandle
curl_setopt($ch, CURLOPT_SHARE, $shareHandle);
这个改进很重要,因为它防止了敏感 cookie 的意外共享。除此之外,开发者不必管理持久化 ID 或担心句柄不匹配。
常量表达式中的一等可调用对象
PHP 8.5 将允许在 PHP 常量表达式中使用一等可调用对象(FCCs)。
如你所知,一等可调用对象是使用 ... 语法将函数或静态方法引用为可调用对象的简洁方式。例如:
// $callable 现在是 strlen() 的可调用对象
$callable = strlen(...);
// MyClass::myMethod() 的可调用对象
$callable = MyClass::myMethod(...);
以前,你不能在常量表达式(如默认属性值、属性参数或常量定义)中使用 FCCs。PHP 8.5 改变了这一点,允许你在常量表达式中使用 FCCs。
const MY_CALLABLE = strlen(...);
#[Attr(self::myMethod(...))]
class C {}
这使得在 PHP 中使用 FCCs 更加一致,并且方便将可重用的可调用对象定义为常量,在属性中使用它们,或作为默认值,使代码更具表现力和 DRY。
常量表达式中使用闭包
PHP 8.5 将允许在 PHP 常量表达式中使用静态闭包(匿名函数)。要理解发生了什么变化,让我们首先理解什么是常量表达式。
PHP 中的常量表达式是必须在编译时完全确定的值。例子包括:
- • 函数参数的默认值
- • 属性参数
- • 类常量
- • 属性默认值
在 PHP 8.5 之前,你不能在常量表达式中使用闭包(匿名函数)。这在 PHP 8.5 中已经改变了。所以,你可以做这样的事情。
function my_array_filter(
array $array,
Closure $callback = static function ($item) { return !empty($item); }
) {
// ...
}
或者将闭包用作属性参数、属性默认值或类常量。
这有几个约束:
必须是静态的:闭包不能使用 $this 或从周围作用域捕获变量(没有 use($foo) 和没有箭头函数)。
没有变量捕获:只允许纯静态闭包,因为常量表达式不能依赖运行时值。
编译时检查:如果你尝试使用非静态闭包或捕获变量的闭包,PHP 会给出错误。
在常量表达式中使用闭包的能力允许你编写更清洁的代码,因为你可以直接在函数签名、属性或常量中提供默认回调或验证器。
除此之外,库可以在属性中使用闭包进行验证、格式化或测试用例生成等,如下所示。
final class Locale
{
#[Validator\Custom(static function (string $languageCode): bool {
return preg_match('/^[a-z][a-z]$/', $languageCode);
})]
public string $languageCode;
}
总的来说,这是一个很好的改进,使代码更具表现力和简洁性。
错误和异常处理函数
PHP 8.5 将向 PHP 添加 get_error_handler() 和 get_exception_handler() 函数,让你直接检索当前的错误和异常处理程序。
在 PHP 中,你可以使用 set_error_handler() 和 set_exception_handler() 设置自定义错误和异常处理程序。但是,没有直接的方法来检查当前的处理程序是什么。
开发者必须使用一个变通方法:设置一个新的处理程序,保存旧的,然后恢复它,这是一个笨拙且容易出错的过程。
PHP 8.5 引入了两个新函数:
- •
get_error_handler(): ?callable - •
get_exception_handler(): ?callable
这些函数返回当前注册的错误或异常处理程序,如果没有设置则返回 null。
以下是如何使用它们。
set_error_handler(null);
get_error_handler(); // null
$handler = [$this, 'error_handler'];
set_error_handler($handler);
get_error_handler() === $handler; // true
$new_handler = $this->error_handler(...);
$old_handler = set_error_handler($new_handler);
get_error_handler() === $new_handler; // true
restore_error_handler();
get_error_handler() === $old_handler; // true
如你所猜测的,这使得设置处理程序的过程变得可靠,因为你得到了你设置的确切处理程序,使调试和处理程序管理更容易。
此外,框架和库现在可以安全地检查或恢复处理程序而不会产生副作用。
静态属性的非对称可见性
PHP 8.5 将允许 PHP 中的静态属性具有非对称可见性,这意味着它们的读写访问可以有不同的可见性级别。
给你一个入门介绍,PHP 8.4 中引入的非对称可见性让你为读取和写入属性设置不同的访问级别。例如,一个属性可以是公共读取但私有或受保护写入。
以前,非对称可见性只适用于对象(实例)属性。在 PHP 8.5 中,你现在也可以将其用于静态属性。
class Example
{
publicprivate(set) staticstring$classTitle = 'Example class';
// 任何人都可以读取 Example::$classTitle,但只有类本身可以更改它。
protected(set) staticint$counter = 0;
// 任何人都可以读取 Example::$counter,但只有类或其子类可以更改它。
publicstaticfunction changeName(string $name): void
{
self::$classTitle = $name; // 允许(在类内部)
}
}
echoExample::$classTitle; // 允许(公共读取)
Example::$classTitle = 'Nope'; // 不允许(私有写入)
这使得非对称可见性特性在语言中更加一致。
新的字形集群 levenshtein 函数
PHP 8.5 添加了一个新的 PHP 函数 grapheme_levenshtein(),通过字形集群(用户感知的字符)而不是字节或代码点来准确比较字符串。
本质上,Levenshtein 距离测量将一个字符串更改为另一个字符串需要多少次单字符编辑(插入、删除、替换)。PHP 现有的 levenshtein() 和 mb_levenshtein() 函数在字节或 Unicode 代码点上操作,这可能对复杂的 Unicode 字符(如表情符号或重音字母)给出不正确的结果。
字形集群是用户看到的单个字符,即使它由多个 Unicode 代码点组成(例如,"é" 可以是单个代码点或 "e" 加上组合重音)。
为了适应这一点,PHP 8.5 引入了 grapheme_levenshtein() 函数,它基于字形集群计算 Levenshtein 距离。这意味着它将视觉上相同的字符视为相同,即使它们的底层编码不同。
var_dump(
grapheme_levenshtein(
"\u{0065}\u{0301}",
"\u{00e9}"
)
); // 结果:0
这里,两个字符串对用户来说都显示为 "é",但编码不同。新函数正确地将它们视为相等。
新的 INI diff 选项
PHP 8.5 引入了 INI diff CLI 选项,显示所有与 PHP 内置默认值不同的 INI 设置。
php --ini=diff
此命令列出所有已从其内置默认值更改的 INI 配置指令。这是查看你的环境中哪些设置已被自定义的快速方法,这对于调试、故障排除或共享配置差异特别有用。
以下是示例输出的样子。
Non-default INI settings:
memory_limit: "128M" -> "512M"
display_errors: "1" -> ""
date.timezone: "UTC" -> "Europe/Amsterdam"
这里,左边的值是默认值,右边的值是当前设置。除此之外,只显示与默认值不同的设置。
这让你快速发现可能影响应用程序行为的配置更改,并轻松比较不同环境(开发、测试、生产)。
这也让你看到容器化或自动化设置中发生了什么变化。
浙公网安备 33010602011771号