简介

PHP 8.5 引入了许多令人兴奋的新特性,包括管道运算符(Pipe Operator)、URI 扩展、Clone With 语法、#[\NoDiscard] 属性等。本文将介绍主要变化和如何从 PHP 8.4 迁移到 PHP 8.5。

参考资源

PHP 8.5 新特性

管道运算符(Pipe Operator)

PHP 8.5 引入了管道运算符 |>,使函数链式调用更加清晰易读:

// PHP 8.5 之前 - 嵌套函数调用,难以阅读
$slug = strtolower(str_replace('.', '', str_replace(' ', '-', trim($title))));

// PHP 8.5 - 使用管道运算符,从左到右清晰阅读
$slug = $title
    |> trim(...)
    |> (fn($str) => str_replace(' ', '-', $str))
    |> (fn($str) => str_replace('.', '', $str))
    |> strtolower(...);

管道运算符将左侧的值作为右侧函数的第一个参数传递,实现函数组合的链式调用。

URI 扩展

PHP 8.5 新增了内置的 URI 扩展,遵循 RFC 3986 和 WHATWG URL 标准来解析和处理 URL。

URI 与 URL 的关系:URI(统一资源标识符)用于标识资源,URL(统一资源定位符)用于定位资源。所有 URL 都是 URI,但不是所有 URI 都是 URL。该扩展命名为 URI 是因为它能处理更广泛的标识符格式。

示例:

// PHP 8.5 之前
$components = parse_url('https://php.net/releases/8.5/en.php');
var_dump($components['host']); // "php.net"

// PHP 8.5 - 使用新的 URI 扩展
use Uri\Rfc3986\Uri;

$uri = new Uri('https://php.net/releases/8.5/en.php');
var_dump($uri->getHost());   // "php.net"
var_dump($uri->getPath());   // "/releases/8.5/en.php"
var_dump($uri->getScheme()); // "https"

新的 URI 扩展由 uriparser 和 Lexbor 库驱动,提供更安全、更符合标准的 URI 解析 API。

Clone With 语法

PHP 8.5 简化了对象克隆时更新属性的操作,特别适用于 readonly 类的 “with-er” 模式。

什么是 “with-er” 模式:一种用于不可变对象的设计模式,方法名以 with 开头(如 withId()withName()),不修改原对象,而是返回一个修改了某个属性的新对象。常见于 PSR-7 HTTP 消息接口和 DateTimeImmutable 等。

示例:

class Color {
    public function __construct(
        public readonly int $red,
        public readonly int $green,
        public readonly int $blue,
        public readonly int $alpha = 255,
    ) {}

    // PHP 8.5 之前
    public function withAlphaOld(int $alpha): self {
        $values = get_object_vars($this);
        $values['alpha'] = $alpha;
        return new self(...$values);
    }

    // PHP 8.5 - 使用 clone with
    public function withAlpha(int $alpha): self {
        return clone($this, ['alpha' => $alpha]);
    }
}

$color = new Color(255, 128, 0);
$transparent = $color->withAlpha(128);

#[\NoDiscard] 属性

新的 #[\NoDiscard] 属性用于标记函数返回值不应被忽略:

#[\NoDiscard("检查操作是否成功")]
function saveData(array $data): bool {
    // 保存数据...
    return true;
}

// 忽略返回值会触发警告
saveData(['key' => 'value']);
// Warning: return value of saveData() should be used or cast to (void)

// 正确使用
$success = saveData(['key' => 'value']);
if (!$success) {
    // 处理错误
}

// 显式忽略(不会警告)
(void) saveData(['key' => 'value']);

常量表达式中的闭包

PHP 8.5 允许在属性、常量和默认值中使用静态闭包和一等可调用对象:

class Validator {
    public const RULES = [
        'email' => filter_var(...),
        'trim' => trim(...),
    ];
}

function process(
    callable $callback = strtoupper(...)
): string {
    return $callback('hello');
}

持久化 cURL Share Handles

新的 curl_share_init_persistent() 函数可以跨请求维护 cURL 共享句柄:

