Fonctionnalités Middleware et Pipeline dans Laravel



Laravel est un système vraiment grand et complexe qui essaie de résoudre la plupart des tâches quotidiennes d'un développeur Web de la manière la plus élégante et de collecter autant d'outils que possible et, ce qui est très important, avec autant d'interface humaine que possible.

Et aujourd'hui, nous allons nous concentrer sur l'un de ces outils, ou plutôt sur son utilisation et sa mise en œuvre par le programmeur. Le manque de documentation complète, ainsi que le manque d'articles en russe et un très petit nombre d'articles étrangers, m'ont poussé à décider de révéler un certain voile de secret sur cette caractéristique intéressante du cadre et à choisir ce sujet comme mon premier article sur Habré.

Middleware


Cet article suppose que le lecteur est déjà familiarisé avec l'utilisation de base de cette fonctionnalité du framework, donc je ne m'attarderai pas sur ce point pendant longtemps.

Prêt à l'emploi, Laravel nous fournit une fonctionnalité assez puissante pour filtrer les requêtes HTTP entrantes vers notre application. Nous parlons du middleware bien-aimé (ou pas) de tout le monde - le développeur rencontre ces classes assez rapidement sur le chemin de la maîtrise de Laravel, tout en lisant la section «Les bases» de la documentation officielle, et cela n'est pas surprenant - le middleware est l'un des principaux et des plus importants briques sur la base desquelles l'ensemble du système est construit.

Des exemples de cas utilisateur standard de ce composant dans Laravel sont: EncryptCookies / RedirectIfAuthenticated / VerifyCsrfToken , et comme exemple d'implémentation personnalisée, vous pouvez citer la localisation de l'application middleware (en définissant la localisation requise en fonction de certaines données de demande) avant de transférer la demande.

Plus profondément dans l'abîme


Abandonnez tout espoir de venir ici


Eh bien, maintenant que les points principaux sont terminés - nous pouvons plonger dans un endroit terrible pour beaucoup - en alpha et oméga, le début et la fin - dans les sources de Laravel . Ceux qui ont tendu la main pour clore l'article immédiatement - prenez votre temps. En fait, dans le code source de ce framework, il n'y a presque rien de vraiment compliqué du côté conceptuel - les créateurs essaient clairement non seulement de créer une interface claire et pratique pour travailler avec leur idée originale, mais ils essaient également de faire la même chose directement au niveau du code source, ce qui ne peut pas ne pas plaire.

J'essaierai d'expliquer le concept de la façon dont Middleware et Pipeline fonctionnent au niveau du code et de la logique aussi simple et accessible que possible, et j'essaierai de ne pas entrer dans les détails - là où ce n'est pas nécessaire dans le cadre de l'article. Donc s'il y a des gens dans les commentaires qui connaissent par cœur toutes les lignes sources, je vous demande de ne pas critiquer ma narration superficielle. Mais toutes recommandations et corrections d'inexactitudes ne sont que les bienvenues.

Middleware - à travers les barricades


Je crois qu'apprendre n'importe quoi est toujours plus facile quand de bons exemples sont fournis. Par conséquent, je vous invite, moi et moi, à étudier cette mystérieuse bête sous le nom de Pipeline . Si de tels hommes courageux existent vraiment, alors avant de poursuivre la lecture, nous devrons installer une version 5.7 du projet Laravel vide - la version n'est due qu'au fait qu'elle est la dernière au moment de la rédaction, tout ce qui précède devrait être identique au moins à la version 5.4. Ceux qui veulent simplement connaître l'essence et les conclusions de l'article peuvent sauter cette partie en toute sécurité.

Quoi de mieux que d'étudier le comportement d'un composant, si ce n'est d'étudier le comportement déjà intégré au système? Peut-être que quelque chose peut le faire, mais nous ferons sans complications inutiles et commencerons notre analyse avec le middleware standard - à savoir, avec le plus simple et le plus compréhensible de tout le gang - RedirectIfAuthenticated :

RedirectIfAuthenticated.php
class RedirectIfAuthenticated { /**      * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @param string|null $guard * @return mixed */ public function handle($request, Closure $next, $guard = null) { if (Auth::guard($guard)->check()) { return redirect('/'); } return $next($request); } } 


