- 后端
- Web框架
【免费下载链接】symfony
The Symfony PHP framework
Symfony 的 PSR-7 Bridge(symfony/psr-http-message-bridge)是连接 Symfony 自身 HTTP 消息模型(HttpFoundation的Request/Response)与 PSR-7/PSR-17 标准消息模型(ServerRequestInterface/ResponseInterface等)的官方桥接组件。本文以该组件的 CHANGELOG.md 为主线,结合仓库内源码、接口定义与功能测试,完整梳理它的核心转换能力、控制器集成方式、安装要求与版本演进脉络,帮助你理解这套双向转换机制并直接用于实战。
一、桥接层全景:四个核心角色
从源码结构看,该 Bridge 由四个可独立使用的构件组成,全部位于src/Symfony/Bridge/PsrHttpMessage/目录下:
| 构件 | 源码位置 | 职责 |
|---|---|---|
PsrHttpFactory | Factory/PsrHttpFactory.php | 将 Symfony 的Request/Response转换为 PSR-7 消息(Symfony → PSR-7) |
HttpFoundationFactory | Factory/HttpFoundationFactory.php | 将 PSR-7 的ServerRequestInterface/ResponseInterface转换为 Symfony 消息(PSR-7 → Symfony) |
PsrServerRequestResolver | ArgumentValueResolver/PsrServerRequestResolver.php | 控制器参数注入:把 PSR-7 请求对象直接作为控制器方法参数 |
PsrResponseListener | EventListener/PsrResponseListener.php | 控制器返回值转换:自动把控制器返回的 PSR-7 响应转回 Symfony 响应 |
两个工厂分别实现 HttpMessageFactoryInterface(createRequest()/createResponse())与 HttpFoundationFactoryInterface(同样是一对createRequest()/createResponse(),方向相反)。CHANGELOG 中多次出现的"工厂"演进(DiactorosFactory→PsrHttpFactory)、"流式请求/响应"支持、"上传文件桥接"等,都落实在这两个工厂及其配套的 Factory/UploadedFile.php 上。
二、安装与环境要求(以当前仓库为准)
根据该 Bridge 的 composer.json:
- PHP 版本:
>= 8.4.1; - 运行时依赖:
psr/http-message^1.0|^2.0(即同时兼容 PSR-7 契约 v1 与 v2,对应 CHANGELOG 2.2.0 中 "Support version 2 of the psr/http-message contracts");symfony/http-foundation^7.4|^8.0; - 开发依赖:
nyholm/psr7 ^1.1(PSR-7/PSR-17 的参考实现)、php-http/discovery ^1.15(PSR-17 工厂自动发现)、symfony/framework-bundle、symfony/http-kernel、symfony/event-dispatcher、symfony/runtime等; - 冲突声明:
php-http/discovery低于1.15会冲突(对应 CHANGELOG 6.4 中引入的php-http/discovery自动发现能力)。
由于创建 PSR-7 消息必须依赖 PSR-17 工厂接口(ServerRequestFactoryInterface、StreamFactoryInterface、UploadedFileFactoryInterface、ResponseFactoryInterface),典型安装命令为(源码中LogicException提示原文):
composer require php-http/discovery psr/http-factory-implementation:*安装php-http/discovery后,即使不显式传入工厂实例,PsrHttpFactory也会自动探测可用的 PSR-17 实现(详见下一节)。
三、Symfony → PSR-7:PsrHttpFactory的转换细节
PsrHttpFactory的构造函数接收四个可选的 PSR-17 工厂:
new PsrHttpFactory( ?ServerRequestFactoryInterface $serverRequestFactory, ?StreamFactoryInterface $streamFactory, ?UploadedFileFactoryInterface $uploadedFileFactory, ?ResponseFactoryInterface $responseFactory, );3.1 工厂自动探测(6.4 新能力)
当任意一个工厂参数为null时,源码会按以下顺序自动选择 PSR-17 实现:
- 若
Http\Discovery\Psr17Factory存在(安装了php-http/discovery),使用DiscoveryPsr17Factory; - 否则若
Nyholm\Psr7\Factory\Psr17Factory存在,使用NyholmPsr17Factory; - 两者都不存在则抛出
LogicException,并提示执行composer require php-http/discovery psr/http-factory-implementation:*。
这正是 CHANGELOG 6.4 中 "Supportphp-http/discoveryfor auto-detecting PSR-17 factories" 的源码落地:四个工厂参数缺一即可触发探测,探测成功后四个接口共用同一个 PSR-17 工厂实例。
3.2createRequest():完整映射表
核心方法createRequest(Request $symfonyRequest): ServerRequestInterface的转换逻辑(对应 PsrHttpFactory.php):
| Symfony 输入 | PSR-7 输出 | 实现要点 |
|---|---|---|
| 方法 + URI + 服务器参数 | createServerRequest(method, uri, serverParams) | URI 由getSchemeAndHttpHost() + getBaseUrl() + getPathInfo()拼接,并按需追加?+QUERY_STRING |
| 请求头 | withHeader() | 逐个头设置,捕获InvalidArgumentException后静默忽略非法头(对应 2.1.3 修复) |
| 请求体 | withBody() | 用createStreamFromResource($symfonyRequest->getContent(true))包装原始资源 |
| 解析后请求体 | withParsedBody() | 若 Content-Type 为 JSON,json_decode(..., JSON_BIGINT_AS_STRING)解析且仅接受数组结果;否则回退为$request->request->all()(表单参数)。这一分支正是 2.3.0 "LeverageRequest::getPayload()" 与 2.3.1 "Don't rely onRequest::getPayload()" 反复打磨的位置 |
| 上传文件 | withUploadedFiles() | 递归处理$files数组:null值转为UPLOAD_ERR_NO_FILE的空上传;UploadedFile实例用流、大小、错误码、原始文件名与 MIME 类型构建 PSR-7 上传文件 |
| Cookies / 查询参数 / 属性 | withCookieParams()/withQueryParams()/withAttribute() | 属性逐个写入,保持路由等附加数据不丢失 |
3.3createResponse():响应转换
createResponse(Response $symfonyResponse): ResponseInterface的处理逻辑(PsrHttpFactory.php):
- 状态码与状态文本来自
Response::$statusTexts; - 二进制文件响应(
BinaryFileResponse,且响应头中没有Content-Range)直接createStreamFromFile()文件路径,避免整文件读入内存; - 其余情况写入
php://temp临时流;StreamedResponse与BinaryFileResponse通过ob_start()输出缓冲逐段捕获sendContent()的内容,普通响应则直接$stream->write($response->getContent()); - 响应头逐一写入,同样忽略非法头;Cookies 被序列化为多个
Set-Cookie头;最后withProtocolVersion()保留 HTTP 协议版本。
其中 "BinaryFileResponse with Content-Range" 走临时流分支,正是 CHANGELOG 2.0.2 中修复内容的源码体现。
四、PSR-7 → Symfony:HttpFoundationFactory的转换细节
方向相反的 HttpFoundationFactory.php 提供两个方法,均可选bool $streamed = false参数(对应 CHANGELOG 1.2.0 "Added support for streamed responses" 与 1.3.0 "Added support for streamed requests")。
4.1createRequest():组装 Symfony 请求
- 服务器参数:从 PSR-7 URI 推导
SERVER_NAME、SERVER_PORT(https默认 443,否则 80)、REQUEST_URI(路径 + 查询串)、QUERY_STRING;https时置HTTPS=on;方法写入REQUEST_METHOD;最终array_replace($psrRequest->getServerParams(), $server)以 URI 推导值为准覆盖原始参数——这正是 2.0.1 "Fix populating default port and headers" 与 2.0.2 "Fix populating server params from URI" 修复的领域; - 请求体:
streamed=true时通过detach()移交流资源(不整体读入内存),否则__toString()一次性读取; - 表单参数:
getParsedBody()仅当结果为数组时才作为请求体参数(is_array($parsedBody) ? $parsedBody : []),避免非数组解析结果污染$_POST语义; - 上传文件:递归把 PSR-7 上传文件转成 Symfony 的
UploadedFile(详见下方); - 属性 / Cookies / 查询参数:分别来自
getAttributes()、getCookieParams()、getQueryParams();请求头通过$request->headers->add($psrRequest->getHeaders())整体注入。
4.2 上传文件桥接:Factory/UploadedFile.php
仓库在 Factory/UploadedFile.php 中提供了一个继承自Symfony\Component\HttpFoundation\File\UploadedFile的桥接类,对应 CHANGELOG 1.3.0 "Fixed bridging UploadedFile objects":
- 构造函数接收 PSR-7 的
UploadedFileInterface与一个"临时路径回调"(HttpFoundationFactory默认用tempnam(sys_get_temp_dir(), 'symfony')生成); - 若上传错误为
UPLOAD_ERR_NO_FILE则路径留空;若流的元数据 URI 不是字符串或并非真实上传文件(is_uploaded_file()为假,常见于测试环境),则判定为 test 模式并先moveTo()到临时路径; - 重写的
move()方法在"有效且非 test 模式"时直接委托给$psrUploadedFile->moveTo(),把移动文件的最终动作交还 PSR-7 实现,并设置chmod(0o666 & ~umask())。
4.3createResponse():PSR-7 响应转回 Symfony
- 先把
Set-Cookie头从 PSR-7 响应中剥离,随后通过Cookie::fromString()逐一还原为 Symfony 的Cookie对象并setCookie()(对应 2.0.2 "Create cookies as raw"); streamed=true时构造StreamedResponse,回调内先rewind()(若可 seek),再按responseBufferMaxLength(默认 16372 字节,构造函数可配置)分块echo $body->read()直到eof(),实现流式输出;非流式则用getBody()->__toString()构造普通Response;- 最后
setProtocolVersion()同步 HTTP 协议版本。
五、控制器集成:请求注入 + 响应自动转换
CHANGELOG 2.1.0 一次性引入了两个关键集成点:PsrResponseListener(自动转换控制器返回的 PSR-7 响应)与PsrServerRequestResolver(允许向控制器注入 PSR-7 请求对象)。它们让桥接从"手动调用工厂"升级为"控制器零样板代码"。
5.1PsrServerRequestResolver:参数注入
该类实现的是 Symfony 6.2 引入的ValueResolverInterface(CHANGELOG 2.3.0 记录),并在 6.4 中移除了旧的ArgumentValueResolverInterface实现("RemoveArgumentValueResolverInterfacefromPsrServerRequestResolver")。源码中维护一张受支持类型表:
private const SUPPORTED_TYPES = [ ServerRequestInterface::class => true, RequestInterface::class => true, MessageInterface::class => true, ];resolve()方法在参数类型命中上述三者之一时,yield $this->httpMessageFactory->createRequest($request)——即自动把当前 Symfony 请求交给HttpMessageFactoryInterface转换后注入控制器。
仓库功能测试夹具 PsrRequestController.php 展示了三种典型用法:
// 注入最具体的 ServerRequestInterface public function serverRequestAction(ServerRequestInterface $request): ResponseInterface { return $this->responseFactory->createResponse() ->withBody($this->streamFactory->createStream( sprintf('<html><body>%s</body></html>', $request->getMethod()) )); } // 注入 RequestInterface,读取方法与请求体 public function requestAction(RequestInterface $request): ResponseInterface { return $this->responseFactory->createResponse()->withStatus(403) ->withBody($this->streamFactory->createStream( sprintf('<html><body>%s %s</body></html>', $request->getMethod(), $request->getBody()->getContents()) )); } // 注入更宽泛的 MessageInterface,读取请求头 public function messageAction(MessageInterface $request): ResponseInterface { return $this->responseFactory->createResponse()->withStatus(422) ->withBody($this->streamFactory->createStream( sprintf('<html><body>%s</body></html>', $request->getHeader('X-My-Header')[0]) )); }5.2PsrResponseListener:响应自动转换
PsrResponseListener订阅KernelEvents::VIEW(PsrResponseListener.php):当控制器返回值instanceof ResponseInterface时,通过HttpFoundationFactoryInterface(未注入时默认new HttpFoundationFactory())将其转为 Symfony 响应并setResponse();返回值不是 PSR-7 响应则直接放行。
5.3 功能测试佐证
仓库 ControllerTest.php 通过 WebTestCase 验证了完整链路:
GET /server-request→ 200,响应体为GET(注入ServerRequestInterface);POST /request(带some content请求体)→ 403,响应体为POST some content(注入RequestInterface并读取流);PUT /message(带X-My-Header: some content)→ 422,响应体为some content(注入MessageInterface并读取头)。
PsrResponseListenerTest.php 则单独验证:控制器返回 PSR-7Response时事件被置入响应;返回数组或null时不产生响应。
六、版本演进时间线:从 1.0.0 到 6.4
CHANGELOG 完整记录了三个主要阶段的演进,逐条展开如下:
6.1 1.x:奠基与工厂化
- 1.0.0 / 1.0.1 / 1.0.2:初始发布,支持 Symfony 4(由 dunglas 贡献);修复 PSR-7 Request 的 request target。
- 1.1.0:新增基于PSR-17 工厂创建 PSR-7 消息的能力——即
PsrHttpFactory的核心思路:不再依赖某个具体 PSR-7 实现类,而是通过四个标准工厂接口构建消息。 - 1.1.1:弃用
DiactorosFactory,建议改用PsrHttpFactory(此时尚不触发弃用告警)。 - 1.1.2:修复
createResponse。 - 1.2.0:最低 PHP 升至 7.1;新增流式响应支持(即
HttpFoundationFactory::createResponse(..., true)的StreamedResponse分支);补充文档链接。 - 1.3.0:最低 Symfony 升至 4.4、支持 Symfony 5.0+;新增流式请求支持(
createRequest(..., true)的detach()分支);修复UploadedFile对象的桥接(见上文 4.2)。
6.2 2.x:契约升级与控制器集成
- 2.0.0:正式移除
DiactorosFactory,PsrHttpFactory成为唯一入口。 - 2.0.1:不再规范化查询字符串(
PsrHttpFactory);修复 HTTPS 请求的转换;修复默认端口与请求头的填充(HttpFoundationFactory)。 - 2.0.2:修复
HttpFoundationFactory从 URI 填充服务器参数;Cookies 以 raw 形式创建;修复带Content-Range的BinaryFileResponse(PsrHttpFactory)。 - 2.1.0:新增
PsrResponseListener(自动转换控制器返回的 PSR-7 响应)与PsrServerRequestResolver(向控制器注入 PSR-7 请求对象)——详见本文第五节。 - 2.1.2:允许 Symfony 6。
- 2.1.3:创建 PSR-7 对象时忽略非法 HTTP 头;修复传入
moveTo()的错误类型。 - 2.2.0:放弃 Symfony 4;最低 PHP 升至 7.2;支持
psr/http-message契约 v2。 - 2.3.0:利用
Request::getPayload()填充 PSR-7 请求的解析后请求体;实现 Symfony 6.2 引入的ValueResolverInterface。 - 2.3.1:不再依赖
Request::getPayload()填充解析后请求体(回归到基于 Content-Type 分支的解析策略,见 3.2 的说明)。
6.3 6.4:并入 monorepo 与生态化
- 6.4:Bridge 被导入 Symfony 官方 monorepo 并同步发布节奏;从
PsrServerRequestResolver移除ArgumentValueResolverInterface(完全转向ValueResolverInterface);支持php-http/discovery自动探测 PSR-17 工厂(见 3.1),并在 composer.json 中声明与php-http/discovery < 1.15冲突。
七、边界修复清单:值得在集成时注意的细节
把 CHANGELOG 中的修复条目整理为一张"避坑清单",每一条都能在源码或测试中找到对应实现:
| 修复条目(版本) | 涉及模块 | 实战含义 |
|---|---|---|
| 忽略非法 HTTP 头(2.1.3) | 两个工厂的withHeader() | 转换时遇到非法头名不会抛异常中断,而是静默跳过 |
| 不规范化查询字符串(2.0.1) | PsrHttpFactory | 查询串原样保留,避免被意外重写 |
| HTTPS 请求转换(2.0.1) | HttpFoundationFactory | 从 URI scheme 推导SERVER_PORT与HTTPS标记 |
| 默认端口与请求头填充(2.0.1) | HttpFoundationFactory | https默认端口 443,否则 80,与请求头一起补全 |
| 从 URI 填充服务器参数(2.0.2) | HttpFoundationFactory | URI 推导值通过array_replace覆盖 PSR-7 原始 serverParams |
| Cookies 以 raw 创建(2.0.2) | HttpFoundationFactory | Set-Cookie头通过Cookie::fromString()原样还原 |
Content-Range的BinaryFileResponse(2.0.2) | PsrHttpFactory | 带Content-Range时不再直接映射文件流,改走临时流逐段发送 |
修复传入moveTo()的类型(2.1.3) | 上传文件桥接 | moveTo()参数类型修正,避免把目录当文件目标 |
| 解析后请求体的两次调整(2.3.0 / 2.3.1) | PsrHttpFactory::createRequest() | JSON 请求用JSON_BIGINT_AS_STRING解码且仅接受数组,其余回退表单参数 |
八、升级路径与使用建议
结合 CHANGELOG 与 composer.json 的约束,给出几条可直接落地的实践建议:
- 老代码迁移:若仍引用
DiactorosFactory,需要按 1.1.1 的弃用提示与 2.0.0 的移除动作,统一迁移到PsrHttpFactory;依赖 PSR-17 工厂接口,不绑定具体实现。 - 控制器风格统一:在基于该 Bridge 的控制器中,统一"注入 PSR-7 请求 + 返回 PSR-7 响应",由
PsrServerRequestResolver与PsrResponseListener自动完成双向转换;注意参数类型可以是ServerRequestInterface、RequestInterface或更宽的MessageInterface,按需选择。 - 流式场景:大文件上传/下载优先开启
streamed选项(HttpFoundationFactory的流式请求用detach()、流式响应按 16372 字节分块输出),避免整体载入内存。 - 版本匹配:当前仓库要求 PHP
>= 8.4.1、psr/http-message ^1.0|^2.0、symfony/http-foundation ^7.4|^8.0;若要使用 PSR-17 工厂自动发现,需安装php-http/discovery且版本不低于 1.15。更详细的运行约束可查看仓库内的 composer.json 与 phpunit.xml.dist(测试套件配置)。
总体而言,该 Bridge 的价值在于:它让 Symfony 应用既能在内部保留HttpFoundation的成熟生态,又能与遵循 PSR-7/PSR-17 的中间件、客户端与框架自由互操作——双向工厂负责数据映射,参数解析器与视图监听器负责把桥接能力无缝接入控制器层,而 CHANGELOG 中每一次版本迭代都在收敛边界行为、升级契约并降低接入成本。
- 后端
- Web框架
【免费下载链接】symfony
The Symfony PHP framework
相关推荐
ShowDoc 中的 PSR-7 HTTP 消息实战:psr/http-message 消息头与流式消息体操作指南
ShowDoc 中的 PSR 7 HTTP 消息实战:psr/http message 消息头与流式消息体操作指南 导读 本文基于 ShowDoc 仓库内 ps
文档知识库后端前端OpenCart 中的 PSR-7 接口契约:psr/http-message 版本演进与 HTTP 消息接口体系解析
OpenCart 中的 PSR 7 接口契约:psr/http message 版本演进与 HTTP 消息接口体系解析 导读 :本文围绕 OpenCart 仓库
电商后端OpenCart 中的 PSR-7 标准:psr/http-message 接口包全解与 HTTP 消息编程实战
OpenCart 中的 PSR 7 标准:psr/http message 接口包全解与 HTTP 消息编程实战 本篇技术指南围绕 OpenCart 仓库中随
电商后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考