中间件

您可以在 Slim 应用程序之前之后运行代码,以按照您认为合适的方式操作 Request 和 Response 对象。这称为中间件你为什么想做这个?也许您想保护您的应用程序免受跨站点请求伪造。也许您想在应用运行之前对请求进行身份验证。中间件非常适合这些场景。

什么是中间件?

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

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

它可以对这些对象做任何合适的事情。唯一的硬性要求是中间件必须返回一个 Psr\Http\Message\ResponseInterface. 每个中间件应该调用下一个中间件并将 Request 对象作为参数传递给它。

中间件如何工作?

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

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

当你运行 Slim 应用程序时,Request 对象从外向内遍历中间件结构。它们首先进入最外层的中间件,然后是下一个最外层的中间件,(依此类推),直到最终到达 Slim 应用程序本身。在 Slim 应用程序调度适当的路由后,生成的 Response 对象退出 Slim 应用程序并从内到外遍历中间件结构。最终,最终的 Response 对象退出最外层的中间件,被序列化为原始 HTTP 响应,并返回给 HTTP 客户端。这是一个说明中间件流程的图表:

中间件架构

我如何编写中间件?

中间件是一个接受两个参数的可调用对象:一个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();

/**
 * Example middleware closure
 *
 * @param  ServerRequest  $request PSR-7 request
 * @param  RequestHandler $handler PSR-15 request handler
 *
 * @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
{
    /**
     * Example middleware invokable class
     *
     * @param  ServerRequest  $request PSR-7 request
     * @param  RequestHandler $handler PSR-15 request handler
     *
     * @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
{
    /**
     * Example middleware invokable class
     *
     * @param  ServerRequest  $request PSR-7 request
     * @param  RequestHandler $handler PSR-15 request handler
     *
     * @return Response
     */
    public function __invoke(Request $request, RequestHandler $handler): Response
    {
        $response = $handler->handle($request);
        $response->getBody()->write('AFTER');
        return $response;
    }
}

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

<?php
use Slim\Factory\AppFactory;

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

$app = AppFactory::create();

// Add Middleware On App
$app->add(new ExampleMiddleware());

// Add Middleware On Route
$app->get('/', function () { ... })->add(new ExampleMiddleware());

// Add Middleware On Group
$app->group('/', function () { ... })->add(new ExampleMiddleware());

// ...

$app->run();

如何添加中间件?

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

应用中间件

为每个传入的HTTP 请求调用应用程序中间件使用 Slim 应用程序实例的add()方法添加应用程序中间件。本示例添加了上面的 Closure 中间件示例:

<?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 应用程序实例相同的中间件接口。使用 Route 实例的add()方法将中间件添加到 Route 本示例添加了上面的 Closure 中间件示例:

<?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方法时,这将输出类似于下面的字符串

It is now 2015-07-06 03:11:01. Enjoy!

访问/utils/time会输出类似于下面的字符串

It is now 1436148762. Enjoy!

但是访问/ (domain-root),预计会生成以下输出,因为没有分配中间件

Hello World

从中间件传递变量

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

在中间件中设置变量:

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

在路由回调中获取变量:

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

寻找可用的中间件

您可能会发现已经编写的 PSR-15 中间件类可以满足您的需求。这里有一些非官方的搜索列表。