Dans toute classe de middleware classique, il existe une méthode principale qui doit traiter directement la demande et passer le traitement à la suivante de la chaîne - dans notre cas, c'est la méthode du handle . Dans cette classe particulière, le traitement de la demande est assez simple - "si l'utilisateur est autorisé, alors redirigez-le vers la page principale et, ainsi, mettez fin à la chaîne."

Si nous regardons l'enregistrement de ce middleware dans app / Http / Kernel.php , nous verrons qu'il est enregistré dans le 'middleware de route'. Afin de découvrir comment le système fonctionne avec ce middleware, allons à la classe dont hérite notre application / Http / Kernel - et elle hérite de la classe Illuminate \ Foundation \ Http \ Kernel . À ce stade, nous ouvrirons directement les portes de l' enfer du code source de notre framework, ou plutôt, de la partie la plus importante et principale de celui-ci - au cœur du travail avec HTTP. Soit dit en passant, peu importe - Laravel est basé sur de nombreux composants de Symfony , en particulier dans cette partie - sur HttpFoundation et HttpKernel .

La définition et l'implémentation de notre middleware dans le constructeur du noyau sont les suivantes:

Illuminate \ Foundation \ Http \ Kernel (Application $ app, Router $ router)
  /**    HTTP Kernel . * Create a new HTTP kernel instance. * * @param \Illuminate\Contracts\Foundation\Application $app * @param \Illuminate\Routing\Router $router * @return void */ public function __construct(Application $app, Router $router) { $this->app = $app; $this->router = $router; $router->middlewarePriority = $this->middlewarePriority; foreach ($this->middlewareGroups as $key => $middleware) { $router->middlewareGroup($key, $middleware); } foreach ($this->routeMiddleware as $key => $middleware) { $router->aliasMiddleware($key, $middleware); } } 


Le code est assez simple et direct - pour chaque middleware du tableau, nous l'enregistrons avec un alias / index dans notre routeur. Les méthodes aliasMiddleware et middlewareGroups de notre classe Route ajoutent simplement un middleware à l'un des tableaux de l'objet routeur. Mais cela n'est pas inclus dans le contexte de l'article, nous allons donc sauter ce moment et passer à autre chose.

Ce qui nous intéresse vraiment, c'est la méthode sendRequestThroughRoute , qui traduit littéralement comment envoyer une demande via Route :

