文档知识库后端前端【免费下载链接】showdocShowDoc is a tool greatly applicable for an IT team to share documents online一个非常适合IT团队的在线API文档、技术文档工具项目地址https://gitcode.com/gh_mirrors/sh/showdoc点击查看免费下载本文以 ShowDoc 仓库自带的psr/http-message包文档 PSR7-Interfaces.md 为主体系统梳理 PSR-7 定义的七大 HTTP 消息接口MessageInterface、RequestInterface、ServerRequestInterface、ResponseInterface、StreamInterface、UriInterface、UploadedFileInterface的全部方法签名、语义与注意事项并结合仓库内接口源码与 ShowDoc 控制器的真实引用方式给出可直接落地的读写实践。读完本文你将掌握 PSR-7 接口的完整方法图谱理解其不可变消息设计哲学并能像 ShowDoc 后端一样在 Slim 风格的控制器里正确使用ServerRequestInterface与ResponseInterface。一、PSR-7 是什么一份接口速查的定位PSR-7 由 PHP-FIG 提出全称是HTTP message interfaces它为 PHP 生态中的 HTTP 消息请求与响应定义了统一的接口规范。其意义在于让不同的中间件、框架和库之间可以互换 HTTP 消息对象而不必关心具体实现。ShowDoc 的server目录通过 Composer 引入了psr/http-message包该包只负责定义接口interface不提供任何具体实现因此它既是规范本身也是项目代码中类型约束与依赖注入的基石。原文档 PSR7-Interfaces.md 的定位非常明确它是一份速查表cheatsheet用于帮助开发者在编写 PSR-7 代码时快速定位某个接口提供了哪些方法。PSR-7 共定义了以下七个接口Class NameDescriptionPsr\Http\Message\MessageInterface一个 HTTP 消息的表示请求与响应的公共抽象Psr\Http\Message\RequestInterface一个外发的、客户端侧的 HTTP 请求的表示Psr\Http\Message\ServerRequestInterface一个入站的、服务端侧的 HTTP 请求的表示Psr\Http\Message\ResponseInterface一个外发的、服务端侧的 HTTP 响应的表示Psr\Http\Message\StreamInterface描述一个数据流消息体Psr\Http\Message\UriInterface表示一个 URI 的值对象Psr\Http\Message\UploadedFileInterface表示一个通过 HTTP 请求上传的文件的值对象其中接口之间存在明确的继承关系RequestInterface、ServerRequestInterface、ResponseInterface都继承自MessageInterface因为请求和响应本质上是 HTTP 消息而ServerRequestInterface又继承自RequestInterface。这意味着当你拿到一个ServerRequestInterface对象时RequestInterface与MessageInterface的全部方法同样可用。这一点在 ShowDoc 中体现得淋漓尽致几乎每个 API 控制器都通过use Psr\Http\Message\ServerRequestInterface as Request;引入该接口作为方法参数类型约束例如 server/app/Common/BaseController.php。二、先理解核心原则消息的不可变性Immutability在使用任何with*方法之前必须先理解 PSR-7 最核心的设计原则。在 MessageInterface.php 的源码注释中明确写道Messages are considered immutable; all methods that might change state MUST be implemented such that they retain the internal state of the current message and return an instance that contains the changed state.即HTTP 消息被视为不可变对象所有可能改变状态的方法必须保留当前消息的内部状态并返回一个包含新状态的新实例。这带来一个非常实际的编码习惯差异$request-withHeader(...)并不会修改$request本身而是返回一个新对象。如果你忽略返回值修改将丢失。因此正确的写法是链式赋值$newRequest $request-withHeader(X-Token, abc123); // 原 $request 不受影响必须使用 $newRequest这一原则贯穿下文所有接口的with*方法也是 PSR-7 规范与普通可变对象最大的区别。在 ShowDoc 中控制器接收Request即ServerRequestInterface后通常直接读取参数而不做修改正是契合了这一不可变设计。三、MessageInterface所有 HTTP 消息的公共契约MessageInterface描述了请求与响应共有的部分协议版本、HTTP 头、消息体。速查表如下Method NameDescriptionNotesgetProtocolVersion()获取 HTTP 协议版本如1.0或1.1withProtocolVersion($version)返回设置了指定 HTTP 协议版本的新消息实例版本字符串只能包含版本号如1.1、1.0getHeaders()获取全部 HTTP 头返回以头名为键、值为字符串数组的关联数组保留原始大小写hasHeader($name)检查是否存在指定名称的 HTTP 头头名比较不区分大小写getHeader($name)获取单个头的全部值数组形式头不存在时返回空数组getHeaderLine($name)获取单个头的值拼接成的逗号分隔字符串头不存在时返回空字符串withHeader($name, $value)返回设置了指定 HTTP 头的新消息实例若原实例已存在该头则用新值替换withAddedHeader($name, $value)返回在指定头上追加值的新消息实例头已存在则追加值不存在则新建withoutHeader($name)移除指定名称的 HTTP 头不区分大小写getBody()获取 HTTP 消息体返回实现StreamInterface的对象withBody(StreamInterface $body)返回设置了指定消息体的新消息实例参数必须是StreamInterface对象源码级要点头是键 → 值数组的结构从 MessageInterface.php 的源码注释可以看到getHeaders()返回的是string[][]结构——每个头名对应一个字符串数组因为一个 HTTP 头可能有多个值如Set-Cookie。文档中的标准遍历写法是foreach ($message-getHeaders() as $name $values) { echo $name . : . implode(, , $values); }同时要注意头名虽然在 HTTP 协议层面不区分大小写但getHeaders()会保留头在设置时的原始大小写。这也是为什么hasHeader()/getHeader()的查找必须做大小写无关匹配。头操作的三种典型差异withHeader(X-Name, a)替换——如果原来有X-Name新实例中只有awithAddedHeader(X-Name, b)追加——新实例中X-Name的值为[a, b]getHeader(X-Name)返回[a, b]而getHeaderLine(X-Name)返回a, b。四、RequestInterface客户端侧请求RequestInterface继承自MessageInterface因此拥有 MessageInterface 的全部方法并额外增加以下与请求语义相关的方法Method NameDescriptionNotesgetRequestTarget()获取消息的请求目标request-target四种形式origin-form、absolute-form、authority-form、asterisk-form定义于 RFC7230withRequestTarget($requestTarget)返回设置了指定请求目标的新消息实例如GET /path?query HTTP/1.1中的/path?querygetMethod()获取请求的 HTTP 方法GET、HEAD、POST、PUT、DELETE、CONNECT、OPTIONS、TRACERFC7231PATCHRFC5789withMethod($method)返回设置了指定 HTTP 方法的新消息实例getUri()获取 URI 实例返回UriInterfacewithUri(UriInterface $uri, $preserveHost false)返回设置了指定 URI 的新消息实例第二个参数控制是否保留原 Host 头关键参数语义request-target 与 $preserveHostrequest-target是请求行中紧随方法名之后的那一段最常见的是 origin-form即路径 查询串如/api/user?id1也允许 absolute-form完整 URL、authority-form仅主机部分用于 CONNECT和 asterisk-form*用于 OPTIONS。withUri()的$preserveHost参数值得特别留意当传入的 URI 带 Host 且原消息也有 Host 头时$preserveHost true会保留原消息的 Host 头false默认则用新 URI 的 Host 更新 Host 头。若新 URI 没有 Host无论该参数如何原 Host 头都会被保留。五、ServerRequestInterface服务端入站请求这是 ShowDoc 后端最常用的接口——它表示服务端收到的请求除了继承RequestInterface与MessageInterface的全部方法外还封装了 PHP 超级全局变量的语义。速查表如下Method NameDescriptionNotesgetServerParams()获取服务器参数通常来源于$_SERVERgetCookieParams()获取客户端发送给服务器的 Cookies通常来源于$_COOKIEwithCookieParams(array $cookies)返回设置了指定 Cookies 的新请求实例不会同步修改 Cookie 头withQueryParams(array $query)返回设置了指定查询字符串参数的新请求实例不会修改 URI 本身getUploadedFiles()获取规范化的文件上传数据叶子节点为UploadedFileInterface实例withUploadedFiles(array $uploadedFiles)返回设置了指定上传文件的新请求实例结构非法时抛InvalidArgumentExceptiongetParsedBody()获取请求体中的参数POST 表单返回$_POST内容可为null/数组/对象withParsedBody($data)返回设置了指定请求体参数的新请求实例只接受数组、对象或nullgetAttributes()获取由请求派生的全部属性应用自定义的附加数据getAttribute($name, $default null)获取单个派生属性不存在时返回默认值withAttribute($name, $value)返回设置了指定派生属性的新请求实例withoutAttribute($name)返回移除了指定派生属性的新请求实例与 PHP 超级全局变量的对应关系从 ServerRequestInterface.php 的源码注释可以清晰看到其设计映射getServerParams()↔$_SERVER表示请求到达应用时的环境状态规范要求视为不可变因此接口没有提供修改 server params 的方法getCookieParams()↔$_COOKIE查询字符串参数 ↔$_GET或通过parse_str()解析上传文件 ↔$_FILES请求体参数 ↔$_POST针对application/x-www-form-urlencoded与multipart/form-data且方法为 POST 的场景。两个容易混淆的点withQueryParams()不会改 URI源码注释明确指出Setting query string arguments MUST NOT change the URI stored by the request因此若你想让 URI 的 query 同步变化需要直接对getUri()返回的 URI 调用withQuery()再withUri()。attributes 是应用层的便签attributes 用于存放由请求派生的数据例如路由匹配结果、解密后的 Cookie、反序列化的请求体等。它是最灵活的机制——getAttribute($name, $default)的存在也让接口无需单独提供hasAttribute()。在中间件链路中前一环通过withAttribute()写入的数据后一环可以用getAttribute()读取这是框架路由注入参数的标准手法。六、ResponseInterface服务端响应ResponseInterface继承自MessageInterface拥有其全部方法并额外增加状态码相关的方法Method NameDescriptionNotesgetStatusCode()获取响应状态码如 200、404、500withStatus($code, $reasonPhrase )返回设置了指定状态码可选原因短语的新响应实例非法状态码抛InvalidArgumentExceptiongetReasonPhrase()获取与状态码关联的原因短语如OK、Not Found状态码与原因短语的关系HTTP 响应行由协议版本 状态码 原因短语组成例如HTTP/1.1 200 OK。getReasonPhrase()返回的就是OK这部分。在withStatus($code, $reasonPhrase )中若省略$reasonPhrase实现方通常会使用状态码对应的标准短语若传入自定义短语如withStatus(418, Im a teapot)则会覆盖默认值。由于响应也是不可变对象设置状态码同样必须接收返回值。在 ShowDoc 的控制器中ResponseInterface被用作方法返回类型例如BaseController中use Psr\Http\Message\ResponseInterface as Response;控制器方法以Response $response参数接收响应对象写入内容后原样返回。七、StreamInterface可读写的数据流HTTP 消息体在 PSR-7 中不是字符串而是StreamInterface对象。它抽象了文件句柄、内存缓冲等底层资源提供统一的光标式读写操作。方法速查如下Method NameDescriptionNotes__toString()从头到尾读取流中全部数据为字符串close()关闭流及底层资源detach()使流与底层资源分离之后流处于不可用状态getSize()获取流的大小如果已知未知时返回nulleof()是否已到流末尾isSeekable()流是否可定位seek($offset, $whence SEEK_SET)将读写指针定位到指定位置$whence默认SEEK_SETrewind()将指针定位到流开头等价于seek(0)isWritable()流是否可写write($string)向流中写入数据isReadable()流是否可读read($length)从流中读取指定长度的数据getContents()将剩余内容读为字符串受当前指针位置影响getMetadata($key null)获取流的元数据关联数组或指定键如uri、mode最容易踩的坑指针位置getContents()只读取从当前指针到末尾的内容而不是从头开始。这意味着写入了内容之后直接调用getContents()很可能会得到空字符串或残缺内容因为指针已经停在写入位置。正确姿势是先rewind()$body $response-getBody(); $body-write(hello); // 指针已停在末尾 $body-rewind(); // 回到开头 $text $body-getContents(); // hello同理若调用$body-seek(1)后再getContents()第一个字符会被跳过。这也是官方文档推荐统一使用rewind()的原因。八、UriInterfaceURI 值对象URI 在 PSR-7 中是一个不可变的值对象提供对各组成部分的读取与替换式修改with*方法同样返回新实例。速查表如下Method NameDescriptiongetScheme()获取 URI 的 scheme 组件如http、httpsgetAuthority()获取 authority 组件user:passhost:portgetUserInfo()获取用户信息组件getHost()获取主机组件getPort()获取端口组件未显式指定时返回nullgetPath()获取路径组件getQuery()获取查询字符串不含?getFragment()获取片段fragment不含#withScheme($scheme)返回设置了指定 scheme 的新实例withUserInfo($user, $password null)返回设置了指定用户信息的新实例withHost($host)返回设置了指定主机的新实例withPort($port)返回设置了指定端口的新实例withPath($path)返回设置了指定路径的新实例withQuery($query)返回设置了指定查询字符串的新实例withFragment($fragment)返回设置了指定 fragment 的新实例__toString()返回完整的 URI 引用字符串authority 的构成getAuthority()是 URI 中user:passhost:port这一整段的组合其中端口只有当端口非默认端口时才应当出现。它是中间件判断当前请求访问哪个主机的最直接入口。with*系列方法withScheme、withHost等都遵循不可变原则修改 URI 的推荐链式写法是$uri $request-getUri() -withScheme(https) -withHost(api.example.com) -withPath(/v2/users); $newRequest $request-withUri($uri);九、UploadedFileInterface上传文件UploadedFileInterface表示通过 HTTP 请求上传的文件$_FILES的规范化表示它提供了移动文件与读取元信息的完整方法集Method NameDescriptiongetStream()获取表示已上传文件的流StreamInterfacemoveTo($targetPath)将上传文件移动到新位置getSize()获取文件大小getError()获取与上传文件关联的错误码对应 PHP 的UPLOAD_ERR_*getClientFilename()获取客户端发送的文件名getClientMediaType()获取客户端发送的媒体类型使用要点moveTo 与 getStream 二选一moveTo($targetPath)是消费上传文件的标准方式它会将上传的临时文件移动而非复制到目标路径移动完成后流即不可用而getStream()则适合在需要原地读取文件内容如解析、校验时使用。需要持久化存储时务必优先调用moveTo()并且建议使用服务端生成的路径而不要直接信任getClientFilename()返回的客户端文件名那是客户端声明的原始文件名可能包含路径或恶意字符。十、ShowDoc 中的真实应用控制器如何消费 PSR-7PSR-7 接口在 ShowDoc 中不是躺在 vendor 里的规范纸而是后端控制器体系的事实标准。psr/http-message包通过 composer.json 以 PSR-4 方式自动加载Psr\Http\Message\→src/并要求 PHP 版本^7.2 || ^8.0。在 server/app/Common/BaseController.php 中可以看到基础控制器的引入方式use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ResponseInterface as Response;这种as Request/as Response的别名写法在 ShowDoc 的数十个 API 控制器中保持一致例如 MockController.php、AgentController.php、ImportSwaggerController.php 等从 server/app/Api/Controller 目录的源码看所有 API 控制器均遵循此模式。控制器方法签名通常为public function index(Request $request, Response $response, $args) { // 通过 $request 读取参数、头、上传文件 // 通过 $response 写入响应体 }这与 Slim 等 PSR-7 中间件框架的约定完全一致方法参数里接收请求、返回响应。得益于 PSR-7 的统一抽象ShowDoc 的控制器既可以运行在 Slim 容器中也可以在测试与 Mock 场景下注入任意实现了ServerRequestInterface/ResponseInterface的对象这正是接口规范带来的可替换性红利。十一、动手实践基于 PSR-7 接口的常用操作结合本包的另一份文档 PSR7-Usage.md 中的示例所有实现 PSR-7 的包行为一致以下假设$request是RequestInterface实例、$response是ResponseInterface实例可以快速掌握日常高频操作1. 操作 HTTP 头// 给响应添加头 $response $response-withHeader(My-Custom-Header, My Custom Message); // 向已存在的头追加值 $response $response-withAddedHeader(My-Custom-Header, The second message); // 检查头是否存在 $request-hasHeader(My-Custom-Header); // false只加在了响应上 $response-hasHeader(My-Custom-Header); // true // 取逗号拼接的值 $request-getHeaderLine(Content-Type); // text/html; charsetUTF-8 $response-getHeaderLine(My-Custom-Header); // My Custom Message; The second message // 取数组形式的值 $request-getHeader(Content-Type); // [text/html, charsetUTF-8] // 移除头 $request $request-withoutHeader(Content-MD5); // 移除废弃头 $response $response-withoutHeader(Content-Length); // 移除后浏览器将流式下载直到结束注意所有with*/without*都返回新实例必须接住返回值。2. 读写消息体两种风格方式一先取出 body 再操作适合多次读写避免$response-write()这类误用$body $response-getBody(); // 对 body 进行读写、seek 等操作…… $response $response-withBody($body); // 可选因为传的是同一对象引用方式二直接在响应上链式操作适合只做一两次操作$response-getBody()-write(hello);3. 读取 body 内容务必先 rewind$body $response-getBody(); $body-rewind(); // 或 $body-seek(0); $bodyText $body-getContents();原因在于getContents()从当前指针位置读到末尾若之前有写入指针停在末尾直接读取会得到空内容若执行了seek(1)再读则会丢掉第一个字符。4. 向 body 前置内容prepend流的写入是从指针位置覆盖式写入直接前置需要借助中间字符串// 假设 body 当前内容为 abcd $body $response-getBody(); $body-rewind(); $contents $body-getContents(); // abcd $body-rewind(); $body-write(ef); // 流内变为 efcd $body-write($contents); // 流内变为 efabcd更稳妥的写法是全部拼接成字符串后再写回$body $response-getBody(); $body-rewind(); $contents ef . $body-getContents(); $body-rewind(); $body-write($contents); // 流内变为 efabcd十二、结语把速查表变成肌肉记忆PSR-7 的七个接口共同构建了 PHP 生态 HTTP 消息的通用语言MessageInterface定义公共骨架RequestInterface/ServerRequestInterface/ResponseInterface分别刻画客户端请求、服务端请求与响应StreamInterface与UriInterface则作为消息体与地址的值对象被复用UploadedFileInterface规范了文件上传的消费方式。记住两个贯穿始终的原则即可事半功倍一是所有with*方法返回新实例不可变性二是流的读写受指针位置影响先rewind()再getContents()。如需深入可直接阅读本仓库中的接口源码 server/vendor/psr/http-message/src每个接口的 PHPDoc 都写明了精确的 MUST/返回值/异常约束对照 ShowDoc 的 BaseController.php 与各 API 控制器观察真实消费方式再配合 PSR7-Usage.md 中的示例动手验证即可在实际项目中熟练运用这套行业标准接口。赞分享文档知识库后端前端【免费下载链接】showdocShowDoc is a tool greatly applicable for an IT team to share documents online一个非常适合IT团队的在线API文档、技术文档工具项目地址https://gitcode.com/gh_mirrors/sh/showdoc点击查看免费下载相关推荐ShowDoc 中的 PSR-7 HTTP Message 接口规范从接口方法速查到底层实现实战ShowDoc 中的 PSR 7 HTTP Message 接口规范从接口方法速查到底层实现实战 PSR 7PHP FIG 制定的 HTTP Message文档知识库后端前端Guzzle与PSR-7标准现代PHP HTTP消息接口最佳实践Guzzle与PSR 7标准现代PHP HTTP消息接口最佳实践 你是否还在为PHP项目中的HTTP请求处理感到困扰不同HTTP客户端库之间的兼容性问题、消后端探索 Nyholm/psr7: PHP PSR-7 HTTP 消息接口实现探索 Nyholm/psr7: PHP PSR 7 HTTP 消息接口实现 在现代PHP开发中遵循统一的接口和标准是提高代码可读性、可维护性和互操作性的关键。上一篇ibus-libpinyin社区贡献指南如何参与开源项目下一篇pywebview文件对话框终极指南如何快速实现打开、保存和文件夹选择功能创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
