PHP 8.6 的 Stream 错误 API:stream 层的 warning 终于能直接拿到了
为什么以前难写
fopen()、stream_socket_client()、file_get_contents() 这类调用失败时,往往不是抛异常,而是发一条 E_WARNING 然后返回 false。异常可以 try/catch,warning 不行——过去只能挂 set_error_handler(),在回调里根据错误消息字符串猜是哪一步出的问题,再想办法把控制流从回调里“跳”出来。消息文本不是稳定接口,多个 stream 操作嵌套时也很难判断警告属于谁。
PHP 8.6 给 stream 层补了一套错误 API,把这类错误变成可检查的状态。
新增的类、枚举与函数
- 类:
StreamError、StreamException - 枚举:
StreamErrorStore、StreamErrorMode、StreamErrorCode - 函数:
stream_last_errors()、stream_clear_errors()
stream_last_errors() 取回最近记录的错误(返回 StreamError 集合),stream_clear_errors() 清空记录。具体的方法签名和枚举成员取值以官方文档为准,这里不展开猜测。
这套机制通过 stream context 的三个选项控制:
| 选项 | 作用 |
|---|---|
error_mode | 错误上报方式,取值由 StreamErrorMode 决定 |
error_store | 错误存到哪里,对应 StreamErrorStore |
error_handler | 自定义错误处理回调 |
配置示例(枚举成员请按文档填写):
$ctx = stream_context_create([
'error_mode' => $mode, // StreamErrorMode 成员,见文档
'error_store' => $store, // StreamErrorStore 成员,见文档
'error_handler' => function ($error) {
// 自定义处理,例如记录或转换
},
]);
$fp = fopen('/path/to/file', 'r', false, $ctx);
if ($fp === false) {
foreach (stream_last_errors() as $error) {
// 消费结构化错误
}
stream_clear_errors();
}
注意 error_mode 与 error_handler 的关系决定了错误是“抛出 StreamException”还是“仅记录”,混用前先确认模式语义。
php://filter 的过滤器数量上限
新增 filter.max_filter_count 选项,用于 php://filter URL:当 URL 上挂的过滤器数量超过该值时,打开失败并抛出 warning。负值表示禁用检查。
$ctx = stream_context_create([
'filter' => ['max_filter_count' => 16],
]);
$fp = fopen('php://filter/read=convert.base64-encode/resource=/etc/hosts', 'r', false, $ctx);
这条对接受外部输入的过滤器链很有用——过去无限制的 filter 链本身就是一类攻击面。
socket context 的新选项
so_reuseaddr:允许关闭地址复用(SO_REUSEADDR),在 Windows 上则显式使用 SO_EXCLUSIVEADDRUSE。需要独占端口时用它。
so_keepalive、tcp_keepidle、tcp_keepintvl、tcp_keepcnt:设置 TCP keepalive 及相关参数(空闲时间、探测间隔、探测次数)。
so_linger:设置 TCP 的 SO_LINGER。正数表示开启并延迟该秒数;0 或负数表示关闭。超过 65535 的值会被截断,因为部分平台上 linger 时间是无符号短整型。
so_rcvbuf、so_sndbuf:设置 TCP/UDP socket 的接收/发送缓冲区字节数(SO_RCVBUF、SO_SNDBUF)。取值必须是 1 到 2147483647 之间的整数,其他值会导致 stream 创建失败。操作系统可能对请求值取整、截断或做其他调整,并且可能不再自动调整该缓冲区大小,因此读回的值和请求值可能不一致。
$ctx = stream_context_create([
'socket' => [
'so_reuseaddr' => false,
'so_keepalive' => true,
'tcp_keepidle' => 60,
'tcp_keepintvl' => 10,
'tcp_keepcnt' => 3,
'so_linger' => 5,
'so_rcvbuf' => 262144,
'so_sndbuf' => 262144,
],
]);
$fp = stream_socket_client('tcp://127.0.0.1:9000', $errno, $errstr, 5, STREAM_CLIENT_CONNECT, $ctx);
调缓冲区时不要假设设置值等于实际值,需要的话用 stream_get_meta_data() 或系统工具核对。
另外两条
- 过滤后的 stream 现在可以转换为文件描述符,用于
stream_select()。 bz2、iconv、zlib、convert过滤器新增write_seek_mode参数,必须通过关联数组传入,键为"write_seek_mode",取值为"preserve"、"reset"、"strict"之一。
以上内容依据迁移文档整理;枚举成员、函数返回值结构等细节以官方文档和实际版本行为为准。