简介

PHP 8.4 引入了许多新特性,如属性钩子(Property Hooks)、非对称可见性(Asymmetric Visibility)、#[Deprecated] 属性以及更新的 DOM API 等。本文将介绍主要变化和如何进行迁移。

参考资源

PHP 8.4 新特性

属性钩子与非对称可见性

// 属性钩子(Property Hooks)与非对称可见性(Asymmetric Visibility)
class User {
    private bool $isModified = false;
    public function __construct(public private(set) string $first, private string $last)
    {
    }
    public string $fullName {
        get => $this->first . " " . $this->last;
        set {
            [$this->first, $this->last] = explode(' ', $value, 2);
            $this->isModified = true;
        }
    }
}
$user = new User("Ming", "Li");
echo $user->fullName; // 输出: Ming Li
$user->fullName = "Hong Xiao";
echo $user->fullName; // 输出: Hong Xiao

// 非对称可见性示例
echo $user->first; // 输出: Hong
$user->first = "Hua";
// 错误: Fatal error: Uncaught Error: Cannot modify private(set) property User::$first

属性钩子允许我们定义属性的获取和设置行为,而非对称可见性允许我们为属性设置不同的读写权限。在上例中,$first 属性可以公开读取但只能私有写入。

#[Deprecated] 属性

PHP 8.4 引入了 #[Deprecated] 属性,可以用来标记已弃用的代码:

#[Deprecated("Use newFunction() instead", since: "2.0")]
function oldFunction(): void {
    // ...
}

oldFunction();
// Warning: Function oldFunction() is deprecated, Use newFunction() instead

这使得开发者可以在自己的代码库中使用与 PHP 内部相同的弃用机制。

增强的 DOM API

PHP 8.4 更新了 DOM API,提供了更现代、更易用的接口:

// 新的 DOM API 示例
$doc = \Dom\HTMLDocument::createFromString('<div><span>Hello</span></div>');

// 使用 querySelector 和 querySelectorAll
$span = $doc->querySelector('span');
echo $span->textContent; // 输出: Hello

// 使用 innerHTML/outerHTML
$div = $doc->querySelector('div');
echo $div->innerHTML; // 输出: <span>Hello</span>
$div->innerHTML = '<p>World</p>';
echo $div->outerHTML; // 输出: <div><p>World</p></div>

BCMath 的对象 API

// BCMath 的对象 API
use BcMath\Number;
$num1 = new Number('0.12345');
$num2 = new Number('2');
$result = $num1 + $num2;
echo $result; // 输出: '2.12345'
var_dump($num1 > $num2); // 输出: false

// PHP 8.4 之前的写法
$num1 = '0.12345';
$num2 = 2;
$result = bcadd($num1, $num2, 5);
echo $result; // 输出: '2.12345'
var_dump(bccomp($num1, $num2) > 0); // 输出: false

// 新增 bcdivmod() 函数 - 同时返回商和余数
[$quotient, $remainder] = bcdivmod('10', '3', 2);
echo $quotient;  // 输出: 3
echo $remainder; // 输出: 1.00

PHP 8.4 为 BCMath 引入了对象 API,使用起来更加直观和面向对象。

HTTP/3 支持

PHP 8.4 的 cURL 扩展新增了 HTTP/3 支持:

$ch = curl_init('https://example.com');
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3);
// 或强制使用 HTTP/3
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3ONLY);
$response = curl_exec($ch);

其他新增的 cURL 选项包括:CURLOPT_PREREQFUNCTIONCURLOPT_DEBUGFUNCTIONCURLOPT_SERVER_RESPONSE_TIMEOUTCURLOPT_TCP_KEEPCNT

PHP 8.4 新增函数

新的数组操作函数

// array_find() - 查找第一个满足条件的元素
$animal = array_find(
    ['dog', 'cat', 'cow', 'duck', 'goose'],
    static fn (string $value): bool => str_starts_with($value, 'c'),
);
var_dump($animal); // 输出: string(3) "cat"

// array_find_key() - 查找第一个满足条件的元素的键
$key = array_find_key(
    ['a' => 1, 'b' => 2, 'c' => 3],
    fn($v) => $v > 1
);
var_dump($key); // 输出: string(1) "b"

// array_any() - 检查是否至少有一个元素满足条件
$result = array_any([1, 2, 3, 4], fn($n) => $n > 3); // true

// array_all() - 检查是否所有元素都满足条件
$result = array_all([1, 2, 3, 4], fn($n) => $n > 0); // true

新的字符串处理函数

