编程 PHP 8.6 流错误处理:错误变成值对象,但 `@` 仍然删不掉

2026-10-22 21:03:21

PHP 8.6 流错误处理:错误变成值对象,但 @ 仍然删不掉

参考:Michał Woliński, PHP 8.6: every feature, before and after(实测于 8.6.0RC3)
原文链接:https://wolinski.com/blog/php-8-6-new-features.html

旧写法:一个全局槽位加一段需要自己解析的字符串

在 PHP 8.5 及之前,流操作失败的标准处理方式是 @ 加 error_get_last():

// PHP 8.5
$data = @file_get_contents($url);
if (false === $data) {
    $error = error_get_last(); // one global slot, clobbered by anything else
    echo $error['message'] ?? 'unknown';
}

这里有两个问题。error_get_last() 只有一个全局槽位,同一请求里任何别的 warning 都会把它覆盖掉;从它拿到的又是一个字符串,还得自己解析。

新写法:配置写在流 context 里,错误是列表

PHP 8.6 把行为配置放到每个流 context 上,失败原因则变成了值对象:

// PHP 8.6
$context = stream_context_create(['stream' => [
    'error_mode' => StreamErrorMode::Silent, // or Error, or Exception
    'error_store' => StreamErrorStore::All, // or Auto, None, NonTerminating, Terminating
]]);
$data = @file_get_contents($url, false, $context);
$errors = stream_last_errors(); // a list of StreamError objects

在一台无法完成域名解析的主机上实测:调用返回 false,stream_last_errors() 返回一个对象,其 code 为 StreamErrorCode::OpenFailed(78 种情况之一),message 里带着解析器自己的原始措辞。把 error_mode 换成 Exception,同一个失败就会抛 StreamException。stream_clear_errors() 用于重置存储。

需要注意选项的位置:必须写在 context 的 stream wrapper 之下。写成 stream_context_create(['error_mode' => ...]) 会被拒绝,直接报 Options should have the form ["wrappername"]["optionname"] = $value。

Silent 不等于可以去掉 @

这一点是实测结果,和模式名字给人的印象不同。

本地路径不存在时,Silent 是真静默:不产生 warning,失败信息只进存储。

域名解析失败时就不一样了。warning 仍然先从网络层出来,流本身还没意识到自己已经失败:

Warning: file_get_contents(): php_network_getaddresses: getaddrinfo for ... failed

即使设了 Silent,这条 warning 照样出现,同时错误也被存进存储。原因是解析器走的是普通错误通道,那部分不归流错误机制管。

所以准确的说法是:这套 API 替换的是 error_get_last() —— 那个无法归因到具体操作的全局槽位,并给出结构化的原因;它并不替换网络调用上的抑制策略。

判定表

失败来源Silent 下是否还出 warning存储里的 code 与 messageException 模式还需要 @ 吗
本地路径不存在无OpenFailed抛 StreamException不需要
域名无法解析有,网络层先报 php_network_getaddresses / getaddrinfo 失败OpenFailed,message 为解析器原文抛 StreamException(warning 仍先出现)需要

顺带:socket 选项搬进了流 context

同一次发布还加了几个以前要用 socket_* 函数或 ini 设置才能配的 socket 选项:so_reuseaddr、so_keepalive(配合 tcp_keepidle / tcp_keepintvl / tcp_keepcnt)、so_linger、so_rcvbuf / so_sndbuf。

要注意 context 对不认识的选项是一声不响直接丢弃的。8.5 上 so_keepalive 被静默忽略(SO_KEEPALIVE 读回 0),so_rcvbuf 也保持 131072 字节的默认值不动;8.6 上 keepalive 读回 1,so_rcvbuf => 8192 得到 16384 字节缓冲(内核做了取整)。

复制全文 生成海报 PHP 错误处理 流 升级兼容

推荐文章

程序员茄子在线接单