Illuminate \ Foundation \ Http \ Kernel :: sendRequestThroughRouter ($ request)
  /**     middleware / router. * Send the given request through the middleware / router. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ protected function sendRequestThroughRouter($request) { // *    * return (new Pipeline($this->app)) ->send($request) ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) ->then($this->dispatchToRouter()); } 


En paramètre, cette méthode reçoit une requête. À ce stade, nous devrions revoir notre code RedirectIfAuthenticated . Nous recevons également une requête dans la méthode handle de notre middleware, nous aurons besoin de cette note un peu plus tard.

Le code ci-dessus a une interface très claire et lisible - «Pipeline», qui envoie une demande via chacun des middlewares enregistrés, puis la «transfère» au routeur . Charmant et merveilleux. Je pense qu'à ce stade, nous n'essaierons pas de décomposer davantage cette section de code, je ne décrirai que brièvement le rôle de cette section dans l'ensemble du système:

Avant qu'une requête ne pénètre dans votre contrôleur, de nombreuses actions passent, de la simple analyse de l'url elle-même à l'initialisation de la classe Request . L'intergiciel est également impliqué dans cette chaîne d' actions. Les classes de middleware elles-mêmes implémentent le (presque) modèle de conception de la chaîne de responsabilité , ou chaîne de responsabilité , de sorte que chaque classe de middleware particulière n'est qu'un maillon de la chaîne.

Ci-dessus, nous ne sommes pas seulement revenus à notre classe RedirectIfAuthenticated initialement considérée. La demande "circule" le long de la chaîne, y compris celle qui passe par tout ce qui est requis pour la route du middleware. Ce moment nous aidera à travailler avec nos propres liens dans notre propre chaîne, plus à ce sujet plus tard.

Pipeline - Assainissement de notre application


Un exemple de la mise en œuvre de Pipeline que nous avons vu ci-dessus. Mais le but de l'article n'était pas seulement d'expliquer le fonctionnement de ce composant au niveau de l'intégration avec Laravel, mais aussi d'expliquer le principe de base de travailler avec cette classe dans notre propre code.

La classe elle-même peut être trouvée par sa définition complète avec namespace:
Illuminate \ Pipeline \ Pipeline

Il peut y avoir beaucoup d'applications pour ce composant, selon la tâche spécifique que vous devez résoudre, mais l'une des motivations les plus évidentes est la nécessité de créer votre propre chaîne de gestionnaires de demandes, qui n'interfère pas avec les processus de l'ensemble du système et est déterminée exclusivement au niveau de votre logique métier. De plus, l'interface de classe a un niveau d'abstraction suffisant et dispose de fonctionnalités suffisantes pour implémenter différents types de files d'attente.

Exemple d'implémentation dans Laravel


Nous mettons en œuvre la chaîne de requête la plus simple et la plus éloignée de la réalité. Nous utiliserons la chaîne "HELLO WORLD" comme données, et avec l'aide de deux gestionnaires, nous en formerons la chaîne "Hello User". Le code est intentionnellement simplifié.

Avant la mise en œuvre immédiate de notre propre «Pipe», nous devons identifier les éléments de cette pipe. Les éléments sont écrits par analogie avec le middleware:

Définition des gestionnaires
StrToLowerAction.php:
 use Closure; class StrToLowerAction { /** * Handle an incoming request. * * @param string $content * @param Closure $next * @return mixed */ public function handle(string $content, Closure $next) { $content = strtolower($content); return $next($content); } } 

SetUserAction.php:

 use Closure; class SetUserAction { /** * Handle an incoming request. * * @param string $content * @param Closure $next * @return mixed */ public function handle(string $content, Closure $next) { $content = ucwords(str_replace('world', 'user', $content)); return $next($content); } } 


Ensuite, nous créons un "pipeline", déterminons le type de données que nous voulons envoyer dessus, déterminons à travers quelle collection de processeurs nous voulons envoyer ces données, et définissons également un rappel, qui reçoit en argument nos données transmises à travers toute la chaîne. Dans le cas où les données de la chaîne restent inchangées, la partie avec rappel peut être omise:

 $pipes = [ StrToLowerAction::class, SetUserNameAction::class ]; $data = 'Hello world'; $finalData = app(Pipeline::class) ->send($data) // ,       ->through($pipes) //   ->then(function ($changedData) { return $changedData; //  ,    }); var_dump($finalData); //      $finalData 

De plus, si vous avez un désir ou besoin de définir votre propre méthode dans les gestionnaires, l'interface Pipeline fournit une méthode spéciale via ('method_name') , alors le traitement en chaîne peut être écrit de cette façon:

 $finalData = app(Pipeline::class) ->send($data) ->through($pipes) ->via('handle') //      ,         ->then(function ($changedData) { return $changedData; }); 

Directement, les données que nous transmettons aux processeurs peuvent être absolument n'importe quoi, ainsi que l'interaction avec eux. La saisie d'indices et la définition du type de l'objet renvoyé dans la chaîne aideront à éviter les erreurs d'intégrité des données.

Conclusion


Laravel fournit un grand nombre de classes intégrées, et la flexibilité de beaucoup d'entre elles nous permet de développer quelque chose de complexe avec une simplicité suffisante. Cet article a examiné la possibilité de créer des files d'attente simples pour les demandes basées sur la classe Pipeline intégrée à Laravel. Les implémentations de cette classe dans le code final peuvent être complètement différentes, et la flexibilité de cet outil vous permet de vous débarrasser de nombreuses actions inutiles lors de la construction de certains algorithmes.

La façon d'utiliser spécifiquement cette fonctionnalité du cadre dépend des tâches qui vous sont assignées.

Source: https://habr.com/ru/post/fr429214/


All Articles