PHP `PSR-7` `HTTP Message` 接口与 `Middleware` 模式

咳咳,各位观众老爷们,晚上好!我是你们今晚的讲师,今天咱们聊聊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开发中不可或缺的工具,掌握它们,可以让你的代码更加优雅、健壮! 今天的讲座就到这里,感谢大家的观看! 下课!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注