搜索
首页后端开发php教程Laravel中间件(Middleware)的核心解读

Laravel中间件(Middleware)的核心解读

Feb 07, 2018 pm 02:58 PM
laravelmiddleware中间件


本文主要和大家分享Laravel中间件(Middleware)的核心解读,中间件(Middleware)在Laravel中起着过滤进入应用的HTTP请求对象(Request)和完善离开应用的HTTP响应对象(Reponse)的作用, 而且可以通过应用多个中间件来层层过滤请求、逐步完善相应。这样就做到了程序的解耦,如果没有中间件那么我们必须在控制器中来完成这些步骤,这无疑会造成控制器的臃肿。

举一个简单的例子,在一个电商平台上用户既可以是一个普通用户在平台上购物也可以在开店后是一个卖家用户,这两种用户的用户体系往往都是一套,那么在只有卖家用户才能访问的控制器里我们只需要应用两个中间件来完成卖家用户的身份认证:

           

class MerchantController extends Controller{
    public function __construct()
    {
        $this->middleware('auth');
        $this->middleware('mechatnt_auth');
    }
}

在auth中间件里做了通用的用户认证,成功后HTTP Request会走到merchant_auth中间件里进行商家用户信息的认证,两个中间件都通过后HTTP Request就能进入到要去的控制器方法中了。利用中间件,我们就能把这些认证代码抽离到对应的中间件中了,而且可以根据需求自由组合多个中间件来对HTTP Request进行过滤。

再比如Laravel自动给所有路由应用的VerifyCsrfToken中间件,在HTTP Requst进入应用走过VerifyCsrfToken中间件时会验证Token防止跨站请求伪造,在Http Response 离开应用前会给响应添加合适的Cookie。(laravel5.5开始CSRF中间件只自动应用到web路由上)

上面例子中过滤请求的叫前置中间件,完善响应的叫做后置中间件。用一张图可以标示整个流程:
3235720194-5a7874d77e8a2_articlex.png

上面概述了下中间件在laravel中的角色,以及什么类型的代码应该从控制器挪到中间件里,至于如何定义和使用自己的laravel 中间件请参考官方文档

下面我们主要来看一下Laravel中是怎么实现中间件的,中间件的设计应用了一种叫做装饰器的设计模式。

Laravel实例化Application后,会从服务容器里解析出Http Kernel对象,通过类的名字也能看出来Http Kernel就是Laravel里负责HTTP请求和响应的核心。

           

/**
 * @var \App\Http\Kernel $kernel
 */$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

$response->send();

$kernel->terminate($request, $response);

index.php里可以看到,从服务容器里解析出Http Kernel,因为在bootstrap/app.php里绑定了Illuminate\Contracts\Http\Kernel接口的实现类App\Http\Kernel所以$kernel实际上是App\Http\Kernel类的对象。
解析出Http Kernel后Laravel将进入应用的请求对象传递给Http Kernel的handle方法,在handle方法负责处理流入应用的请求对象并返回响应对象。

           

/**
 * Handle an incoming HTTP request.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response
 */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;
}

中间件过滤应用的过程就发生在$this->sendRequestThroughRouter($request)里:

           

