PHP 8.6 的 Stream 错误 API:把 E_WARNING 换成可查询的 StreamError
PHP 的 Stream API 长期被社区吐槽“乱”,最直接的一点是错误处理:失败时只有一条 E_WARNING 加一个 false 返回值,想知道到底哪一步出错,只能去匹配 warning 文本。PHP 8.6 引入了一套一致的 Stream 错误模式、错误类型和异常,让 stream 层的失败变成可以查询的结构化数据。
这套 API 采用 opt-in 设计,默认行为不变,库会逐步跟进采用。
新增的类、枚举和函数
类
StreamError:单个错误的载体,把一次失败描述成结构化对象,而不是一段提示字符串。StreamException:异常形态的 Stream 错误,在异常模式下抛出。
枚举
StreamErrorStore:控制错误存放在哪里。StreamErrorMode:控制错误以什么形式呈现,比如继续E_WARNING、记录到错误存储,还是抛出StreamException。StreamErrorCode:把失败原因归类成可比较的枚举值,不必再去匹配 warning 文本。
函数
stream_last_errors():取回记录下来的 Stream 错误。stream_clear_errors():清空错误记录。
控制方式:三个 stream context 选项
行为通过 stream context 配置,而不是全局开关:
error_mode:错误模式,对应StreamErrorMode。error_store:错误存放策略,对应StreamErrorStore。error_handler:错误发生时的回调。
下面的片段按 API 名称书写,枚举常量名与函数签名以 PHP 8.6 官方文档为准:
$ctx = stream_context_create([
'stream' => [
'error_mode' => /* StreamErrorMode 中的异常模式 */,
'error_store' => /* StreamErrorStore 中的存储策略 */,
],
]);
$fp = @fopen('tls://example.com:443', 'r', false, $ctx);
if ($fp === false) {
foreach (stream_last_errors() as $err) {
// $err 是 StreamError,可拿到错误码与失败上下文
error_log((string) $err);
}
}
为什么是 opt-in
Stream 相关调用遍布现有代码,如果默认就抛异常,大量依赖 @fopen(...) === false 判断的老代码会被直接打断。因此 8.6 保持默认行为不变:不配置 error_mode 时仍然是警告加 false。想要结构化错误,才在 context 里显式打开。
典型用法
fopen / fsockopen 失败
打开失败是最常见场景。开启异常模式后,失败不再是一句 warning:
$ctx = stream_context_create([
'stream' => ['error_mode' => /* 抛异常模式 */],
]);
try {
$fp = fopen('tls://example.com:443', 'r', false, $ctx);
} catch (StreamException $e) {
// 拿到的是 StreamException,可读取其 StreamError 信息
}
fsockopen() 同理,连接失败的原因可以从错误对象里取,而不是从 warning 文本里猜。
fwrite 短写
短写在旧接口下只体现为返回值小于请求长度,原因不可见。开启错误存储后,短写对应的错误会被记录下来,可以事后用 stream_last_errors() 取回并按 StreamErrorCode 分支处理。
TLS 握手失败
TLS 握手失败以前尤其难查,warning 文本信息有限。新 API 下握手失败会以结构化的 StreamError 呈现,配合 StreamErrorCode 可以直接判断失败类别,再决定是重试、降级还是报错。
错误存储的生命周期
错误按 stream 存储,不会自动消失:
- 需要显式调用
stream_clear_errors()清空。 - 长时间保持的连接里,如果持续产生可恢复错误,记录会不断堆积。
- 在循环里复用同一个 stream 时,建议每轮处理完就清一次,避免读到上一轮的错误。
和 error_handler 回调一起用时
error_handler 在错误发生的路径上被调用,回调里不要再触发 stream 操作。否则会在错误处理过程中再次进入 stream 层,产生递归或额外的错误记录,让本来要排查的问题变得更难定位。回调里只做记录、计数、标记这类不涉及 I/O 的动作。
同版本中的其它 stream 相关变化
- 新增
filter.max_filter_count上下文选项:限制php://filter的过滤器数量,超限时打开失败并给出警告;负值关闭该检查。 - 被过滤的 stream 可以 cast 成文件描述符,交给
stream_select使用。 bz2、iconv、zlib、convert过滤器新增write_seek_mode参数,取值为preserve、reset、strict。- socket context 选项新增:
so_reuseaddr、so_keepalive、tcp_keepidle、tcp_keepintvl、tcp_keepcnt、so_linger、so_rcvbuf、so_sndbuf。 - OpenSSL stream 支持 TLS session resumption(
session_data、session_cache、session_timeout等选项)以及 TLS 1.3 early data。