Skip to content

中间件

您可以在 Slim 应用程序中运行代码,以便在所需时操纵请求和响应对象。这被称为中间件。为什么要这样做呢?也许您希望保护应用程序免受跨站请求伪造的攻击。或者您希望在应用程序运行之前对请求进行身份验证。对于这些情况,中间件非常适用。

什么是中间件?

中间件实现了 PSR-15 中间件接口

  1. Psr\Http\Message\ServerRequestInterface - PSR-7 请求对象
  2. Psr\Http\Server\RequestHandlerInterface - PSR-15 请求处理器对象

它可以根据需要对这些对象执行任何适当的操作。唯一的硬性要求是中间件 必须 返回一个 Psr\Http\Message\ResponseInterface 实例。每个中间件 应该 调用下一个中间件,并将请求对象作为参数传递给它。

中间件如何工作?

不同的框架使用中间件的方式不同。Slim 将中间件添加为围绕核心应用程序的同心圆层。每个新的中间件层都环绕着任何现有的中间件层。随着添加额外的中间件层,同心结构向外扩展。

最后添加的中间件层将首先被执行。

当您运行 Slim 应用程序时,请求对象从外向内遍历中间件结构。它们首先进入最外层的中间件,然后进入下一个最外层中间件(以此类推),直到最终到达 Slim 应用程序本身。在 Slim 应用程序调度适当的路由之后,生成的响应对象离开 Slim 应用程序并从内向外遍历中间件结构。最终,最外层的中间件将输出最终的响应对象,并将其序列化为原始 HTTP 响应,返回给 HTTP 客户端。下面是一个图示说明中间件的流程:

Image

如何编写中间件?

中间件是一个可调用的函数,接受两个参数:Request 对象和 RequestHandler 对象。每个中间件 必须 返回一个 Psr\Http\Message\ResponseInterface 实例。

匿名函数中间件示例

以下示例中间件是一个匿名函数。

<?php
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Factory\AppFactory;
use Slim\Psr7\Response;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();

/**
 * 示例中间件闭包
 *
 * @param  ServerRequest  $request PSR-7 请求
 * @param  RequestHandler $handler PSR-15 请求处理器
 *
 * @return Response
 */
$beforeMiddleware = function (Request $request, RequestHandler $handler) {
    $response = $handler->handle($request);
    $existingContent = (string) $response->getBody();

    $response = new Response();
    $response->getBody()->write('BEFORE' . $existingContent);

    return $response;
};

$afterMiddleware = function ($request, $handler) {
    $response = $handler->handle($request);
    $response->getBody()->write('AFTER');
    return $response;
};

$app->add($beforeMiddleware);
$app->add($afterMiddleware);

// ...

$app->run();

可调用类中间件示例

以下示例中间件是一个实现了魔术 __invoke() 方法的可调用类。

<?php
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Psr7\Response;

class ExampleBeforeMiddleware
{
    /**
     * 示例中间件可调用类
     *
     * @param  ServerRequest  $request PSR-7 请求
     * @param  RequestHandler $handler PSR-15 请求处理器
     *
     * @return Response
     */
    public function __invoke(Request $request, RequestHandler $handler): Response
    {
        $response = $handler->handle($request);
        $existingContent = (string) $response->getBody();

        $response = new Response();
        $response->getBody()->write('BEFORE' . $existingContent);

        return $response;
    }
}
<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;

class ExampleAfterMiddleware
{
    /**
     * 示例中间件可调用类
     *
     * @param  ServerRequest  $request PSR-7 请求
     * @param  RequestHandler $handler PSR-15 请求处理器
     *
     * @return Response
     */
    public function __invoke(Request $request, RequestHandler $handler): Response
    {
        $response = $handler->handle($request);
        $response->getBody()->write('AFTER');
        return $response;
    }
}

要将这些类作为中间件使用,可以在 $app 的路由映射方法 get()、post()、put()、patch()、delete()、options()、any()group() 之后链式调用 add(new ExampleMiddleware()); 函数,如下面的代码所示。

<?php
use Slim\Factory\AppFactory;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();

// 在应用程序上添加中间件
$app->add(new ExampleMiddleware());