/**
 * Send the given request through the middleware / router.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\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());
}

这个方法的前半部分是对Application进行了初始化,在上一篇讲解服务提供器的文章里有对这一部分的详细讲解。Laravel通过Pipeline(管道)对象来传输请求对象,在Pipeline中请求对象依次通过Http Kernel里定义的中间件的前置操作到达控制器的某个action或者直接闭包处理得到响应对象。

看下Pipeline里这几个方法:

           
public function send($passable){    $this->passable = $passable;    return $this;
}public function through($pipes){    $this->pipes = is_array($pipes) ? $pipes : func_get_args();    return $this;
}public function then(Closure $destination){
    $firstSlice = $this->getInitialSlice($destination);    
    //pipes 就是要通过的中间件
    $pipes = array_reverse($this->pipes);    //$this->passable就是Request对象    return call_user_func(
        array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable
    );
}protected function getInitialSlice(Closure $destination){    return function ($passable) use ($destination) {        return call_user_func($destination, $passable);
    };
}//Http Kernel的dispatchToRouter是Piple管道的终点或者叫目的地protected function dispatchToRouter(){    return function ($request) {        $this->app->instance('request', $request);        return $this->router->dispatch($request);
    };
}

上面的函数看起来比较晕,我们先来看下array_reduce里对它的callback函数参数的解释:

mixed array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] )
array_reduce() 将回调函数 callback 迭代地作用到 array 数组中的每一个单元中,从而将数组简化为单一的值。
callback ( mixed $carry , mixed $item )carry携带上次迭代里的值; 如果本次迭代是第一次,那么这个值是 initial。item 携带了本次迭代的值。

getInitialSlice方法,他的返回值是作为传递给callbakc函数的$carray参数的初始值,这个值现在是一个闭包,我把getInitialSlice和Http Kernel的dispatchToRouter这两个方法合并一下,现在$firstSlice的值为:

           

$destination = function ($request) {    $this->app->instance('request', $request);    return $this->router->dispatch($request);
};

$firstSlice = function ($passable) use ($destination) {    return call_user_func($destination, $passable);
};

接下来我们看看array_reduce的callback:

           

//Pipeline protected function getSlice(){    return function ($stack, $pipe) {        return function ($passable) use ($stack, $pipe) {            try {
                $slice = parent::getSlice();                return call_user_func($slice($stack, $pipe), $passable);
            } catch (Exception $e) {                return $this->handleException($passable, $e);
            } catch (Throwable $e) {                return $this->handleException($passable, new FatalThrowableError($e));
            }
        };
    };
}//Pipleline的父类BasePipeline的getSlice方法protected function getSlice(){    return function ($stack, $pipe) {        return function ($passable) use ($stack, $pipe) {            if ($pipe instanceof Closure) {                return call_user_func($pipe, $passable, $stack);
            } elseif (! is_object($pipe)) {                //解析中间件名称和参数 ('throttle:60,1')                list($name, $parameters) = $this->parsePipeString($pipe);
                $pipe = $this->container->make($name);
                $parameters = array_merge([$passable, $stack], $parameters);
            } else{
                $parameters = [$passable, $stack];
            }            //$this->method = handle            return call_user_func_array([$pipe, $this->method], $parameters);
        };
    };
}

注:在Laravel5.5版本里 getSlice这个方法的名称换成了carray, 两者在逻辑上没有区别,所以依然可以参照着5.5版本里中间件的代码来看本文。

getSlice会返回一个闭包函数, $stack在第一次调用getSlice时它的值是$firstSlice, 之后的调用中就它的值就是这里返回的值个闭包了:

           

$stack = function ($passable) use ($stack, $pipe) {            try {
                $slice = parent::getSlice();                return call_user_func($slice($stack, $pipe), $passable);
            } catch (Exception $e) {                return $this->handleException($passable, $e);
            } catch (Throwable $e) {                return $this->handleException($passable, new FatalThrowableError($e));
            }
 };

getSlice返回的闭包里又会去调用父类的getSlice方法,他返回的也是一个闭包,在闭包会里解析出中间件对象、中间件参数(无则为空数组), 然后把$passable(请求对象), $stack和中间件参数作为中间件handle方法的参数进行调用。

上面封装的有点复杂,我们简化一下,其实getSlice的返回值就是:

           

$stack = function ($passable) use ($stack, $pipe) {                //解析中间件和中间件参数,中间件参数用$parameter代表,无参数时为空数组
               $parameters = array_merge([$passable, $stack], $parameters)               return $pipe->handle($parameters)
};

array_reduce每次调用callback返回的闭包都会作为参数$stack传递给下一次对callback的调用,array_reduce执行完成后就会返回一个嵌套了多层闭包的闭包,每层闭包用到的外部变量$stack都是上一次之前执行reduce返回的闭包,相当于把中间件通过闭包层层包裹包成了一个洋葱。

在then方法里,等到array_reduce执行完返回最终结果后就会对这个洋葱闭包进行调用:

           

return call_user_func( array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable);

这样就能依次执行中间件handle方法,在handle方法里又会去再次调用之前说的reduce包装的洋葱闭包剩余的部分,这样一层层的把洋葱剥开直到最后。通过这种方式让请求对象依次流过了要通过的中间件,达到目的地Http Kernel 的dispatchToRouter方法。

通过剥洋葱的过程我们就能知道为什么在array_reduce之前要先对middleware数组进行反转, 因为包装是一个反向的过程, 数组$pipes中的第一个中间件会作为第一次reduce执行的结果被包装在洋葱闭包的最内层,所以只有反转后才能保证初始定义的中间件数组中第一个中间件的handle方法会被最先调用。

上面说了Pipeline传送请求对象的目的地是Http Kernel 的dispatchToRouter方法,其实到远没有到达最终的目的地,现在请求对象了只是刚通过了\App\Http\Kernel类里$middleware属性里罗列出的几个中间件:

           

protected $middleware = [    \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,    \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,    \App\Http\Middleware\TrimStrings::class,    \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,    \App\Http\Middleware\TrustProxies::class,
];

当请求对象进入dispatchToRouter方法后,请求对象在被Router dispatch派发给路由时会进行收集路由上应用的中间件和控制器里应用的中间件。

           

public function dispatch(Request $request){    $this->currentRequest = $request;

    $response = $this->dispatchToRoute($request);    return $this->prepareResponse($request, $response);
}public function dispatchToRoute(Request $request){    return $this->runRoute($request, $this->findRoute($request));
}protected function runRouteWithinStack(Route $route, Request $request){
    $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&                            $this->container->make('middleware.disable') === true;    //收集路由和控制器里应用的中间件
    $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);    return (new Pipeline($this->container))
                    ->send($request)
                    ->through($middleware)
                    ->then(function ($request) use ($route) {                        return $this->prepareResponse(
                            $request, $route->run()
                        );
                    });
}

收集完路由和控制器里应用的中间件后,依赖时利用Pipeline对象来传送请求对象通过收集上来的这些中间件然后到达最终的目的地,在这里会执行路由对应的控制器方法生成响应对象,然后响应对象会依次来通过上面应用的所有中间件的后置操作,最终离开应用被发送给客户端。

限于篇幅和为了文章的可读性,收集路由和控制器中间件然后执行路由对应的处理方法的过程我就不在这里详述了,感兴趣的同学可以自己去看Router的源码,本文的目的还是主要为了梳理laravel是如何设计中间件的以及如何执行它们的,希望能对感兴趣的朋友有帮助。

相关推荐:

Laravel中间件实现原理详解

laravel中间件中的Closure $next是什么意思

关于laravel中间件

以上是Laravel中间件(Middleware)的核心解读的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
PHP的当前状态:查看网络开发趋势PHP的当前状态:查看网络开发趋势Apr 13, 2025 am 12:20 AM

PHP在现代Web开发中仍然重要,尤其在内容管理和电子商务平台。1)PHP拥有丰富的生态系统和强大框架支持,如Laravel和Symfony。2)性能优化可通过OPcache和Nginx实现。3)PHP8.0引入JIT编译器,提升性能。4)云原生应用通过Docker和Kubernetes部署,提高灵活性和可扩展性。

PHP与其他语言:比较PHP与其他语言:比较Apr 13, 2025 am 12:19 AM

PHP适合web开发,特别是在快速开发和处理动态内容方面表现出色,但不擅长数据科学和企业级应用。与Python相比,PHP在web开发中更具优势,但在数据科学领域不如Python;与Java相比,PHP在企业级应用中表现较差,但在web开发中更灵活;与JavaScript相比,PHP在后端开发中更简洁,但在前端开发中不如JavaScript。

PHP与Python:核心功能PHP与Python:核心功能Apr 13, 2025 am 12:16 AM

PHP和Python各有优势,适合不同场景。1.PHP适用于web开发,提供内置web服务器和丰富函数库。2.Python适合数据科学和机器学习,语法简洁且有强大标准库。选择时应根据项目需求决定。

PHP:网络开发的关键语言PHP:网络开发的关键语言Apr 13, 2025 am 12:08 AM

PHP是一种广泛应用于服务器端的脚本语言,特别适合web开发。1.PHP可以嵌入HTML,处理HTTP请求和响应,支持多种数据库。2.PHP用于生成动态网页内容,处理表单数据,访问数据库等,具有强大的社区支持和开源资源。3.PHP是解释型语言,执行过程包括词法分析、语法分析、编译和执行。4.PHP可以与MySQL结合用于用户注册系统等高级应用。5.调试PHP时,可使用error_reporting()和var_dump()等函数。6.优化PHP代码可通过缓存机制、优化数据库查询和使用内置函数。7

PHP:许多网站的基础PHP:许多网站的基础Apr 13, 2025 am 12:07 AM

PHP成为许多网站首选技术栈的原因包括其易用性、强大社区支持和广泛应用。1)易于学习和使用,适合初学者。2)拥有庞大的开发者社区,资源丰富。3)广泛应用于WordPress、Drupal等平台。4)与Web服务器紧密集成,简化开发部署。

超越炒作:评估当今PHP的角色超越炒作:评估当今PHP的角色Apr 12, 2025 am 12:17 AM

PHP在现代编程中仍然是一个强大且广泛使用的工具,尤其在web开发领域。1)PHP易用且与数据库集成无缝,是许多开发者的首选。2)它支持动态内容生成和面向对象编程,适合快速创建和维护网站。3)PHP的性能可以通过缓存和优化数据库查询来提升,其广泛的社区和丰富生态系统使其在当今技术栈中仍具重要地位。

PHP中的弱参考是什么?什么时候有用?PHP中的弱参考是什么?什么时候有用?Apr 12, 2025 am 12:13 AM

在PHP中,弱引用是通过WeakReference类实现的,不会阻止垃圾回收器回收对象。弱引用适用于缓存系统和事件监听器等场景,需注意其不能保证对象存活,且垃圾回收可能延迟。

解释PHP中的__ Invoke Magic方法。解释PHP中的__ Invoke Magic方法。Apr 12, 2025 am 12:07 AM

\_\_invoke方法允许对象像函数一样被调用。1.定义\_\_invoke方法使对象可被调用。2.使用$obj(...)语法时,PHP会执行\_\_invoke方法。3.适用于日志记录和计算器等场景,提高代码灵活性和可读性。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
4 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具