各位 PHP 精神病学家,各位在屎山代码里摸爬滚打多年的幸存者,大家下午好。
我知道,现在的你们正盯着屏幕上的报错红字,像是盯着那只试图在午夜从你家马桶飞出去的蟑螂。别怕,那只蟑螂是 PHP 8.4,它不仅飞不了多远,而且——它现在要被扫地出门了。
今天我们不谈特性,不谈语法糖,我们来谈谈“尸体”。具体来说,是 PHP 8.4 中那些即将被推入乱葬岗的废弃特性。如果你的项目要在 2026 年之前幸存,或者说,如果你的项目能在 2026 年之前不被运维部门扔进服务器机架,你就必须学会如何和这些废弃特性“割席”。
准备好了吗?让我们开始解剖这具名为“PHP 8.4”的巨型鲸鱼。
第一部分:弱引用的幽灵 —— WeakReference 的谢幕
首先,我们要谈谈 WeakReference。在 PHP 8.4 之前,WeakReference 是那些试图在内存管理领域搞点“青蒿素”的架构师的玩具。它的初衷很简单:我想引用一个对象,但我绝对、绝对不想让垃圾回收器(GC)因为我引用了它,而把它留在人间。
这听起来很完美,对吧?就像是你想跟前任保持一种“偶尔联系,但绝不结婚”的微妙关系。
但在实际工程中,WeakReference 是个什么体验呢?它像是一个拿着望远镜看戏的变态。你创建它,你使用它,但如果对象被 GC 了,你的引用瞬间变成 null。这就是它的悲剧。
在 PHP 8.4 中,它被彻底废弃了。
为什么?因为 PHP 引入了一个更优雅的解决方案 —— WeakMap。
1. 为什么 WeakReference 总是让你心梗?
让我们看看为什么 WeakReference 被嫌弃。在旧版本中,如果你想在对象中存储关于另一个对象的信息,且不阻止其回收,你必须这么做:
class Cache {
// 使用 WeakReference 的旧方式,痛苦指数:⭐⭐⭐⭐⭐
private WeakReference $ref;
public function set($obj) {
$this->ref = WeakReference::create($obj);
}
public function get() {
// 这里的逻辑很尴尬:你总是要检查它是否存在
if ($this->ref) {
return $this->ref->get();
}
return null;
}
}
注意那个 if ($this->ref)。你总是得检查它是不是空的,因为如果对象被回收了,它就是空的。这就像是你买了保险,但每次理赔前都要确认保险公司倒闭没。而且,WeakReference 有个更糟糕的兄弟叫 __wakeup,如果在反序列化时返回 null,这玩意儿直接就废了,连报错的机会都没有。
2. WeakMap:强强联合的 CP
PHP 8.4 推出了 WeakMap。这不仅仅是改名,这是架构的进化。WeakMap 允许你将数据存储在对象和键之间,而不会阻止垃圾回收。但最妙的是,它不需要你去手动检查 null。
class Registry {
// PHP 8.4 新方式,优雅指数:⭐⭐⭐⭐⭐
private WeakMap $map;
public function __construct() {
$this->map = new WeakMap();
}
public function attach($obj, string $data) {
// 直接赋值,不需要担心 GC,不需要检查 null
$this->map[$obj] = $data;
}
public function getData($obj) {
// 如果对象被回收了,访问 $this->map[$obj] 会直接返回 null,
// 但代码逻辑非常干净,不需要额外的 if 判断
return $this->map[$obj] ?? null;
}
}
工程扫描建议:
如果你的代码里现在还在用 WeakReference,尤其是配合 __wakeup 使用的情况,请立刻停止。不要试图修复它,直接把它扔进 WeakMap 里。WeakMap 的性能实际上更好,因为它不需要反复调用 get() 方法去取那个脆弱的引用。
第二部分:丑陋的前缀 —— 全局 mysqli_* 函数的死刑
接下来,我们要面对的是那个曾经让所有人头痛的数据库连接库 —— mysqli。
在 PHP 的历史上,mysqli 一直试图从 mysql(那个早已死亡的亲戚)中独立出来。但 PHP 的全局命名空间就像一个塞满了过期罐头的冰箱,什么都要往里塞。于是,mysqli_connect、mysqli_query 这些函数全都被扔到了全局命名空间里。
这在过程式编程时代还好,但在现代 PHP(特别是 8.0+)里,这简直是灾难。你把面向对象的 PDO 和面向过程/命名空间的 mysqli 混在一起用,代码看起来就像是一个穿着燕尾服的猪头。
PHP 8.4 终于决定:动手吧,清理厨房。
PHP 8.4 将所有以 mysqli_ 开头的全局函数(如 mysqli_connect, mysqli_query, mysqli_fetch_all 等)全部标记为 Deprecated(废弃)。
这意味着,在未来的版本(可能是 PHP 9.0)中,这些函数将彻底消失。
1. 旧代码的噩梦
请看这段典型的“屎山”代码:
// 这里的代码在 PHP 8.4 中会发出刺耳的废弃警告
$mysqli = mysqli_connect('localhost', 'root', 'password', 'database');
if (!$mysqli) {
die('连接失败');
}
$result = mysqli_query($mysqli, 'SELECT * FROM users WHERE id = 1');
if ($result) {
while ($row = mysqli_fetch_assoc($result)) {
echo $row['name'] . '<br>';
}
mysqli_free_result($result);
}
这段代码不仅在语法上令人作呕,而且在现代 PHP 的标准中是错误的。你用了命名空间却不用它,这就像买了 iPhone 却坚持用诺基亚的按键操作逻辑。
2. 重构方案:拥抱命名空间
PHP 8.4 的推荐做法是:显式使用 mysqli 命名空间。
// PHP 8.4 新方式:显式调用命名空间
$mysqli = mysqliconnect('localhost', 'root', 'password', 'database');
if (!$mysqli) {
// 使用命名空间中的错误处理函数
trigger_error(mysqliconnect_error(), E_USER_ERROR);
}
$result = mysqliquery($mysqli, 'SELECT * FROM users WHERE id = 1');
if ($result) {
while ($row = mysqlifetch_assoc($result)) {
echo $row['name'] . '<br>';
}
mysqlifree_result($result);
}
工程扫描建议:
面对这种大规模的废弃,光靠眼睛看是不行的。
- 全局搜索: 在你的 IDE 中搜索
mysqli_connect、mysqli_query、mysqli_fetch等关键字。 - 正则替换: 这是一个重体力活。你需要把
mysqli_替换为mysqli。 use语句: 如果你的文件很长,或者你频繁调用这些函数,建议在文件顶部写上use function mysqliconnect;然后直接用connect(...)。这会让你的代码看起来既现代又整洁。
第三部分:DOM 的棺材钉 —— DOMDocument::saveHTML 的终结
Web 开发中充满了妥协。我们用 XML 的结构存 HTML,用 HTML 的标签存 JSON。DOMDocument 是处理这些妥协的利器,但 PHP 对 HTML 的支持一直很尴尬。
PHP 8.4 废弃了两个非常常见的 DOM 方法:DOMDocument::saveHTML() 和 DOMDocument::saveHTMLFile()。
为什么?因为这两个方法在处理 HTML 时经常产生不一致的行为。它们试图把 DOM 节点序列化为 HTML,但往往会把 XML 命名空间或者怪异的 HTML5 标签搞得一团糟。
1. 旧代码的遗留
很多旧的 CMS 或者数据导出系统还在用这个:
$dom = new DOMDocument();
$dom->loadHTML('<html><body><h1>Hello World</h1></body></html>');
// 这行代码在 PHP 8.4 会废弃
$htmlString = $dom->saveHTML();
echo $htmlString;
虽然它跑得通,但它的行为在某些边缘情况下是不可预测的。而且 saveHTMLFile 更是多余的,因为它内部调用了 saveHTML 然后再 file_put_contents。
2. 重构方案:回归 saveXML
如果你需要输出 HTML 字符串,最安全、最标准、最符合 XML 规范的做法是使用 DOMDocument::saveXML()。
$dom = new DOMDocument();
$dom->loadHTML('<html><body><h1>Hello World</h1></body></html>');
// 推荐使用 saveXML,它明确告诉你这是 XML 输出
$htmlString = $dom->saveXML($dom->documentElement);
// 如果你真的只需要保存文件
$dom->saveXML($dom->documentElement, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
file_put_contents('output.html', $htmlString);
工程扫描建议:
检查你的 DOM 处理逻辑。如果你在 DOMDocument 上使用 saveHTML,请立刻替换为 saveXML。虽然这可能会稍微改变输出(比如在 html 标签外多包一层),但这实际上是对输出结果更严格的控制,避免了浏览器解析时的意外行为。
第四部分:时间与日期的陷阱 —— DateTime 的细节
PHP 处理日期时间的库一直是个笑话。但现在,我们得在这个笑话里补个丁了。
PHP 8.4 废弃了 DateTime::createFromFormat 在某些特定情况下抛出 Exception 的行为。等等,这听起来像是个好事情吧?抛出异常不是很好吗?
实际上,旧的行为是:如果 createFromFormat 无法创建日期,它会返回 false。但为了“现代”和“面向对象”,PHP 决定让它返回 null(在 PHP 8.0+)。
但在 PHP 8.4 中,如果你试图在 createFromFormat 返回 false 或 null 时捕获 Exception,这将会被废弃。这就像是你试图去接一个根本不存在的抛物线一样。
1. 危险的代码模式
// 这种代码在 PHP 8.4 中会废弃
$date = DateTime::createFromFormat('Y-m-d', 'invalid-date');
if ($date === false) {
// 假设错误处理
} else {
// ...
}
或者试图捕获一个不应该被抛出的异常:
try {
$date = DateTime::createFromFormat('Y-m-d', 'invalid-date');
} catch (Exception $e) {
// 这段代码在 PHP 8.4 中会废弃
// 因为 createFromFormat 不会抛出异常,它返回 false/null
}
2. 重构方案:检查布尔值
DateTime::createFromFormat 是一个静态工厂方法,它的行为就是:成功返回对象,失败返回 false。这就是 PHP 的约定。除非你开启了严格模式,否则不要试图让它变得“更严格”。
$date = DateTime::createFromFormat('Y-m-d', 'invalid-date');
if ($date === false) {
// 现在的写法是正确的,检查返回值,而不是异常
trigger_error('日期格式错误', E_USER_WARNING);
} else {
echo $date->format('Y-m-d');
}
第五部分:国际化与数组的怪癖
除了上述重头戏,PHP 8.4 还清理了一些角落里的垃圾。
1. IntlDateFormatter 的废弃
IntlDateFormatter 在某些参数上的行为变得严格了。如果你在创建格式化器时使用了过时的参数顺序,或者某些已经废弃的 ICU 版本特性,PHP 会提示你。
检查你的代码:
// 确保你的 ICU 扩展是最新版,并检查参数列表
$formatter = new IntlDateFormatter(
'en_US',
IntlDateFormatter::FULL,
IntlDateFormatter::FULL,
'America/New_York'
);
2. array_key_exists vs isset
虽然这不是 PHP 8.4 废弃的,但在迁移过程中,你会发现 array_key_exists 在某些语境下被 isset 替代了。PHP 8.4 的错误报告级别提高了,如果你在一个数组不存在的键上使用 array_key_exists(实际上 isset 已经返回 false 了),可能会触发一些更严格的警告。
工程扫描建议:
全局搜索 array_key_exists,并审视它的上下文。如果它用于检查 null 值,请改用 isset。如果它真的需要检查 null 值,请保留。这是一个微妙的区别,但对于代码的健康度至关重要。
总结与行动清单(不写总结,只有行动)
好了,各位工程师,我们的时间到了。PHP 8.4 的这些废弃特性就像是体检报告上的异常指标,它们不是绝症,但如果不处理,迟早会出大事。
为了防止 2026 年你的系统在生产环境崩溃,请拿出你的 IDE,开始以下工作:
- 清理弱引用:找出所有
WeakReference,替换为WeakMap。这是内存优化的好机会。 - 清理数据库连接:找出所有
mysqli_*函数调用。进行一次大规模的正则替换,加上use语句。别让全局函数污染你的命名空间。 - 清理 DOM 输出:把
saveHTML换成saveXML。让输出更干净,让浏览器更听话。 - 清理日期逻辑:检查
DateTime::createFromFormat的错误处理。别试图捕获一个不存在的异常。
记住,代码写得再烂,只要跑得动就能维持现状。但当你面对 PHP 8.4 的“大清洗”时,如果你不主动去清理,那就是系统在清理你——用蓝屏死机来作为最后的告别。
别让 PHP 8.4 成为你职业生涯的墓志铭,把它变成你系统进化的阶梯。现在,拿起键盘,干活吧!