Laravel 中间件提供了一种方便的机制来过滤进入应用的 HTTP 请求。例如,Laravel 内置了一个中间件来验证用户的身份认证。如果用户没有通过身份认证,中间件会将用户重定向到登录界面。但是,如果用户被认证,中间件将允许该请求进一步进入该应用。
当然,除了身份认证以外,还可以编写另外的中间件来执行各种任务。例如:CORS 中间件可以负责为所有离开应用的响应添加合适的头部信息;日志中间件可以记录所有传入应用的请求。
Laravel 自带了一些中间件,包括身份验证、CSRF 保护等。所有这些中间件都位于 app/Http/Middleware
目录。
简单来说就是请求在不去修改自身的逻辑,通过中间件扩展或者处理一些功能。
1、先来了解一下 array_reduce 函数
地址:https://blog.youkuaiyun.com/raoxiaoya/article/details/103447483
2、源码解读
index.php中
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response);
然后
Illuminate\Foundation\Http\Kernel 的handle方法
public function handle($request)
{
try {
$request->enableHttpMethodParameterOverride();
$response = $this->sendRequestThroughRouter($request);
} catch (Exception $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
} catch (Throwable $e) {
$this->reportException($e = new FatalThrowableError($e));
$response = $this->renderException($request, $e);
}
$this->app['events']->dispatch(
new Events\RequestHandled($request, $response)
);
return $response;
}
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap();
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
// 通过管道之后进入路由,控制器
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);
return $this->router->dispatch($request);
};
}
然后是 Illuminate\Pipeline\Pipeline
send
方法设置通过管道的数据 即request
through
方法设置要通过的管道数组
then
方法是执行,传入的参数是一个闭包,也就是如果顺利通过管道后进入的路由层。
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
);
// 执行闭包,并传入一个参数,也就是request对象
return $pipeline($this->passable);
}
protected function prepareDestination(Closure $destination)
{
return function ($passable) use ($destination) {
return $destination($passable);
};
}
根据 array_reduce(元素数组, 回调函数, 初始化值)
的了解,如果传入的是闭包嵌套,那么最终解包后的执行顺序是反的,也就是说最里层的闭包最后执行,而在封装的时候,第一个是在最里面的,最后一个是最外面的,所以要使用array_reverse来反转middlewares,并且最后的路由层反而应该设置在初始化的位置.
那么问题来了,我们定义的路由中间件都是单独的类,实现了 public function handle($request, Closure $next)
方法,如何将其封装为闭包呢?实际上在外层套一个闭包函数就可以了
return function ($passable) use ($stack, $pipe) {
return (new $pipe)->handle($passable, $stack);
}
为了实现在carry方法中统一的调用方式,将 $destination 在嵌套一层闭包,也就是 prepareDestination
方法的作用,只需要再次 use 自己就行。实际上不加这一层也没毛病。
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->container->make(Request::class))
: $response;
};
};
}
要注意,array_reduce 的作用只是形成一个递归闭包,要使其能够一层层执行下去,需要 $pipeline($this->passable)
触发,然后还需要内部建立关联关系,即 $next($request)
,而刚好最里层的闭包不需要 next,设计得还算很巧妙。由这种嵌套的方式注定了你不能设置太多的中间件,因为层次越深性能将急剧下降。
一个示例
<?php
/**
* ----------------------------------------------------------
* date: 2019/12/8 14:39
* ----------------------------------------------------------
* author: Raoxiaoya
* ----------------------------------------------------------
* describe:
* ----------------------------------------------------------
*/
class VerfiyCsrfToken
{
public function handle($request, Closure $next)
{
print_r($request);
$request['token'] = 'xxxxxxxxxxx';
$response = $next($request);
return $response;
}
}
class VerfiyAuth
{
public function handle($request, Closure $next)
{
print_r($request);
$request['auth'] = 'aaaaaaaaaaa';
$response = $next($request);
return $response;
}
}
class SetCookieInfo
{
public function handle($request, Closure $next)
{
print_r($request);
$request['cookie'] = 'ccccccccccccc';
$response = $next($request);
return $response;
}
}
$middlewareArr = [
'VerfiyCsrfToken',
'VerfiyAuth',
'SetCookieInfo',
];
$request = [];
$destination = function ($request) {
print_r($request);
return '通过管道之后';
};
$handle = function ($passable) use ($destination) {
return $destination($passable);
};
$pipeline = array_reduce(
array_reverse($middlewareArr),
function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
if (is_callable($pipe)) {
return $pipe($passable, $stack);
} elseif (! is_object($pipe)) {
$pipe = new $pipe();
}
$response = $pipe->handle($passable, $stack);
return $response;
};
},
$handle
);
print_r($pipeline);
var_dump($pipeline($request));
最终得到的大闭包
执行结果:
我们打印出解包过程:
第一次解包时的打印
第二次解包时打印
第三次解包时打印
只打印了三次,那还有一次呢?因为经过这个回调函数的只有我们定义的三个中间件,最后一个不经过这里,所以不会在这里执行。这样的话 is_callable
也是多余的。