中间件

你可以在你的 Slim 应用的请求对象和响应对象__之前__和__之后__执行代码,这就是__中间件__。为什么需要做这些?假如你需要在你的应用中防止跨域攻击(cross-site request forgery,CSRF)。你可能就需要在你的应用启动之前添加验证,这时候就可以使用中间件。

什么是中间件

从技术角度上讲,中间件就是一个需要接受三个参数的 回调对象( callable )。三个参数分别是:

  1. \Psr\Http\Message\ServerRequestInterface - PSR7请求对象
  2. \Psr\Http\Message\ResponseInterface - PSR7响应对象
  3. callable - 下一层中间件的回调对象

中间件是如何工作的

不同的框架的中间件的使用方式是不同的。在Slim中中间件以同心圆的形式包裹着你的核心应用。每一个新增的中间件会包裹着之前已经添加到中间件,所以同心圆结构会不断向外扩张。

在运行Slim的应用的时候,请求对象和响应对象会从最外层的中间件开始进入,不断向内深入,直到穿过所有 中间件结构抵达核心应用。在核心应用分派完路由之后,返回的响应对象会从最内层的中间件开始,由内向外穿过所有的中间件。穿过最外层的中间件之后,最终响应对象会被序列化成最原始的HTTP消息,返回给客户端。下面的图表很清晰的说明了中间件的执行流程:

怎么创建中间件

中间件是一个可以接受三个参数的回调对象:请求对象,响应对象,下一个中间件。每一个中间件__必须__返回一个实现了 \Psr\Http\Message\ResponseInterface接口的实例。

闭包实现的中间件示例
<?php
/**
 * Example middleware closure
 *
 * @param  \Psr\Http\Message\ServerRequestInterface $request  PSR7 request
 * @param  \Psr\Http\Message\ResponseInterface      $response PSR7 response
 * @param  callable                                 $next     Next middleware
 *
 * @return \Psr\Http\Message\ResponseInterface
 */
function ($request, $response, $next) {
		$response->getBody()->write('BEFORE');
		$response = $next($request, $response);
		$response->getBody()->write('AFTER');

		return $response;
};
通过带 __invoke方法的类实现的中间件

下面的示例是通过魔术方法 __invoke 实现的:

<?php
class ExampleMiddleware
{
		/**
		 * Example middleware invokable class
		 *
		 * @param  \Psr\Http\Message\ServerRequestInterface $request  PSR7 request
		 * @param  \Psr\Http\Message\ResponseInterface      $response PSR7 response
		 * @param  callable                                 $next     Next middleware
		 *
		 * @return \Psr\Http\Message\ResponseInterface
		 */
		public function __invoke($request, $response, $next)
		{
				$response->getBody()->write('BEFORE');
				$response = $next($request, $response);
				$response->getBody()->write('AFTER');

				return $response;
		}
}

你可以通过下面的方式使用上面的中间件类,使用 ->add( new ExampleMiddleware() ); $subject 指的是$app, Route, Group

$subject->add( new ExampleMiddleware() );

如何添加中间件

你可以给你的Slim应用,或者单独的路由或者路由群组添加中间件,因为Slim应用和路由实现了同样的中间件接口,可以接受同样的中间件。

应用中间件

应用中间件在所有进入应用的HTTP的请求都会被调用。通过调用 add() 方法来实现给Slim应用添加应用中间件。下面是给Slim应用添加闭包中间件的示例:

<?php
$app = new \Slim\App();

$app->add(function ($request, $response, $next) {
		$response->getBody()->write('BEFORE');
		$response = $next($request, $response);
		$response->getBody()->write('AFTER');

		return $response;
});

$app->get('/', function ($req, $res, $args) {
		echo ' Hello ';
});

$app->run();

输出结果:

BEFORE Hello AFTER
路由中间件

路由中间件只会在匹配当前HTTP请求或者URI的路由中才会被调用。我们在调用了Slim应用的路由(e.g., get() or post())之后,立即指定路由中间件。每一条路由都会返回一个实现 \Slim\Route 的实例。\Slim\Route也实现了与Slim应用一样的中间件接口。添加中间件使用 add() 。下面是添加路由中间件的示例:

<?php
$app = new \Slim\App();

$mw = function ($request, $response, $next) {
		$response->getBody()->write('BEFORE');
		$response = $next($request, $response);
		$response->getBody()->write('AFTER');

		return $response;
};

$app->get('/', function ($req, $res, $args) {
		echo ' Hello ';
})->add($mw);

$app->run();

输出结果:

BEFORE Hello AFTER
路由群组中间件

除了整个应用和标准路由允许添加中间件外,我们还定义了 group() 方法同时给多个路由添加中间件。路由群组中间件__只有__在当前HTTP请求和URI能够匹配 路由群组里的某一条路由之后才会被调用。路由群组添加中间件是在 group() 之后使用 add() 方法,下面是使用给路由群组添加中间件的示例:

<?php
require_once __DIR__.'/vendor/autoload.php';

$app = new \Slim\App();

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

$app->group('/utils', function () use ($app) {
		$app->get('/date', function ($request, $response) {
				return $response->getBody()->write(date('Y-m-d H:i:s'));
		});
		$app->get('/time', function ($request, $response) {
				return $response->getBody()->write(time());
		});
})->add(function ($request, $response, $next) {
		$response->getBody()->write('It is now ');
		$response = $next($request, $response);
		$response->getBody()->write('. Enjoy!');

		return $response;
});

访问 /utils/date 输出下面的结果:

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

访问 /utils/time 输出下面的结果:

It is now 1436148762. Enjoy!

访问 / 网站根目录则不会有中间件的效果,输出如下:

Hello World

文档翻译的并不是很专业,仅供个人学习作用,建议看官方文档。

如有兴趣,请看下一篇 SLIM 3 文档(六)-依赖容器")