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 与 message | Exception 模式 | 还需要 @ 吗 |
|---|---|---|---|---|
| 本地路径不存在 | 无 | 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 字节缓冲(内核做了取整)。