Heim > Artikel > Backend-Entwicklung > Kerninterpretation der Laravel-Middleware (Middleware)
Dieser Artikel teilt Ihnen hauptsächlich die Kerninterpretation der Laravel-Middleware (Middleware) mit. Middleware (Middleware) in Laravel spielt die Rolle des Filterns des HTTP-Anforderungsobjekts (Request), das in die Anwendung gelangt Verbesserung des Ausgangs. Die Rolle des HTTP-Antwortobjekts (Reponse) der Anwendung und die Anwendung mehrerer Middlewares können Anforderungen Schicht für Schicht filtern und die Antwort schrittweise verbessern. Dadurch wird eine Entkopplung des Programms erreicht. Wenn keine Middleware vorhanden ist, müssen wir diese Schritte im Controller ausführen, was zweifellos zu einer Überlastung des Controllers führt.
Um ein einfaches Beispiel zu nennen: Auf einer E-Commerce-Plattform kann ein Benutzer entweder ein gewöhnlicher Benutzer sein, der auf der Plattform einkauft, oder ein Verkäuferbenutzer, nachdem er ein Geschäft eröffnet hat. Das Benutzersystem dieser beiden Arten von Benutzern ist oft gleich. Dann müssen wir nur zwei Middlewares im Controller anwenden, auf die nur Verkäuferbenutzer zugreifen können, um die Identitätsauthentifizierung der Verkäuferbenutzer abzuschließen:
class MerchantController extends Controller{ public function __construct() { $this->middleware('auth'); $this->middleware('mechatnt_auth'); } }
Die allgemeine Benutzerauthentifizierung erfolgt in der Authentifizierungs-Middleware. Nach Erfolg geht die HTTP-Anfrage an die Merchant_Auth-Middleware, um die Händler-Benutzerinformationen zu authentifizieren. Die HTTP-Anfrage kann die Controller-Methode eingeben, zu der sie gehen möchte. Mithilfe von Middleware können wir diese Authentifizierungscodes in entsprechende Middleware extrahieren und mehrere Middleware je nach Bedarf frei kombinieren, um HTTP-Anfragen zu filtern.
Ein weiteres Beispiel ist die VerifyCsrfToken
-Middleware, die Laravel automatisch auf alle Routen anwendet. Wenn die HTTP-Anforderung in die Anwendung gelangt und die VerifyCsrfToken
-Middleware durchläuft, wird das Token überprüft, um eine standortübergreifende Anforderungsfälschung zu verhindern . Die HTTP-Antwort wird vor dem Verlassen der Anwendung überprüft. Fügen Sie der Antwort entsprechende Cookies hinzu. (Ab Laravel 5.5 wird CSRF-Middleware nur automatisch auf das Web-Routing angewendet.)
Im obigen Beispiel wird diejenige, die die Anfrage filtert, als Prä-Middleware bezeichnet, und diejenige, die die Antwort vervollständigt, wird als Pre-Middleware bezeichnet Post-Middleware. Ein Bild kann verwendet werden, um den gesamten Prozess zu markieren:
Das Obige beschreibt die Rolle der Middleware in Laravel und welche Art von Code vom Controller zur Middleware verschoben werden sollte Informationen zum Definieren und Verwenden Ihrer eigenen Laravel-Middleware finden Sie in der offiziellen Dokumentation.
Werfen wir einen Blick darauf, wie Middleware in Laravel implementiert wird? Das Design der Middleware wendet ein Designmuster namens Decorator an.
Nachdem Laravel die Anwendung instanziiert hat, analysiert es das HTTP-Kernel-Objekt aus dem Dienstcontainer. Aus dem Namen der Klasse ist auch ersichtlich, dass der HTTP-Kernel der Kern ist, der für HTTP-Anfragen und -Antworten in Laravel verantwortlich ist.
, da die Implementierungsklasse der -Schnittstelle in
gebunden ist, sodass $kernel tatsächlich ein Objekt von Klasse./** * @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);Nach dem Parsen des HTTP-Kernels übergibt Laravel das in die Anwendung eingehende Anforderungsobjekt an die Handle-Methode des HTTP-Kernels. Die Handle-Methode ist für die Verarbeitung des in die Anwendung eingehenden Anforderungsobjekts und die Rückgabe des Antwortobjekts verantwortlich.
index.php
bootstrap/app.php
IlluminateContractsHttpKernel
AppHttpKernel
AppHttpKernel
Der Artikel enthält eine detaillierte Erklärung dieses Teils. Laravel überträgt das Anforderungsobjekt über das Pipeline-Objekt. In der Pipeline erreicht das Anforderungsobjekt eine Aktion des Controllers durch die Voroperation der im HTTP-Kernel definierten Middleware oder die direkte Abschlussverarbeitung, um das Antwortobjekt zu erhalten. Sehen Sie sich diese Methoden in Pipeline an: Die obige Funktion sieht etwas verwirrend aus. Schauen wir uns zunächst die Erklärung ihrer Callback-Funktionsparameter in array_reduce an:
/** * 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)
getInitialSlice-Methode, ihr Rückgabewert ist der Anfangswert des an die callbakc-Funktion übergebenen Parameters $carray. Dieser Wert ist jetzt ein Abschluss, den ich mit getInitialSlice und Http Kernel's packetToRouter kombiniert habe Jetzt ist der Wert von $firstSlice:
/** * 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()); }
Als nächstes werfen wir einen Blick auf den Rückruf von 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中间件中的Closure $next是什么意思
Das obige ist der detaillierte Inhalt vonKerninterpretation der Laravel-Middleware (Middleware). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!