// 多字节字符串修剪函数
$str = " こんにちは "; // 包含全角空格
echo mb_trim($str);   // "こんにちは"
echo mb_ltrim($str);  // "こんにちは "
echo mb_rtrim($str);  // " こんにちは"

// 多字节首字母大小写转换
echo mb_ucfirst('ñoño'); // "Ñoño"
echo mb_lcfirst('ÑoÑo'); // "ñoÑo"

// 按字形簇拆分字符串
$chars = grapheme_str_split('👨‍👩‍👧‍👦');
var_dump($chars); // ['👨‍👩‍👧‍👦']

日期时间新方法

// 从时间戳创建 DateTime 对象
$dt = DateTime::createFromTimestamp(1704067200);
$dti = DateTimeImmutable::createFromTimestamp(1704067200.123456);

// 微秒处理
$dt = new DateTime();
echo $dt->getMicrosecond(); // 获取微秒
$dt->setMicrosecond(500000); // 设置微秒

round() 函数新舍入模式

// PHP 8.4 新增舍入模式
echo round(1.5, 0, PHP_ROUND_HALF_UP);       // 2 (四舍五入)
echo round(1.5, 0, PHP_ROUND_HALF_DOWN);     // 1 (五舍六入)
echo round(1.5, 0, PHP_ROUND_HALF_EVEN);     // 2 (银行家舍入)
echo round(1.5, 0, PHP_ROUND_HALF_ODD);      // 1 (舍入到奇数)
echo round(1.2, 0, PHP_ROUND_CEILING);       // 2 (向上取整)
echo round(1.8, 0, PHP_ROUND_FLOOR);         // 1 (向下取整)
echo round(-1.5, 0, PHP_ROUND_TOWARD_ZERO);  // -1 (向零取整)
echo round(-1.5, 0, PHP_ROUND_AWAY_FROM_ZERO); // -2 (远离零取整)

// 注意:无效的舍入模式现在会抛出 ValueError

其他新函数

// request_parse_body() - 解析请求体
[$_POST, $_FILES] = request_parse_body();

// HTTP 响应头处理
$headers = http_get_last_response_headers(); // 获取最后一次响应头
http_clear_last_response_headers(); // 清除响应头缓存

// IANA 时区 ID
echo intltz_get_iana_id('Asia/Shanghai'); // 获取规范时区 ID

兼容性与迁移

弃用特性

隐式可空类型

// ❌ PHP 8.4 已弃用
function save(Book $book = null) {}

// ✅ 修复方法:显式声明可空类型
function save(?Book $book = null) {}

在 PHP 8.4 中,隐式可空类型已被弃用,需要使用问号(?)显式声明可空类型。

其他弃用

// ❌ E_STRICT 常量已弃用
error_reporting(E_ALL & ~E_STRICT);

// ❌ session_set_save_handler() 超过 2 个参数已弃用
session_set_save_handler($open, $close, $read, $write, $destroy, $gc);

// ✅ 使用 SessionHandlerInterface
session_set_save_handler(new MySessionHandler(), true);

// ❌ CSV 函数的 $escape 参数默认值已弃用,需显式传递
fgetcsv($handle, 0, ',', '"'); // 需要传递第四个参数

// ❌ CURLOPT_BINARYTRANSFER 已弃用(实际上从未有任何效果)

行为变化

exit/die 变为函数

exitdie 从语言结构变为函数,这意味着:

// 现在可以作为 callable 使用
$callback = exit(...);

// 参数类型更严格
exit(new stdClass()); // TypeError: exit(): Argument #1 ($status) must be of type string|int

常量类型变化

// PHP_ZTS 和 PHP_DEBUG 从 int 变为 bool
var_dump(PHP_ZTS);   // bool(true) 或 bool(false)
var_dump(PHP_DEBUG); // bool(true) 或 bool(false)

已移除的扩展

以下扩展从 PHP 核心移至 PECL:

  • Pspell - 拼写检查扩展
  • IMAP - 邮件协议扩展
  • OCI8 - Oracle 数据库扩展
  • PDO-OCI - Oracle PDO 驱动

如需继续使用,需从 PECL 安装。

最低版本要求

  • OpenSSL:最低版本 1.1.1
  • libcurl:最低版本 7.61.0
  • Bcrypt:密码哈希默认 cost 从 10 提升到 12

总结

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

主要新特性:

  • 属性钩子和非对称可见性
  • #[Deprecated] 属性
  • 现代化的 DOM API
  • BCMath 对象 API
  • HTTP/3 支持
  • 新的数组和字符串函数

需要注意的变化:

  • 隐式可空类型已弃用
  • exit/die 变为函数
  • 部分扩展移至 PECL
  • 依赖库最低版本要求提升

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