Maison  >  Article  >  développement back-end  >  Interprétation de base du middleware Laravel (Middleware)

Interprétation de base du middleware Laravel (Middleware)

小云云
小云云original
2018-02-07 14:58:125151parcourir


Cet article partage principalement avec vous l'interprétation principale du middleware Laravel (Middleware) dans Laravel qui joue le rôle de filtrer l'objet de requête HTTP (Request) entrant dans l'application et améliorer la sortie.Le rôle de l'objet de réponse HTTP de l'application (Reponse) et l'application de plusieurs middlewares peuvent filtrer les requêtes couche par couche et améliorer progressivement la réponse. Cela réalise le découplage du programme. S'il n'y a pas de middleware, nous devons effectuer ces étapes dans le contrôleur, ce qui entraînera sans aucun doute une surcharge du contrôleur.

Pour donner un exemple simple, sur une plateforme de e-commerce, un utilisateur peut être soit un utilisateur ordinaire effectuant des achats sur la plateforme, soit un utilisateur vendeur après avoir ouvert une boutique. Le système d'utilisateurs de ces deux types d'utilisateurs est. souvent le même. Il suffit alors d'appliquer deux middlewares dans le contrôleur auxquels seuls les utilisateurs vendeurs peuvent accéder pour compléter l'authentification de l'identité des utilisateurs vendeurs :

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

L'authentification générale de l'utilisateur est effectuée dans le middleware d'authentification. Après succès, la requête HTTP sera envoyée au middleware Merchant_auth pour authentifier les informations de l'utilisateur du commerçant. La requête HTTP peut saisir la méthode du contrôleur à laquelle elle souhaite accéder. À l'aide d'un middleware, nous pouvons extraire ces codes d'authentification dans le middleware correspondant, et nous pouvons librement combiner plusieurs middlewares en fonction des besoins pour filtrer les requêtes HTTP.

Un autre exemple est le middleware VerifyCsrfToken que Laravel applique automatiquement à toutes les routes. Lorsque la requête HTTP entre dans l'application et passe par le middleware VerifyCsrfToken, le jeton sera vérifié pour empêcher la falsification de requêtes intersites. . La réponse HTTP sera vérifiée avant de quitter l'application. Ajoutez les cookies appropriés à la réponse. (À partir de Laravel 5.5, le middleware CSRF n'est automatiquement appliqué qu'au routage Web)

Dans l'exemple ci-dessus, celui qui filtre la requête s'appelle le pré-middleware, et celui qui complète la réponse s'appelle le post-middleware. Une image peut être utilisée pour marquer l'ensemble du processus :
Interprétation de base du middleware Laravel (Middleware)

Ce qui précède décrit le rôle du middleware dans Laravel et quel type de code doit être déplacé du contrôleur vers le middleware. comment Pour définir et utiliser votre propre middleware laravel, veuillez vous référer à la documentation officielle.

Jetons un coup d'œil à la manière dont le middleware est implémenté dans Laravel. La conception du middleware applique un modèle de conception appelé décorateur.

Une fois que Laravel a instancié l'application, il analysera l'objet Http Kernel du conteneur de service. Il ressort également du nom de la classe que Http Kernel est le noyau responsable des requêtes et des réponses HTTP dans Laravel.

, car la classe d'implémentation de l'interface est liée à

, donc $kernel est en fait un objet de
/**
 * @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);
classe.

Après avoir analysé le noyau Http, Laravel transmet l'objet de requête entrant dans l'application à la méthode handle du noyau Http. La méthode handle est responsable du traitement de l'objet de requête entrant dans l'application et du renvoi de l'objet de réponse. index.phpbootstrap/app.phpIlluminateContractsHttpKernelAppHttpKernel AppHttpKernel

                                               L'article contient une explication détaillée de cette partie. Laravel transmet l'objet de requête via l'objet Pipeline. Dans le Pipeline, l'objet de requête atteint une action du contrôleur via la pré-opération du middleware défini dans le noyau HTTP ou le traitement de fermeture directe pour obtenir l'objet de réponse. Regardez ces méthodes dans Pipeline : La fonction ci-dessus semble un peu déroutante. Examinons d'abord l'explication de ses paramètres de fonction de rappel dans array_reduce :

/**
 * 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)

méthode getInitialSlice, sa valeur de retour est la valeur initiale du paramètre $carray passé à la fonction callbakc Cette valeur est maintenant une fermeture, j'ai mis getInitialSlice et dispatchToRouter du noyau Http Combinez ces deux méthodes, et maintenant, la valeur de $firstSlice est :

/**
 * 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());
}

Ensuite, nous examinons le rappel de array_reduce :

           

//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中间件

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn