Laravel中的中间件和管道功能



Laravel是一个真正庞大而复杂的系统,它试图以最优雅的方式解决Web开发人员的大多数日常任务,并收集尽可能多的工具,而且非常重要的是,要使用尽可能多的人机界面。

今天,我们将重点关注这些工具之一,或者更关注程序员的使用和实现。 缺少完整的文档,以及缺少俄语的文章和极少数的外国文章,促使我决定对框架的这一有趣功能公开一个秘密的面纱,并选择该主题作为我在Habré上的第一篇文章。

中间件


本文假定读者已经熟悉了该框架功能的基本用法,因此我不会在很长一段时间内都在讨论这一点。

开箱即用,Laravel为我们提供了相当强大的功能,用于过滤对应用程序的传入HTTP请求。 我们谈论的是每个人都钟爱的(或不是) 中间件 -开发人员在阅读Laravel的过程中很快遇到了这些类,同时阅读了官方文档的“基础”部分,这并不奇怪-中间件是主要且最重要的之一整个系统所基于的积木。

Laravel中此组件的标准用户案例包括: EncryptCookies / RedirectIfAuthenticated / VerifyCsrfToken ,作为自定义实现的示例,您可以引用中间件应用程序本地化(基于某些请求数据设置所需的本地化),然后再进一步传输请求。

深入深渊


放弃希望大家进来


好吧,既然要点已经解决了,我们就可以深入研究Laravel资料中许多人的可怕地方,包括alpha和omega,即起点和终点。 那些伸出手立即关闭文章的人-慢慢来。 实际上,在此框架的源代码中,从概念上讲,几乎没有什么真正复杂的东西—创建者显然不仅在尝试创建清晰,方便的界面来与他们的创造力一起工作,而且他们还非常尝试直接在源代码级别上做同样的事情,不讨好

我将尝试解释中间件流水线如何在代码和逻辑级别上尽可能简单和可访问的概念,并且我将尽量不介绍它-在本文框架内没有必要的地方。 因此,如果评论中的人完全了解所有源代码行,我请您不要批评我的肤浅叙述。 但是只欢迎提出任何建议和纠正不正确之处。

中间件-跨越路障


我相信,只要有好的榜样,学习任何东西总是容易的。 因此,我邀请您和我以Pipeline的名称研究这种神秘的野兽。 如果确实有这样勇敢的人,那么在进一步阅读之前,我们将需要安装一个空的Laravel项目5.7版-该版本仅是由于它是撰写本文时的最后一个事实,因此上述所有内容至少应与5.4版相同。 那些只想了解本文的本质和结论的人可以安全地跳过此部分。

除了研究组件的行为之外,还有什么比研究组件的行为更好的呢? 也许可以,但是我们会在没有不必要的复杂性的情况下进行操作,并从标准中间件开始分析-即,以整个帮派中最简单和最易懂的方式-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); } } 


在任何经典的中间件类中,都有一个main方法必须直接处理请求并将处理传递给链中的下一个请求-在我们的例子中,这是handle方法。 在这个特定的类中,请求的处理非常简单-“如果用户被授权,则将其重定向到主页,从而终止链。”

如果我们在app / Http / Kernel.php中查看此中间件的注册,我们将看到它已在“路由中间件”中注册。 为了让我们了解系统如何使用该中间件,让我们转到应用程序/ Http /内核继承的类-该类继承自Illuminate \ Foundation \ Http \ Kernel类。 在此阶段,我们将直接打开通往框架源代码的大门,或者更直接地打开它的最重要和主要部分-使用HTTP的核心。 顺便说一句,谁在乎-Laravel基于Symfony的许多组件,特别是在此部分中-基于HttpFoundationHttpKernel

中间件在内核构造函数中的定义和实现如下:

照亮\基础\ Http \内核(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); } } 


代码非常简单明了-对于阵列中的每个中间件,我们在路由器中使用别名/索引对其进行注册。 我们的Route类的aliasMiddleware和middlewareGroups方法只是将中间件添加到路由器对象的数组之一。 但这不包括在本文的上下文中,因此我们将跳过这一刻并继续。

我们真正感兴趣的是sendRequestThroughRoute方法,该方法从字面上解释了如何通过Route 发送 请求

照亮\基础\ Http \内核:: 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()); } 


作为参数,此方法接收一个请求。 此时,我们应该再次查看我们的RedirectIfAuthenticated代码。 我们还在中间件的handle方法中得到了一个请求,稍后我们将需要此注释。

上面的代码具有非常清晰易读的界面- “ Pipeline”,它通过每个已注册的中间件发送请求,然后将其“传输”到路由器 。 迷人而美妙。 我认为在此阶段,我们不会尝试进一步分解此部分代码,我只会简要描述此部分在整个系统中的作用:

在请求进入您的控制器之前,需要执行许多操作,从简单的url本身解析到初始化Request类。 中间件也参与此操作 。 中间件类本身实现了责任链(或称责任链 )的(几乎)设计模式,因此每个具体的中间件类都只是链中的一个链接。

上面,我们还没有返回到最初考虑的RedirectIfAuthenticated类。 该请求正在链中“循环”,包括遍历中间件路由所需的所有请求。 此刻将帮助我们使用自己的链中的链接,这将在以后进行更多介绍。

管道-我们的应用程序的污水处理


我们在上面看到的流水线实施示例之一。 但是,本文的目的不仅是要在与Laravel集成的级别上解释该组件的操作,而且还要解释在我们自己的代码中使用此类的基本原理。

该类本身可以通过其完整的命名空间定义找到:
照亮\管道\管道

该组件可能有很多应用程序,具体取决于您需要解决的特定任务,但是最明显的动机之一就是要求创建自己的请求处理程序链,该请求链不会干扰整个系统的流程,并且仅在业务逻辑级别确定。 同样,类接口具有足够的抽象级别,并且具有足够的功能来实现不同类型的队列。

Laravel中的示例实现


我们实现了最简单,最远离现实的查询链。 我们将使用字符串“ HELLO WORLD”作为数据,并在两个处理程序的帮助下,从中形成字符串“ Hello User”。 该代码是有意简化的。

在立即实施我们自己的“管道”之前,我们需要确定该管道的元素。 元素是用中间件类推编写的:

定义处理程序
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); } } 


然后,我们创建一个“管道”,确定要在其上发送的数据类型,确定我们要通过哪个处理器集合发送该数据,并定义一个回调,该回调将通过整个链传递的数据作为参数接收。 如果整个链中的数据保持不变,则可以省略带有回调的部分:

 $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 

另外,如果您希望或需要在处理程序中定义自己的方法,那么Pipeline接口提供了一种特殊的via('method_name')方法,那么链处理可以这样编写:

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

直接地,我们通过处理器传递的数据绝对可以是任何东西,以及与它们之间的交互。 键入提示并设置链中返回对象的类型将有助于避免数据完整性错误。

结论


Laravel提供了大量的内置类,其中许多类的灵活性使我们能够以足够的简单性来开发复杂的东西。 本文研究了基于Laravel内置的Pipeline类为请求创建简单队列的可能性。 最终代码中此类的实现可以完全不同,并且该工具的灵活性使您可以在构建某些算法时摆脱许多不必要的操作。

如何具体使用框架的此功能取决于分配给您的任务。

Source: https://habr.com/ru/post/zh-CN429214/


All Articles