PHP 8.4 核心特性
简介:
自 PHP 8.0 发布以来,该语言经历了一系列重要的功能迭代与性能改进。本分类将按版本梳理 PHP 8.x 的新特性,对于开发者而言,系统理解这些新特性不仅是技术更新的需要,更是优化代码设计、降低维护成本的有效途径。
PHP 8.4 核心特性
8.4官方手册参考指南(其他特性):https://www.php.net/releases/8.4/zh.php#new-uri-extension
一、属性钩子
属性钩子是 Zend 引擎原生支持的属性访问拦截机制,在属性读取(get)、写入(set)时自动触发自定义逻辑,无需额外方法调用。
告别冗余,拥抱原生。属性钩子的优势是颠覆性的:
原生性能:它是 Zend 引擎在语言层面直接支持的机制,性能远优于 __get/__set 魔术方法
更优雅的语法:告别重复的方法体,直接在属性上定义存取逻辑,代码更简洁
出色的工具支持:IDE 和静态分析工具(如 PHPStan)能原生识别属性钩子,告别了依赖 @property 注解的时代
精准的控制:与“一揽子”拦截的 __get/__set 不同,钩子可以精准地为单个属性定制行为,同时为未来预留扩展可能,避免了 API 的不兼容变更
语法示例:
<?php class User { public string $firstName; public string $lastName; // 使用箭头函数定义只读的计算属性 仅定义get public string $fullName { get => $this->firstName .' '. $this->lastName; } // 使用代码块定义带验证的 setter 仅定义set public string $email { set(string $value){ if(!filter_var($value, FILTER_VALIDATE_EMAIL)){ throw new InvalidArgumentException("Invalid email address"); } $this->email = strtolower($value); } } } $user = new User(); $user->firstName = "John"; $user->lastName = "Doe"; echo $user->fullName; // John Doe echo '<br/>'; $user->email = "John@Example.COM"; echo $user->email; // john@example.com
使用场景:
赋值时数据校验 + 清洗(最核心),写入属性时自动触发 set,无需手动调用方法。例:邮箱、手机号、年龄、价格、用户名等合法性校验;自动去空格、转小写
读取时自动格式化(不修改原值),读取属性时自动触发 get,原值保持原始格式。例:姓名大写、日期格式化、金额加货币符号、手机号脱敏
虚拟 / 计算属性(无存储,动态派生),每次读取触发 get 计算,无存储、不占用内存。例:全名、订单总价、BMI、年龄计算等无需存库的派生字段
非对称可见性(对外只读,内部可写),外部写入直接报错,内部写入触发 set。例:ID、创建时间、状态等禁止外部修改的字段
类型自动双向转换(ORM / 存储层常用),读→转对象 / 数组,写→转字符串存库。例:数据库存字符串,代码用对象;数组↔JSON 互转
属性变更监听(日志、触发事件、联动更新),赋值时 set 执行副作用逻辑。例:记录修改日志、状态变更时更新其他字段、触发通知
防非法赋值(自动修正,不抛异常),写入时 set 自动修正值。例:库存不能为负、价格默认 0、空值自动填充
二、不对称可见性
在 PHP8.4 之前,类属性的可见性 (public/protected/private) 是对称的—— 读取和写入操作必须遵循相同的访问规则。如果需要实现 "对外只读、对内可写" 的常见需求,开发者只能通过以下两种繁琐方式实现:
私有属性 + 公共 getter 方法
只读属性
只读属性的局限性在于只能在构造函数中赋值,无法在对象生命周期内通过类内部方法修改。
PHP8.4 的不对称可见性 (Asymmetric Visibility),也常被简称为 "aviz"。这一特性彻底改变了 PHP 中类属性的访问控制机制,允许开发者为属性的读取 (get) 和写入 (set) 操作设置不同的可见性级别,解决了长期以来属性封装的痛点问题。
语法格式:{读取可见性} {写入可见性}(set) {类型声明} $属性名;
示例:
<?php class Order { public function __construct( public private(set) int $orderId, // 读取:public,写入:private(仅类内部可写) protected private(set) float $amount, // 读取:protected(类和子类可见),写入:private(仅类内部可写) public protected(set) string $status = 'pending' // 读取:public,写入:protected(类和子类可写) ) {} // 类内部方法可修改所有属性 public function updateStatus(string $newStatus) :void { $this->status = $newStatus; // 允许,写入可见性为protected } } $order = new Order(1001, 99.99); echo '<pre>'; // 读取操作(符合读取可见性) var_dump($order->orderId); // 1001 (允许:public读取) var_dump($order->status); // pending (允许:public读取) // 写入操作(需符合写入可见性) //$order->orderId = 1002; // 错误:private(set),外部不可写 //$order->status = 'shipped'; // 错误:protected(set),外部不可写 // 类内部方法修改 $order->updateStatus('shipped'); // 允许:类内部调用 var_dump($order->status); // shipped (成功修改)
不对称可见性并非无限制,PHP 引擎实施了以下重要约束以保证类型安全和代码一致性:
写入可见性不能宽于读取可见性:写入操作的可见性必须等于或更严格于读取可见性 。例:public 读取不能搭配 protected 写入以外的更宽松设置 private public(set) int $id(错误)
必须有类型声明:不对称可见性仅适用于有显式类型的属性。例:public private(set) $name(错误,无类型)
静态属性支持:初始版本不支持静态属性,后续通过单独 RFC 补充支持。例:public private(set) static int $counter(PHP8.4.1 + 有效)
与 readonly 不兼容:不能与readonly关键字同时使用 (语义冲突) 例:public private(set) readonly int $id (错误)
与属性钩子 (Property Hooks) 的结合。PHP8.4 同时引入了属性钩子特性,允许为属性添加自定义的 get/set 逻辑,与不对称可见性完美兼容。示例:
<?php class User { // 不对称可见性:读公开,写私有 // 同时为 get 和 set 添加钩子 public private(set) string $name { get { return 'User: ' . $this->name; // 自动添加前缀 } set { if ($value === '') { throw new InvalidArgumentException('Name cannot be empty'); } $this->name = $value; // 直接写入后备字段,不会递归 } } public function __construct(string $name) { $this->name = $name; // 触发 set 钩子(私有写) } } $user = new User('Alice'); echo $user->name; // 输出:User: Alice // $user->name = 'Bob'; // 错误:Cannot modify private(set) property
子类重写父类的不对称可见性属性时,必须遵循以下规则,确保不破坏封装契约:
类型兼容性:子类属性必须与父类属性完全相同的类型(协变 / 逆变不允许)
读取可见性:子类读取可见性不能比父类更严格(可保持相同或更宽松)
子类写入可见性不能比父类更严格(可保持相同或更宽松)
private (set) 限制:父类中声明为private(set)的属性不能被子类重写(等效于 final 属性)
最佳实践:
实体类 / 值对象(最常用):User、Product、Order 等业务实体,ID、创建时间、唯一编码等属性,外部只能读取,仅类内部可初始化 / 修改。
服务类的内部状态属性:缓存服务、日志服务等,外部只读服务状态,内部根据操作动态更新
配置类的只读配置:应用配置、数据库配置等,加载后外部只读,仅初始化时内部设置。
与属性钩子结合(验证 / 格式化):需要对写入值做验证 / 格式化,但仍保持 “对外只读、对内可写”。
父子类共享的可写属性:基类定义属性,外部只读,子类可内部修改(用 public protected(set))
合理选择可见性组合:对外只读:public private(set)、父子类共享写入:public protected(set)、内部组件可见:protected private(set)
三、#[Deprecated]
#[Deprecated]是 PHP 8.4 新增的内置属性,用于标记代码元素为已弃用,替代传统的trigger_error(E_USER_DEPRECATED)手动实现。适用目标:函数、方法、类常量 (8.4)、全局常量 (8.5)、特质 (8.5),错误级别为:E_USER_DEPRECATED (区别于内核的E_DEPRECATED),不会中断程序执行。
语法示例:
<?php // 简易示例 #[\Deprecated] function simple_function(): void {} // 完整示例 #[\Deprecated(message:"使用new_function()替代", since: "1.5.0")] /** * message:弃用说明,建议包含替代方案,会显示在警告信息中 * since:标记弃用起始版本,便于版本管理 */ function old_function(): void {} class FileUploader { #[\Deprecated(message: "使用API_VERSION_3替代")] public const API_VERSION_2 = "2.0"; public const API_VERSION_3 = "3.0"; #[\Deprecated( message: "请使用uploadFile()方法替代,该方法支持文件验证", since: "3.0.0" )] public function upload(string $path): void { // 旧实现 } public function uploadFile(string $path): void { // 带验证的新实现 } } simple_function();// 错误:Deprecated: Function simple_function() is deprecated in old_function();// 错误:Deprecated: Function old_function() is deprecated since 1.5.0, 使用new_function()替代 in var_dump(FileUploader::API_VERSION_2); // 触发弃用警告 输出 “2.0” $uploader = new FileUploader(); $uploader->upload("/tmp/file.txt"); // 触发弃用警告
四、ext-dom 功能和 HTML5 支持
历史痛点与解决方案:
传统 DOMDocument 依赖 libxml2 解析 HTML,而 libxml2 的 HTML 解析停留在 HTML 4 时代,存在三大问题:无法正确识别 <article>、<section> 等 HTML5 语义标签、解析规则与 HTML5 规范不一致,导致文档结构异常、处理嵌入式 JavaScript 等现代内容时容易出错。
PHP 8.4 引入 Lexbor 解析库(由 Alexander Borisov 开发),专为 HTML5 设计,具备以下优势:完全符合 WHATWG HTML 规范,支持所有 HTML5 元素与解析规则、高性能(C 语言实现,内存占用低,解析速度快)、原生支持 CSS 选择器查询,无需额外依赖、良好的错误恢复机制,处理 “标签汤(Tag Soup)” 更健壮。
关键设计:
Dom\HTMLDocument 与 Dom\XMLDocument 均继承自抽象类 Dom\Document
旧 DOMDocument 类现在也继承自 Dom\Document,实现部分新特性兼容
新 API 使用静态构造函数而非 new 关键字创建实例,更符合现代 PHP 实践
基础使用示例:
<?php // 1. 解析 HTML5 文档 $html = '<!DOCTYPE html><html lang="zh-CN"><head><title>测试</title></head><body><div id="main"><p class="intro">PHP 8.4 新特性</p></div></body></html>'; $dom = Dom\HTMLDocument::createFromString($html); echo '<pre>'; print_r($dom); // 2. CSS 选择器查询 $intro = $dom->querySelector('#main .intro'); print_r($intro->textContent);// 输出: PHP 8.4 新特性 // 3. 修改内容 $intro->innerHTML = 'PHP 8.4 <strong>DOM 新特性</strong>'; // 4. 操作类名 $intro->classList->add('highlight'); $intro->classList->remove('intro'); // 5. 序列化输出 print_r($dom->saveHTML());
五、BCMath 对象 API
在 PHP 8.4 之前,处理高精度数学运算的唯一选择是调用 bcadd、bcsub 这类略显晦涩的过程式函数。PHP 8.4 引入了一个全新的、面向对象的 API——BcMath\Number 类。它不是一个简单的语法糖,而是为高精度计算带来了可读性、安全性和强大功能的全面提升。
核心特性:
运算符重载:支持 +、-、*、/、%、** 等算术运算符及所有比较运算符,代码更直观易读
不可变性:所有计算操作返回新对象,原对象保持不变,避免副作用
Stringable 接口:可直接在字符串上下文中使用(如 echo $num),自动转换为字符串表示
独立精度控制:不受 bcmath.scale INI 指令影响,每个对象和操作可单独设置精度
全面方法支持:提供与传统 BCMath 函数对应的面向对象方法,覆盖所有高精度计算需求
基础示例:
<?php declare (strict_types = 1); namespace app\controller; use app\BaseController; use BcMath\Number; class Index extends BaseController { public function index() { // 整数初始化,scale 自动设为 0 $num1 = new Number(100); dump($num1->value, $num1->scale); // string(3) "100", int(0) // 字符串初始化,自动解析 scale $num2 = new Number('123.456'); dump($num2->value, $num2->scale); // string(3) "123.456", int(3) // 负数支持 $num3 = new Number('-789.00'); dump($num3->value, $num3->scale); // string(3) "-789.00", int(2) // 仅支持 int/string 类型参数,浮点数以字符串形式传入,传入无效数字字符串会抛出 ValueError //$num4 = new Number(0.1); // 错误:Argument #1 ($num) must be of type string|int, float given //$num5 = new Number('abc123'); // 错误:Argument #1 ($num) is not well-formed } }
核心算数运算:
加法:add($num, ?int $scale = null)
减法:sub($num, ?int $scale = null)
乘法:mul($num, ?int $scale = null)
除法:div($num, ?int $scale = null)
取模:mod($num, ?int $scale = null)
幂运算:pow($exponent, ?int $scale = null)
幂模运算:powmod($exponent, $modulus, ?int $scale = null)
平方根:sqrt(?int $scale = null)
商余运算:divmod($num, ?int $scale = null)
向上取整:ceil()
向下取整:floor()
四舍五入:round(int $precision = 0, RoundingMode $mode = RoundingMode::HalfAwayFromZero)
比较两个数值:compare($num, ?int $scale = null)
示例:
<?php declare (strict_types = 1); namespace app\controller; use app\BaseController; use BcMath\Number; class Index extends BaseController { public function index() { $num1 = new Number('5.5'); $num2 = new Number(5); $num3 = new Number('5.0'); // 求和 dump($num1->add($num2, 2)); // BcMath\Number对象 value = 10.50 (scale=2) dump($num1 + $num2); // BcMath\Number对象 value = 10.5 // 减法 dump($num1->sub($num2)); // BcMath\Number对象 value = 0.5 dump($num1 - $num2); // BcMath\Number对象 value = 0.5 // 乘法 dump($num1->mul($num2)); // BcMath\Number对象 value = 27.5 dump($num1 * $num2); // BcMath\Number对象 value = 27.5 // 除法 dump($num1->div($num2)); // BcMath\Number对象 value = 1.1 dump($num1 / $num2); // BcMath\Number对象 value = 1.1 // 取模 dump($num1->mod($num2)); // BcMath\Number对象 value = 0.5 dump($num1 % $num2); // BcMath\Number对象 value = 0.5 // 幂运算 dump($num2->pow(2));// BcMath\Number对象 value = 25 dump($num2 ** 2);// BcMath\Number对象 value = 25 // 平方根 dump(new Number(4)->sqrt()); // BcMath\Number对象 value = 2 // 商余运算 [$quotient, $remainder] = $num1->divmod($num2); dump($quotient, $remainder); // BcMath\Number对象 value : quotient = 1 remainder = 0.5 // 幂模运算 dump(new Number(4)->powmod($num2, 5)); // BcMath\Number对象 value = 4 // 相等比较 dump($num2 == $num3); // true // 严格比较 dump($num2 === $num3); // false // 不等比较 != 或 <> dump($num2 <> $num3); // false dump($num2 != $num3); // false dump($num2 !== $num3); // true // 小于比较、大于比较、小于等于比较、大于等于比较 dump($num1 > $num2); // true dump($num1 >= $num2); // true dump($num2 < $num3); // false dump($num2 <= $num3); // true /** 复杂计算 链式调用 */ // 计算订单总价(含折扣和税费) $price = new Number('99.99'); $quantity = new Number('3'); $discountRate = new Number('0.1'); // 10% 折扣 $taxRate = new Number('0.08'); // 8% 税费 $subtotal = $price->mul($quantity, 2); // 299.97 $discount = $subtotal->mul($discountRate, 2); // 29.997 → 30.00 $tax = ($subtotal->sub($discount))->mul($taxRate, 2); // 21.5976 → 21.60 $total = ($subtotal->sub($discount))->add($tax, 2); // 291.57 dump($total); } }
最佳实践:
确保 PHP 版本 ≥ 8.4,启用 BCMath 扩展(extension=bcmath)
始终使用字符串初始化小数
显式控制关键计算的精度,而非自动计算的 scale
链式调用简化复杂计算
利用不可变性进行安全计算
六、新的 array_*() 函数
PHP 8.4 正式引入了四个全新的数组操作函数:array_find()、array_find_key()、array_all() 和 array_any()。这些函数填补了 PHP 数组操作能力的空白,提供了更简洁、直观的方式来处理常见的数组查找和条件检查任务,避免了编写冗长的循环或使用不那么直观的现有函数组合。
1、array_find()
功能:查找第一个匹配元素。
语法:array_find(array $array, callable $callback): mixed。其中$array:待搜索的数组;$callback:回调函数,接收两个参数($value, $key),返回布尔值表示是否匹配
返回值:第一个使回调函数返回 true 的元素值,无匹配元素时返回 null
示例:
<?php $users = [ ['id' => 1, 'name' => 'Alice', 'active' => false], ['id' => 2, 'name' => 'Bob', 'active' => true], ['id' => 3, 'name' => 'Charlie', 'active' => true] ]; // 查找第一个活跃用户 active = true $firstActiveUser = array_find($users, fn($user) => $user['active']); // 查找名字以 'C' 开头的用户 $userWithCName = array_find($users, fn($user) => str_starts_with($user['name'], 'C')); // 查找不存在的条件 $inactiveUserOver30 = array_find($users, fn($user) => !$user['active'] && $user['id'] > 30); echo '<pre>'; var_dump($firstActiveUser); //输出:array(3) { ["id"]=> int(2) ["name"]=> string(3) "Bob" ["active"]=> bool(true) } var_dump($userWithCName); //输出:array(3) { ["id"]=> int(3) ["name"]=> string(7) "Charlie" ["active"]=> bool(true) } var_dump($inactiveUserOver30); // null
2、array_find_key()
功能:查找第一个匹配元素。
语法:array_find_key(array $array, callable $callback): mixed。其中$array:待搜索的数组;$callback:回调函数,接收两个参数($value, $key),返回布尔值表示是否匹配
返回值:第一个使回调函数返回 true 的元素键名(保留原始键类型),无匹配元素时返回 null
示例:
<?php $products = [ 'laptop' => ['price' => 999, 'in_stock' => true], 'phone' => ['price' => 699, 'in_stock' => false], 'tablet' => ['price' => 299, 'in_stock' => true] ]; // 查找第一个有库存的产品键名 $firstInStockKey = array_find_key($products, fn($products) => $products['in_stock']); // 查找价格低于 500 的产品键名 $affordableProductKey = array_find_key($products, fn($product) => $product['price'] < 500); echo '<pre>'; var_dump($firstInStockKey); // 输出:string(6) "laptop" var_dump($affordableProductKey); // 输出:string(6) "tablet"
3、array_all()
功能:检查所有元素是否满足条件。
语法:array_all(array $array, callable $callback): bool。其中$array:待搜索的数组;$callback:回调函数,接收两个参数($value, $key),返回布尔值表示是否匹配
返回值:所有元素都使回调函数返回 true 时返回 true;只要有一个元素使回调函数返回 false,立即返回 false(短路求值);空数组时返回 true
示例:
<?php $numbers = [2, 4, 6, 8, 10]; // 检查所有数字是否为偶数 $allEven = array_all($numbers, fn($n) => $n % 2 === 0); // 检查所有数字是否小于 5 $allSmall = array_all($numbers, fn($n) => $n < 5); // 空数组测试 $emptyCheck = array_all([], fn($n) => $n > 0); echo '<pre>'; var_dump($allEven); // 输出:bool(true) var_dump($allSmall); // 输出:bool(false) var_dump($emptyCheck); // 输出:bool(true)
4、array_any()
功能:检查是否存在满足条件的元素。
语法:array_any(array $array, callable $callback): bool。其中$array:待搜索的数组;$callback:回调函数,接收两个参数($value, $key),返回布尔值表示是否匹配
返回值:只要有一个元素使回调函数返回 true,立即返回 true(短路求值);所有元素都使回调函数返回 false 时返回 false;空数组时返回 false
示例:
<?php $emails = [ 'user1@example.com', 'user2@test.org', 'user3@domain.net' ]; // 检查是否存在以 .org 结尾的邮箱 $hasOrgEmail = array_any($emails, fn($email) => str_ends_with($email, '.org')); // 检查是否存在以 .gov 结尾的邮箱 $hasGovEmail = array_any($emails, fn($email) => str_ends_with($email, '.gov')); // 空数组测试 $emptyCheck = array_any([], fn($email) => str_ends_with($email, '.com')); var_dump($hasOrgEmail); // 输出:bool(true) var_dump($hasGovEmail); // 输出:bool(false) var_dump($emptyCheck); // 输出:bool(false)

浙公网安备 33010602011771号