Maison >développement back-end >tutoriel php >Qu'est-ce que le middleware Laravel ? Interprétation du middleware Laravel (Middleware)

Qu'est-ce que le middleware Laravel ? Interprétation du middleware Laravel (Middleware)

不言
不言original
2018-07-06 14:42:4810938parcourir

Dans Laravel, le middleware, comme son nom l'indique, intercepte les données de requête, vérifie les données et détermine s'il faut autoriser l'entrée au middleware suivant après le traitement logique entre les requêtes et les réponses. middleware ; peut être utilisé pour l’authentification des autorisations, la journalisation, etc.

Qu'est-ce que le middleware Laravel ? Interprétation du middleware Laravel (Middleware)

Le middleware dans Laravel joue le rôle de filtrer les objets de requête HTTP (Request) entrant dans l'application et d'améliorer les objets de réponse HTTP (Reponse) quittant la fonction d'application. , et peut filtrer les requêtes couche par couche et améliorer progressivement la réponse en appliquant plusieurs middlewares. 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, puis dans le contrôleur auquel seuls les utilisateurs vendeurs peuvent accéder, il suffit d'appliquer deux middlewares 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');
    }
}

Dans le middleware d'authentification, authentification générale des utilisateurs. est terminé. Après le succès, la requête HTTP sera envoyée au middleware Merchant_auth pour authentifier les informations de l'utilisateur du commerçant. Après le passage des deux middlewares, la requête HTTP peut entrer dans la méthode de contrôleur souhaitée. À 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 :
Quest-ce que le middleware Laravel ? Interprétation 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.

Voyons comment implémenter le middleware dans Laravel. La conception du middleware applique un modèle de conception appelé décorateur. Si vous ne savez toujours pas quel est le modèle de décorateur, vous pouvez consulter le modèle de conception. Pour les livres connexes, vous pouvez également vous référer brièvement à cet article.

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

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

Vous pouvez voir dans index.php que le noyau Http est analysé à partir du conteneur de service, car la classe d'implémentation de l'bootstrap/app.phpinterfaceIlluminateContractsHttpKernel est liée dans AppHttpKernel, donc $kernel est en fait Ce qui précède est un objet de classe AppHttpKernel.
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.

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

Le processus d'application de filtrage middleware se produit dans $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());
}

La première moitié de cette méthode consiste à initialiser l'application. Le service a été expliqué dans l'article précédent. Cette partie est expliquée en détail dans l'article du fournisseur. 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 :

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);
    };
}

La fonction ci-dessus semble déroutante. Examinons d'abord l'explication de ses paramètres de fonction de rappel dans array_reduce :

mixed array_reduce. ( array $array , callable $callback [, Mixed $initial = NULL ] )

array_reduce() applique de manière itérative la fonction de rappel à chaque unité du tableau, simplifiant ainsi le tableau en une seule valeur.

callback ( Mixed $carry , Mixed $item )
carry
porte la valeur de la dernière itération ; si cette itération est la première, alors cette valeur est initiale. item porte la valeur de cette itération.

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

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

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

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);
        };
    };
}

Remarque : getSlice est-ce dans la version Laravel 5.5 Le nom du La méthode a été modifiée pour carry. Il n'y a pas de différence logique entre les deux, vous pouvez donc toujours lire cet article en référence au code du middleware dans la version 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,
];

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

namespace Illuminate\Foundation\Http;
class Kernel implements KernelContract
{
    protected function dispatchToRouter()
    {
        return function ($request) {
            $this->app->instance('request', $request);

            return $this->router->dispatch($request);
        };
    }
}


namespace Illuminate\Routing;
class Router implements RegistrarContract, BindingRegistrar
{    
    public function dispatch(Request $request)
    {
        $this->currentRequest = $request;

        return $this->dispatchToRoute($request);
    }
    
    public function dispatchToRoute(Request $request)
    {
        return $this->runRoute($request, $this->findRoute($request));
    }
    
    protected function runRoute(Request $request, Route $route)
    {
        $request->setRouteResolver(function () use ($route) {
            return $route;
        });

        $this->events->dispatch(new Events\RouteMatched($route, $request));

        return $this->prepareResponse($request,
            $this->runRouteWithinStack($route, $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是如何设计中间件的以及如何执行它们的,希望能对感兴趣的朋友有帮助。

以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!

相关推荐:

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