Guzzle异步基于Promise而非async关键字,需用wait()或then()处理;中间件须通过HandlerStack注册;PSR-18不支持其异步和中间件特性;修改请求体应创建新Stream避免重放问题。

guzzle 的异步请求不是靠 async 关键字实现的
PHP 本身不支持真正的多线程异步,Guzzle 的「异步」实际是基于 ReactPHP 或 amphp 等事件循环库的 Promise 驱动模型。调用 $client->getAsync() 返回的是一个 Promise 对象,而非立即执行的响应。
常见错误是直接对返回值调用 getStatusCode() —— 这会报错,因为 Promise 尚未 resolve。
- 必须用
$promise->wait()同步等待(开发调试可用,但阻塞) - 更合理的方式是链式调用
then()和otherwise()处理成功/失败分支 - 多个异步请求应统一用
Promise\settle()或Promise\all()协调,避免手动轮询
中间件拦截器必须在 Client 初始化时注册
Guzzle 的中间件(Middleware)本质是函数,接收 RequestInterface 和 callable $next,返回 Promise<responseinterface></responseinterface>。它不会自动生效,必须通过 HandlerStack 注入到客户端中。
容易踩的坑是:写了中间件函数,却没把它加进 handler stack,结果请求完全不受影响。
立即学习“PHP免费学习笔记(深入)”;
- 正确做法是先获取默认栈:
$handler = HandlerStack::create(); - 再用
$handler->push($middleware, 'log');注册,第二个参数是可选名称,仅用于调试标识 - 最后把该 handler 传给
Client构造函数:new Client(['handler' => $handler]) - 顺序很重要:越早 push 的中间件,越靠近请求发起端(即最先执行)
PSR-18 兼容层不能直接使用 Guzzle 的 Promise API
PSR-18 规范只定义了同步的 sendRequest() 方法,返回 ResponseInterface。如果你用的是 PSR-18 客户端抽象(比如 Http\Client\HttpClient),那它底层可能是 Guzzle,也可能是其他实现 —— 但你无法假设它支持 getAsync() 或中间件。
这意味着:一旦你选择依赖 PSR-18 接口,就主动放弃了 Guzzle 特有的异步和中间件能力。
- 想保留异步能力?必须直接依赖
GuzzleHttp\Client,绕过 PSR-18 抽象 - 想兼顾可替换性与扩展性?可以封装一层适配器,在需要时降级为同步调用,或按需切换底层实现
- PSR-18 的价值在于解耦,不是功能增强;强行在 PSR-18 接口上叠加 Guzzle 特性,反而破坏契约
中间件里修改请求体要小心 Stream 的不可重放性
Guzzle 的 RequestInterface 中的 body 是一个 Psr\Http\Message\StreamInterface。很多中间件想在发送前重写 body(比如加签名、加密),但直接 $request->getBody()->write() 可能失败 —— 因为 stream 可能是只读的、已 EOF、或不支持 rewind。
典型现象是:第一次请求正常,第二次报 Cannot write to a closed stream 或静默丢数据。
- 安全做法是用
Utils::streamFor($newContent)创建新 stream 替换 body - 若需复用原 body 内容,先
->__toString()或->getContents()读出字符串,再构造新 stream - 避免在中间件里反复
rewind()原 stream,某些封装(如LazyOpenStream)不保证支持



















