gslsoft

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

和往年一样,今年我们也将迎来新版本的 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 = [12345];
$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"

这里,左边的值是默认值,右边的值是当前设置。除此之外,只显示与默认值不同的设置。

这让你快速发现可能影响应用程序行为的配置更改,并轻松比较不同环境(开发、测试、生产)。

这也让你看到容器化或自动化设置中发生了什么变化。

posted on 2025-07-24 01:58  gslsoft  阅读(472)  评论(0)    收藏  举报