// 在路由上添加中间件
$app->get('/', function () { ... })->add(new ExampleMiddleware());

// 在分组上添加中间件
$app->group('/', function () { ... })->add(new ExampleMiddleware());

// ...

$app->run();

如何添加中间件?

您可以向 Slim 应用程序、单个 Slim 应用程序路由或路由组添加中间件。所有的情况都接受相同的中间件,并实现相同的中间件接口。

应用程序中间件

应用程序中间件对于每个 传入的 HTTP 请求都会被调用。使用 Slim 应用程序实例的 add() 方法添加应用程序中间件。下面的示例添加了上述闭包中间件:

<?php
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Factory\AppFactory;
use Slim\Psr7\Response;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();

$app->add(function (Request $request, RequestHandler $handler) {
    $response = $handler->handle($request);
    $existingContent = (string) $response->getBody();

    $response = new Response();
    $response->getBody()->write('BEFORE ' . $existingContent);

    return $response;
});

$app->add(function (Request $request, RequestHandler $handler) {
    $response = $handler->handle($request);
    $response->getBody()->write(' AFTER');
    return $response;
});

$app->get('/', function (Request $request, Response $response, $args) {
    $response->getBody()->write('Hello World');
    return $response;
});

$app->run();

这将输出以下 HTTP 响应正文:

BEFORE Hello World AFTER

路由中间件

只有当路由匹配当前的 HTTP 请求方法和 URI 时,才会调用路由中间件。路由中间件是在调用任何 Slim 应用程序的路由方法(如 get()post())之后指定的。每个路由方法都返回一个 \Slim\Route 实例,该类提供与 Slim 应用程序实例相同的中间件接口。使用路由实例的 add() 方法将中间件添加到路由。下面的示例添加了上述闭包中间件:

<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Factory\AppFactory;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();

$mw = function (Request $request, RequestHandler $handler) {
    $response = $handler->handle($request);
    $response->getBody()->write('World');

    return $response;
};

$app->get('/', function (Request $request, Response $response, $args) {
    $response->getBody()->write('Hello ');

    return $response;
})->add($mw);

$app->run();

这将输出以下 HTTP 响应正文:

Hello World

分组中间件

除了整个应用程序和标准路由能够接受中间件之外,group() 多路由定义功能还允许在组内部使用单独的路由。只有当分组的路由匹配组中定义的 HTTP 请求方法和 URI 之一时,才会调用路由组中间件。要在回调函数中添加中间件,并通过在 group() 方法之后链式调用 add() 来设置整个组的中间件。

使用带有 url 处理程序组的回调中间件的示例应用程序

<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Factory\AppFactory;
use Slim\Routing\RouteCollectorProxy;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();

$app->get('/', function (Request $request, Response $response) {
    $response->getBody()->write('Hello World');
    return $response;
});

$app->group('/utils', function (RouteCollectorProxy $group) {
    $group->get('/date', function (Request $request, Response $response) {
        $response->getBody()->write(date('Y-m-d H:i:s'));
        return $response;
    });

    $group->get('/time', function (Request $request, Response $response) {
        $response->getBody()->write((string)time());
        return $response;
    });
})->add(function (Request $request, RequestHandler $handler) use ($app) {
    $response = $handler->handle($request);
    $dateOrTime = (string) $response->getBody();

    $response = $app->getResponseFactory()->createResponse();
    $response->getBody()->write('It is now ' . $dateOrTime . '. Enjoy!');

    return $response;
});

$app->run();

调用 /utils/date 方法时,将输出类似于以下内容的字符串

现在是 2015-07-06 03:11:01。尽情享受吧!

访问 /utils/time 将输出类似于以下内容的字符串

现在是 1436148762。尽情享受吧!

但是访问 /(域根)将生成以下输出,因为没有分配中间件

Hello World

从中间件传递变量

从中间件传递属性最简单的方法是使用请求的属性。

在中间件中设置变量:

$request = $request->withAttribute('foo', 'bar');

在路由回调函数中获取变量:

$foo = $request->getAttribute('foo');

查找可用的中间件

您可以找到已经编写好以满足您需求的 PSR-15 中间件类。以下是一些非官方的列表供搜索。