咳咳,各位观众老爷们,晚上好!我是你们今晚的讲师,今天咱们聊聊PHP的PSR-7 HTTP Message接口和Middleware模式,保证让大家听完之后,功力大增,Bug数量直线下降!
一、 啥是PSR-7? 别慌,先喝口水!
首先,什么是PSR-7? 简单来说,它是一套关于HTTP消息(请求和响应)的标准接口。 这个标准的目的,是为了让不同的PHP框架和库能够更和谐地共存,不再像以前那样,你用Symfony的Request,我用Laravel的Request,大家谁也不认识谁,沟通起来费劲。
PSR-7定义了两个核心接口:
PsrHttpMessageRequestInterface
: 代表HTTP请求。PsrHttpMessageResponseInterface
: 代表HTTP响应。
它们就像两张通行证,只要你实现了这两个接口,就可以在不同的框架和库之间自由穿梭,再也不用担心“语言不通”的问题了。
二、RequestInterface:告诉我你想干啥!
RequestInterface
包含了HTTP请求的所有信息,比如:
- 请求方法(GET, POST, PUT, DELETE等)
- URI (Uniform Resource Identifier,统一资源标识符)
- 请求头 (Headers)
- 请求体 (Body)
- HTTP协议版本
让我们看看 RequestInterface
的一些核心方法:
方法名 | 返回值 | 作用 |
---|---|---|
getMethod() |
string |
获取请求方法 (GET, POST, etc.) |
withMethod(string $method) |
RequestInterface |
返回一个修改了请求方法的新实例 (Request对象是不可变的) |
getUri() |
UriInterface |
获取URI对象 |
withUri(UriInterface $uri, bool $preserveHost = false) |
RequestInterface |
返回一个修改了URI的新实例 (Request对象是不可变的),$preserveHost 决定是否保留原始Host头 |
getHeaders() |
array |
获取所有请求头 |
getHeader(string $name) |
string[] |
获取指定名称的请求头 |
hasHeader(string $name) |
bool |
检查是否存在指定名称的请求头 |
withHeader(string $name, $value) |
RequestInterface |
返回一个添加了指定请求头的新实例 (Request对象是不可变的) |
withoutHeader(string $name) |
RequestInterface |
返回一个移除了指定请求头的新实例 (Request对象是不可变的) |
getBody() |
StreamInterface |
获取请求体 (Stream对象,后面会讲) |
withBody(StreamInterface $body) |
RequestInterface |
返回一个修改了请求体的新实例 (Request对象是不可变的) |
getProtocolVersion() |
string |
获取HTTP协议版本 (例如 "1.1") |
withProtocolVersion(string $version) |
RequestInterface |
返回一个修改了协议版本的新实例 (Request对象是不可变的) |
getRequestTarget() |
string |
获取请求目标 (一般是URI的路径部分) |
withRequestTarget(string $requestTarget) |
RequestInterface |
返回一个修改了请求目标的新实例 (Request对象是不可变的) |
代码示例:创建一个Request对象
<?php
use NyholmPsr7Request;
use NyholmPsr7Uri;
// 创建一个URI对象
$uri = new Uri('https://example.com/api/users?page=1');
// 创建一个Request对象,指定请求方法和URI
$request = new Request('GET', $uri);
// 添加一个请求头
$request = $request->withHeader('X-Custom-Header', 'My Value');
// 获取请求方法和URI
echo "Method: " . $request->getMethod() . PHP_EOL;
echo "URI: " . $request->getUri() . PHP_EOL;
// 获取请求头
echo "Custom Header: " . $request->getHeaderLine('X-Custom-Header') . PHP_EOL;
// 打印整个Request对象 (仅用于演示,实际情况可能不同)
echo $request;
重点:Request对象是不可变的!
注意到了吗? 所有的 with*
方法都会返回一个新的 RequestInterface
实例。 这是因为PSR-7要求Request对象是不可变的。 这样做的好处是,可以避免在传递Request对象时,不小心修改了它的状态,导致程序出现意想不到的错误。 你可以想象成,你手里拿着一个冰淇淋,每次你想加点巧克力酱或者草莓酱,都会得到一个新的冰淇淋,而原来的冰淇淋还是原来的样子,不会被你弄脏。
三、ResponseInterface:告诉我结果如何!
ResponseInterface
包含了HTTP响应的所有信息,比如:
- 状态码 (Status Code)
- 响应头 (Headers)
- 响应体 (Body)
- HTTP协议版本
让我们看看 ResponseInterface
的一些核心方法:
方法名 | 返回值 | 作用 |
---|---|---|
getStatusCode() |
int |
获取HTTP状态码 (例如 200, 404, 500) |
withStatus(int $code, string $reasonPhrase = '') |
ResponseInterface |
返回一个修改了状态码的新实例 (Response对象是不可变的) |
getReasonPhrase() |
string |
获取状态码的描述信息 (例如 "OK", "Not Found") |
getHeaders() |
array |
获取所有响应头 |
getHeader(string $name) |
string[] |
获取指定名称的响应头 |
hasHeader(string $name) |
bool |
检查是否存在指定名称的响应头 |
withHeader(string $name, $value) |
ResponseInterface |
返回一个添加了指定响应头的新实例 (Response对象是不可变的) |
withoutHeader(string $name) |
ResponseInterface |
返回一个移除了指定响应头的新实例 (Response对象是不可变的) |
getBody() |
StreamInterface |
获取响应体 (Stream对象,后面会讲) |
withBody(StreamInterface $body) |
ResponseInterface |
返回一个修改了响应体的新实例 (Response对象是不可变的) |
getProtocolVersion() |
string |
获取HTTP协议版本 (例如 "1.1") |
withProtocolVersion(string $version) |
ResponseInterface |
返回一个修改了协议版本的新实例 (Response对象是不可变的) |
代码示例:创建一个Response对象
<?php
use NyholmPsr7Response;
use NyholmPsr7Stream;
// 创建一个Stream对象作为响应体
$body = Stream::create('Hello, PSR-7!');
// 创建一个Response对象,指定状态码和响应体
$response = new Response(200, ['Content-Type' => 'text/plain'], $body);
// 添加一个响应头
$response = $response->withHeader('X-Custom-Header', 'My Value');
// 获取状态码和响应体
echo "Status Code: " . $response->getStatusCode() . PHP_EOL;
echo "Body: " . $response->getBody() . PHP_EOL;
// 打印整个Response对象 (仅用于演示,实际情况可能不同)
echo $response;
重点:Response对象也是不可变的!
和Request对象一样,Response对象也是不可变的。 每次修改都会返回一个新的实例。
四、StreamInterface:数据的河流!
StreamInterface
代表一个数据流,可以用于读取和写入数据。 它是Request和Response的Body的载体。
StreamInterface
的一些常用方法:
方法名 | 返回值 | 作用 |
---|---|---|
__toString() |
string |
将流中的所有数据读取为字符串 |
close() |
void |
关闭流 |
detach() |
resource |
将流与其底层资源分离,并返回该资源。 |
getSize() |
?int |
获取流的大小(以字节为单位),如果大小未知,则返回 null 。 |
tell() |
int |
返回流的当前位置。 |
seek(int $offset, int $whence = SEEK_SET) |
void |
将流指针移动到指定位置。 $whence 可以是 SEEK_SET (从流的开始位置计算),SEEK_CUR (从当前位置计算)或 SEEK_END (从流的结束位置计算)。 |
rewind() |
void |
将流指针移动到流的开始位置。 |
isSeekable() |
bool |
确定流是否可搜索。 |
isWritable() |
bool |
确定流是否可写。 |
isReadable() |
bool |
确定流是否可读。 |
write(string $string) |
int |
将字符串写入流。 返回写入的字节数。 |
read(int $length) |
string |
从流中读取指定长度的字符串。 |
getContents() |
string |
将流中的剩余内容读取为字符串。 |
getMetadata(string $key = null) |
array |mixed |null |
获取流的元数据。 如果没有提供键,则返回所有元数据作为数组。 |
代码示例:使用Stream对象
<?php
use NyholmPsr7Stream;
// 创建一个Stream对象
$stream = Stream::create('This is some data.');
// 读取流中的数据
echo "Data: " . $stream->getContents() . PHP_EOL;
// 将流指针移动到开始位置
$stream->rewind();
// 再次读取流中的数据
echo "Data (again): " . $stream->read(5) . PHP_EOL; // 只读取前5个字节
// 写入数据到流
$stream->seek(0, SEEK_END); //移动到流末尾,否则会覆盖原有内容
$stream->write(' More data!');
$stream->rewind();
echo "Full Data: " . $stream->getContents() . PHP_EOL;
五、Middleware:HTTP请求的流水线!
现在,我们来聊聊Middleware模式。 想象一下,你的HTTP请求要经过一个流水线,流水线上有很多工人(Middleware),每个工人负责处理请求的某一部分,比如:
- 身份验证 (Authentication)
- 授权 (Authorization)
- 日志记录 (Logging)
- 请求参数验证 (Request Validation)
- 响应内容压缩 (Response Compression)
每个Middleware都接收一个Request对象,并返回一个Response对象。 它可以修改Request对象,也可以直接返回一个Response对象。
MiddlewareInterface:定义Middleware的接口
<?php
namespace PsrHttpServer;
use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;
use PsrHttpServerRequestHandlerInterface;
interface MiddlewareInterface
{
/**
* Process an incoming server request.
*
* Processes an incoming server request in order to produce a response.
* If unable to produce the response itself, it may delegate to the provided
* request handler to do so.
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface;
}
process()
方法是Middleware的核心,它接收一个ServerRequestInterface
对象和一个RequestHandlerInterface
对象,并返回一个ResponseInterface
对象。ServerRequestInterface
继承自RequestInterface
, 专门用来处理服务器端的请求。RequestHandlerInterface
负责处理Request,并返回一个Response。 它可以是另一个Middleware,也可以是最终的处理函数(Controller)。
RequestHandlerInterface:最终的请求处理器
<?php
namespace PsrHttpServer;
use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;
interface RequestHandlerInterface
{
/**
* Handles a request and produces a response.
*
* May call other collaborating code to generate the response.
*/
public function handle(ServerRequestInterface $request): ResponseInterface;
}
handle()
方法接收一个ServerRequestInterface
对象,并返回一个ResponseInterface
对象。
代码示例:一个简单的Middleware
<?php
use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;
use PsrHttpServerMiddlewareInterface;
use PsrHttpServerRequestHandlerInterface;
use NyholmPsr7Response;
class LoggingMiddleware implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
// 在请求处理之前记录日志
echo "Request received: " . $request->getUri() . PHP_EOL;
// 调用下一个Middleware或最终的RequestHandler
$response = $handler->handle($request);
// 在响应发送之后记录日志
echo "Response sent with status code: " . $response->getStatusCode() . PHP_EOL;
return $response;
}
}
class AuthenticationMiddleware implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
// 检查用户是否已登录
$authHeader = $request->getHeaderLine('Authorization');
if (empty($authHeader)) {
return new Response(401, [], 'Unauthorized');
}
// 验证用户身份 (这里只是一个示例)
if ($authHeader !== 'Bearer valid_token') {
return new Response(403, [], 'Forbidden');
}
// 调用下一个Middleware或最终的RequestHandler
return $handler->handle($request);
}
}
class RequestHandler implements RequestHandlerInterface
{
public function handle(ServerRequestInterface $request): ResponseInterface
{
// 最终的处理逻辑 (Controller)
return new Response(200, [], 'Hello, World!');
}
}
六、Middleware Queue:让Middleware井然有序!
为了让Middleware能够按照一定的顺序执行,我们需要一个Middleware Queue(中间件队列)。 它可以是一个简单的数组,也可以是一个更复杂的类。
代码示例:一个简单的Middleware Queue
<?php
use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;
use PsrHttpServerMiddlewareInterface;
use PsrHttpServerRequestHandlerInterface;
class MiddlewareQueue implements RequestHandlerInterface
{
private array $middlewares = [];
private RequestHandlerInterface $finalHandler;
public function __construct(RequestHandlerInterface $finalHandler)
{
$this->finalHandler = $finalHandler;
}
public function add(MiddlewareInterface $middleware): void
{
$this->middlewares[] = $middleware;
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
// 创建一个RequestHandler,它将依次调用middlewares
$handler = $this->finalHandler;
foreach (array_reverse($this->middlewares) as $middleware) {
$handler = new class($middleware, $handler) implements RequestHandlerInterface {
private MiddlewareInterface $middleware;
private RequestHandlerInterface $nextHandler;
public function __construct(MiddlewareInterface $middleware, RequestHandlerInterface $nextHandler)
{
$this->middleware = $middleware;
$this->nextHandler = $nextHandler;
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
return $this->middleware->process($request, $this->nextHandler);
}
};
}
return $handler->handle($request);
}
}
代码示例:使用Middleware Queue
<?php
use NyholmPsr7ServerRequest;
use NyholmPsr7Uri;
// 创建一个Request对象
$request = new ServerRequest('GET', new Uri('/'));
$request = $request->withHeader('Authorization', 'Bearer valid_token');
// 创建一个Middleware Queue
$queue = new MiddlewareQueue(new RequestHandler());
// 添加Middleware
$queue->add(new LoggingMiddleware());
$queue->add(new AuthenticationMiddleware());
// 处理请求
$response = $queue->handle($request);
// 输出响应
echo $response->getBody() . PHP_EOL;
七、总结:PSR-7 + Middleware = 优雅的HTTP处理!
通过今天的讲解,相信大家已经对PSR-7和Middleware模式有了一定的了解。 它们可以帮助你:
- 编写更可维护、更可测试的代码。
- 提高代码的复用性。
- 更容易地添加新的功能。
- 让你的HTTP处理流程更加清晰和灵活。
总而言之,PSR-7和Middleware是现代PHP开发中不可或缺的工具,掌握它们,可以让你的代码更加优雅、健壮! 今天的讲座就到这里,感谢大家的观看! 下课!