// 创建持久化的共享句柄
$share = curl_share_init_persistent('my-share-handle');

$ch = curl_init('https://example.com');
curl_setopt($ch, CURLOPT_SHARE, $share);
curl_exec($ch);

PHP 8.5 新增函数

array_first() 和 array_last()

获取数组的第一个或最后一个元素,数组为空时返回 null

$events = ['login', 'view_page', 'checkout', 'logout'];

$first = array_first($events);  // 'login'
$last = array_last($events);    // 'logout'

$empty = [];
var_dump(array_first($empty));  // null
var_dump(array_last($empty));   // null

get_error_handler() 和 get_exception_handler()

获取当前注册的错误处理器和异常处理器:

// 设置自定义错误处理器
set_error_handler(function($errno, $errstr) {
    echo "Error: $errstr";
});

// 获取当前错误处理器
$handler = get_error_handler();
var_dump($handler); // Closure

// 获取当前异常处理器
$exceptionHandler = get_exception_handler();

IntlListFormatter 类

新的国际化列表格式化类:

$formatter = new IntlListFormatter('zh-CN', IntlListFormatter::TYPE_AND);
echo $formatter->format(['苹果', '香蕉', '橙子']);
// 输出: 苹果、香蕉和橙子

$formatter = new IntlListFormatter('en-US', IntlListFormatter::TYPE_OR);
echo $formatter->format(['apple', 'banana', 'orange']);
// 输出: apple, banana, or orange

curl_multi_get_handles()

从 multi-cURL 会话中获取所有句柄:

$mh = curl_multi_init();
$ch1 = curl_init('https://example.com/1');
$ch2 = curl_init('https://example.com/2');

curl_multi_add_handle($mh, $ch1);
curl_multi_add_handle($mh, $ch2);

$handles = curl_multi_get_handles($mh);
var_dump(count($handles)); // 2

locale_is_right_to_left() / Locale::isRightToLeft()

判断语言环境是否使用从右到左的文字方向:

var_dump(locale_is_right_to_left('ar'));    // true (阿拉伯语)
var_dump(locale_is_right_to_left('he'));    // true (希伯来语)
var_dump(locale_is_right_to_left('zh-CN')); // false (中文)
var_dump(Locale::isRightToLeft('fa'));      // true (波斯语)

grapheme_levenshtein()

计算两个字符串之间的 Levenshtein 编辑距离(基于字形簇):

$distance = grapheme_levenshtein('café', 'cafe');
var_dump($distance); // 1

Closure::getCurrent()

在闭包内部获取当前闭包实例,便于实现递归:

$factorial = function(int $n): int {
    if ($n <= 1) return 1;
    return $n * Closure::getCurrent()($n - 1);
};

echo $factorial(5); // 120

其他改进

致命错误包含堆栈跟踪

PHP 8.5 中,致命错误现在会包含完整的堆栈跟踪信息,大大提升了调试体验。

新的 DOM 方法

$doc = new DOMDocument();
$doc->loadHTML('<div class="box">A</div><div class="box">B</div>');

// getElementsByClassName()
$elements = $doc->getElementsByClassName('box');
echo $elements->length; // 2

// insertAdjacentHTML()
$div = $doc->querySelector('div');
$div->insertAdjacentHTML('beforeend', '<span>New</span>');

属性可以应用于常量

class MyClass {
    #[Deprecated("Use NEW_CONST instead")]
    public const OLD_CONST = 'old';

    public const NEW_CONST = 'new';
}

静态属性支持非对称可见性

class Counter {
    public private(set) static int $count = 0;

    public static function increment(): void {
        self::$count++;
    }
}

echo Counter::$count; // 可读
Counter::$count = 10; // 错误:不可写

构造函数提升支持 final 属性

class User {
    public function __construct(
        public final string $id,
        public string $name,
    ) {}
}

setcookie() 支持 “partitioned” 选项

setcookie('name', 'value', [
    'expires' => time() + 3600,
    'path' => '/',
    'secure' => true,
    'partitioned' => true,  // 新选项
]);

