Lumen框架经过设置中间件能够实现业务与非业务逻辑的隔离。在构建api服务的过程当中中间件能够作不少工做,例如:php
Lumen中间件分为两种,都须要在/bootstrap/app.php中进行设置node
$app->middleware([
App\Http\Middleware\ExampleMiddleware::class
]);
复制代码
$app->routeMiddleware([
'auth' => App\Http\Middleware\Authenticate::class,
'log' => App\Http\Middleware\Logger::class
]);
复制代码
路由中间件须要在路由中(一般在./routes/web.php)进行设置web
$app->group(['middleware' => 'Example'], function () use ($app) {
$app->get('age',['uses'=>'ExampleController@age']);
});
复制代码
Lumen中间件采用的是装饰器模式(固然也能够说成是责任链、管道模式)。和node.js框架koa2的洋葱模型效果同样。实现的都是对http请求的层层处理,最终返回给用户响应信息。bootstrap
Lumen框架实现中间件使用到了管道和Closure(匿名函数类),源码不太好理解,笔者准备了一个小demo帮助你们理解:api
<?php
interface Middleware
{
public function handle(Closure $next);
}
class LogMiddleware implements Middleware
{
public function handle(Closure $next)
{
echo "记录请求日志" . '<br/>';
$next();
echo "记录响应日志" . '<br/>';
}
}
class ApiMiddleware implements Middleware
{
public function handle(Closure $next)
{
echo "Apimiddleware校验token" . '<br/>';
$next();
}
}
class RateLimitMiddleware implements Middleware
{
public function handle(Closure $next)
{
echo "校验流量限制" . '<br/>';
$next();
}
}
function carry($closures, $middleware)
{
return function () use ($closures, $middleware) {
return $middleware->handle($closures);
};
}
function then()
{
$middlewares = [new LogMiddleware(), new ApiMiddleware(), new RateLimitMiddleware()];
$prepare = function () {
echo '<b>用户处理逻辑,返回响应结果</b>' . '<br/>';
};
$go = array_reduce(array_reverse($middlewares), 'carry', $prepare);
$go();
}
then();
复制代码
例子中建立了三个Middleware,都实现了Middleware接口。其中handle为处理逻辑。handle方法接收一个匿名函数类(Closure),handle方法有本身的处理逻辑,这些处理逻辑能够在匿名函数类执行以前,也能够在它以后。(这也是洋葱模型的精要,请求处理通过中间件到逻辑处理函数,返回响应后还会再次通过它)。LogMiddleware在匿名函数类执行以前和以后分别打印了信息;ApiMiddleware和RateLimitMiddleware则分别在匿名函数执行以前打印了信息。数组
咱们直接来看then函数:首先$middleware数组保存了一组中间件;$prepare是一个匿名函数,这里主要是为了模拟用户处理逻辑,对应咱们Lumen中的业务入口Controller中的相关方法,例如:bash
public function getList(Request $r){
...//这里是业务逻辑
}
复制代码
接下来的array_reduce是重点,php官方是这样定义的:闭包
array_reduce() 将回调函数 callback 迭代地做用到 array 数组中的每个单元中,从而将数组简化为单一的值。app
array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] ) : mixed
复制代码
参数框架
array 输入的 array。
callback callback ( mixed $carry , mixed $item ) : mixed carry 携带上次迭代里的值; 若是本次迭代是第一次,那么这个值是 initial。
item 携带了本次迭代的值。
initial 若是指定了可选参数 initial,该参数将在处理开始前使用,或者当处理结束,数组为空时的最后一个结果。
看完了官方的定义,咱们就比较好理解demo中的carry函数就是中间件的迭代函数了,$prepare会作为carry函数首次迭代时的第一个参数;carry函数的返回值使用到了闭包:
return function () use ($closures, $middleware) {
return $middleware->handle($closures);
};
复制代码
咱们知道函数内部的变量都是局部变量,除非使用global声明。闭包的使用使得函数内部可使用外部变量。这里咱们就把上一层中间件的实例和它用到的参数引用了进来。
存储中间件的数组$middleware为何要使用array_reverse()函数反转呢?
由于array_reduce迭代以后,匿名函数所处的位置和数组原来的位置恰好是相反的。通过一次reverse以后,再次通过array_reduce的迭代,造成了一个符合咱们预期的执行链路。像洋葱同样,一层层的包裹。最后只须要执行then()函数,就能实现咱们上图所表示的逻辑链路了,打印的结果为:
记录请求日志
Apimiddleware校验token
校验流量限制
用户处理逻辑,返回响应结果
记录响应日志
复制代码
若是你们对上述中间件实现过程还不是太明白,建议回过头来再读一遍,将demo拷贝到本地运行调试。相信你们明白了这个例子之后,阅读下面lumen中间件源码的理解会有一种豁然开朗的感受。
框架的入口文件(./app/public/index.php)很是简单:
$app = require __DIR__.'/../bootstrap/app.php';
$app->run();
复制代码
run函数是在Trait中定义的:
trait RoutesRequests
{
protected $middleware = [];
protected $routeMiddleware = [];
......
public function run($request = null)
{
$response = $this->dispatch($request);
if ($response instanceof SymfonyResponse) {
$response->send();
} else {
echo (string) $response;
}
if (count($this->middleware) > 0) {
$this->callTerminableMiddleware($response);
}
}
......
}
复制代码
框架中的中间件存储在成员变量$middleware和$routeMiddleware中,咱们接着看dispatch函数:
trait RoutesRequests
{
......
public function dispatch($request = null)
{
//解析路由和请求方法
[$method, $pathInfo] = $this->parseIncomingRequest($request);
try {
//启动服务注册
$this->boot();
//经过向中间件分发请求处理获得响应信息
return $this->sendThroughPipeline($this->middleware, function ($request) use ($method, $pathInfo) {
$this->instance(Request::class, $request);
if (isset($this->router->getRoutes()[$method.$pathInfo])) {
return $this->handleFoundRoute([true, $this->router->getRoutes()[$method.$pathInfo]['action'], []]);
}
return $this->handleDispatcherResponse(
$this->createDispatcher()->dispatch($method, $pathInfo)
);
});
} catch (Throwable $e) {
return $this->prepareResponse($this->sendExceptionToHandler($e));
}
}
......
复制代码
接下来的sendThroughPipeline实现了中间件处理:
trait RoutesRequests
{
......
protected function sendThroughPipeline(array $middleware, Closure $then)
{
if (count($middleware) > 0 && ! $this->shouldSkipMiddleware()) {
return (new Pipeline($this))
->send($this->make('request'))
->through($middleware)
->then($then);
}
return $then($this->make('request'));
}
......
}
复制代码
咱们只看有逻辑中间件的状况,先来看then函数(/vendor/illuminate/pipeline/Pipeline.php):
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
);
return $pipeline($this->passable);
}
复制代码
到这里,读者大概就明白了,实现原理和咱们上述demo基本是同样的。只不过这里的迭代函数更加的复杂一些,咱们来看一下:
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
if (is_callable($pipe)) {
return $pipe($passable, $stack);
} elseif (! is_object($pipe)) {
[$name, $parameters] = $this->parsePipeString($pipe);
$pipe = $this->getContainer()->make($name);
$parameters = array_merge([$passable, $stack], $parameters);
} else {
$parameters = [$passable, $stack];
}
$response = method_exists($pipe, $this->method)
? $pipe->{$this->method}(...$parameters)
: $pipe(...$parameters);
return $response instanceof Responsable
? $response->toResponse($this->getContainer()->make(Request::class))
: $response;
};
};
}
复制代码
注意咱们第二个参数传入的是:$this->carry();因此实际的迭代函数是第一个return后的匿名对象。这个匿名对象返回的匿名对象(第二个return)接收一个参数$passable,并使用闭包引用了外层$middleware的实例(匿名函数)。
$pipeline($this->passable)中的$this->passable是什么呢?正是send方法传入的参数(Request):
class Pipeline implements PipelineContract
{
......
public function send($passable)
{
$this->passable = $passable;
return $this;
}
......
}
复制代码
那么$this->carry()中的这个返回值$pipe($passable, $stack);也让咱们联想到了中间件中的handle方法了吧:
class ExampleMiddleware
{
public function handle($request, Closure $next)
{
......
return $next($request);
......
}
}
复制代码
array_reduce中的第三个参数咱们也来简单看一下吧:
protected function prepareDestination(Closure $destination)
{
return function ($passable) use ($destination) {
return $destination($passable);
};
}
复制代码
$passable就是Request对象,$destination是传入的参数,就是dispatch方法中返回值中的第二个参数,是匿名函数:
return $this->sendThroughPipeline($this->middleware, function ($request) use ($method, $pathInfo) {
$this->instance(Request::class, $request);
if (isset($this->router->getRoutes()[$method.$pathInfo])) {
return $this->handleFoundRoute([true, $this->router->getRoutes()[$method.$pathInfo]['action'], []]);
}
return $this->handleDispatcherResponse(
$this->createDispatcher()->dispatch($method, $pathInfo)
);
});
复制代码
这个匿名函数最终返回的结果是“服务容器”根据$method和$pathInfo到路由中解析出来具体的执行逻辑(哪一个Controller的哪一个方法)实例。也就是$destination($passable)对应的是:
class ExampleController extends Controller
{
public function getList(Request $r)
{
....
}
复制代码
到这里,相信读者都已经明白了Lumen中间件的实现原理.
中间件是API框架必不可少的一个模块,其目的都是为了解耦非业务逻辑代码和业务逻辑代码,实现效果是相似于洋葱同样的请求处理模型;不一样的语言有不一样的特性,node.js框架Ko2是采用的回调函数的形式实现了中间件,只不过其封装的更加优雅并容易使用;Go语言的特性提倡组合大于继承,Gin框架利用组合方法的方式也实现了相似Ko2框架中间件的模式。本节讲了Lumen框架如何实现中间件,基于php的特性,实现起来确实复杂了一些,相信读完本文,读者可以对API框架中间件有更深刻的认识!