新的全局常量

// 构建提供者标识
echo PHP_BUILD_PROVIDER; // 例如: "debian" 或 "remi"

// 构建日期
echo PHP_BUILD_DATE; // 例如: "Jan 1 2026"

CLI 新选项

# 显示与默认值不同的 INI 配置
php --ini=diff

max_memory_limit INI 指令

管理员可以设置内存限制的上限:

; php.ini
max_memory_limit = 256M

应用程序无法将 memory_limit 设置为超过 max_memory_limit 的值。

弃用特性

反引号运算符(shell_exec 别名)

// ❌ PHP 8.5 已弃用
$output = `ls -la`;

// ✅ 使用 shell_exec()
$output = shell_exec('ls -la');

非标准类型转换

// ❌ PHP 8.5 已弃用
$bool = (boolean) $value;
$int = (integer) $value;
$float = (double) $value;
$bin = (binary) $value;

// ✅ 使用标准类型转换
$bool = (bool) $value;
$int = (int) $value;
$float = (float) $value;
$bin = (string) $value;

mysqli_execute()

// ❌ 已弃用
mysqli_execute($stmt);

// ✅ 使用 mysqli_stmt_execute()
mysqli_stmt_execute($stmt);

curl_close() 和 curl_share_close()

这些函数自 PHP 8.0 起就是空操作,现在正式弃用:

// ❌ 已弃用(实际上不需要调用)
curl_close($ch);
curl_share_close($sh);

// cURL 句柄现在会自动清理

xml_parser_free()

// ❌ 已弃用(自 PHP 8.0 起是空操作)
xml_parser_free($parser);

// XML 解析器现在会自动清理

socket_set_timeout()

// ❌ 已弃用
socket_set_timeout($stream, 30);

// ✅ 使用 stream_set_timeout()
stream_set_timeout($stream, 30);

MHASH_* 常量

所有 MHASH_* 相关常量已弃用,应使用 hash() 函数替代。

__sleep() 和 __wakeup()

这两个魔术方法被软弃用(soft-deprecated),建议使用 __serialize()__unserialize() 替代:

// ❌ 软弃用
class OldStyle {
    public function __sleep(): array {
        return ['data'];
    }
    public function __wakeup(): void {}
}

// ✅ 推荐方式
class NewStyle {
    public function __serialize(): array {
        return ['data' => $this->data];
    }
    public function __unserialize(array $data): void {
        $this->data = $data['data'];
    }
}

case 语句后使用分号

// ❌ PHP 8.5 已弃用
switch ($value) {
    case 1;  // 分号
        break;
}

// ✅ 使用冒号
switch ($value) {
    case 1:  // 冒号
        break;
}

null 作为数组偏移量

// ❌ PHP 8.5 已弃用
$array[null] = 'value';

// ✅ 使用空字符串或其他明确的键
$array[''] = 'value';

移除的特性

disable_classes INI 设置

disable_classes INI 设置已被完全移除。

CLI -z / –zend-extension 选项

从命令行加载 Zend 扩展的选项已被移除。

兼容性变化

类型转换警告

  • NAN 转换为其他类型时会产生警告
  • 浮点数转整数时的精度损失会产生警告
  • 对非数组使用解构语法会产生警告

class_alias() 限制

“array” 和 “callable” 不再是 class_alias() 的有效类名。

总结

PHP 8.5 带来了许多实用的新特性和改进:

主要新特性:

  • 管道运算符 |>
  • URI 扩展
  • Clone With 语法
  • #[\NoDiscard] 属性
  • array_first()array_last() 函数
  • 常量表达式中的闭包
  • 致命错误堆栈跟踪
  • IntlListFormatter 类

需要注意的变化:

  • 反引号运算符已弃用
  • 非标准类型转换已弃用
  • __sleep()__wakeup() 软弃用
  • disable_classes INI 设置已移除

通过了解这些变化并遵循迁移建议,可以顺利地从 PHP 8.4 迁移到 PHP 8.5。