In Laravel fängt die Middleware, wie der Name schon sagt, Anforderungsdaten ab, überprüft die Daten und bestimmt, ob der Zugriff auf die nächste Middleware zulässig ist, nachdem die logische Verarbeitung zwischen Anforderungen und Antworten erfolgt. Middleware ist in Präfixe unterteilt. Middleware; kann zur Berechtigungsauthentifizierung, Protokollierung usw. verwendet werden.
Middleware in Laravel spielt die Rolle des Filterns der HTTP-Anfrageobjekte (Request), die in die Anwendung gelangen, und der Verbesserung der HTTP-Antwortobjekte (Reponse), die die Anwendung verlassen und kann Anfragen Schicht für Schicht filtern und die Antwort durch die Anwendung mehrerer Middlewares 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 im Controller, auf den nur Verkäuferbenutzer zugreifen können, nur zwei Middlewares anwenden, um die Identitätsauthentifizierung der Verkäuferbenutzer abzuschließen:
class MerchantController extends Controller$ { public function __construct() { $this->middleware('auth'); $this->middleware('mechatnt_auth'); } }
Nachdem wir die universelle Benutzerauthentifizierung in der Authentifizierungs-Middleware durchgeführt haben, Nach Erfolg wird die HTTP-Anfrage an die Merchant_auth-Middleware weitergeleitet, um die Händlerbenutzerinformationen zu authentifizieren. Nachdem beide Middlewares übergeben wurden, kann die HTTP-Anfrage die gewünschte Controller-Methode eingeben. Mit 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 Webrouting 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.
Schauen wir uns an, wie man Middleware in Laravel implementiert. Beim Design von Middleware wird ein Designmuster namens Decorator angewendet. Wenn Sie noch nicht wissen, was das Decorator-Muster ist, können Sie sich das Designmuster ansehen. Für verwandte Bücher können Sie auch kurz auf diesen Artikel verweisen.
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.
/** * @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);
Wie Sie in index.php
sehen können, wird der HTTP-Kernel aus dem Service-Container analysiert, da die Implementierungsklasse der bootstrap/app.php
SchnittstelleIlluminateContractsHttpKernel
in AppHttpKernel
gebunden ist, tatsächlich $kernel AppHttpKernel
Klassenobjekt.
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.
/** * 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; }
Der Prozess der Middleware-Filteranwendung erfolgt in $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()); }
Die erste Hälfte dieser Methode besteht darin, die Anwendung zu initialisieren. Im vorherigen Artikel wird der Dienstanbieter ausführlich erläutert 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.
Schauen Sie sich diese Methoden in Pipeline an:
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); }; }
Die obige Funktion sieht etwas verwirrend aus. Schauen wir uns zunächst die Erklärung ihrer Callback-Funktionsparameter in array_reduce an:
mixed array_reduce ( array $array , callable $callback [, Mixed $initial = NULL ] )array_reduce() wendet die Callback-Funktion callback iterativ auf jede Einheit im Array-Array an und vereinfacht so das Array in einen einzelnen Wert.
Callback ( Mixed $carry , Mixed $item )
Carry
trägt den Wert in der letzten Iteration; wenn diese Iteration die erste ist, dann ist dieser Wert initial. item trägt den Wert dieser Iteration.
getInitialSlice-Methode, ihr Rückgabewert ist der Anfangswert des $carry-Parameters, der an die callbakc-Funktion übergeben wird. Dieser Wert ist jetzt ein Abschluss, den ich mit den beiden Methoden getInitialSlice und Http Kernel zusammenführe. Jetzt ist der Wert von $firstSlice:
$destination = function ($request) { $this->app->instance('request', $request); return $this->router->dispatch($request); }; $firstSlice = function ($passable) use ($destination) { return call_user_func($destination, $passable); };
Als nächstes schauen wir uns den Rückruf von array_reduce an:
//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); }; }; }
Hinweis: Der Name der getSlice-Methode wurde in Laravel 5.5 Version It geändert ist zu einem Carry geworden. Es gibt keinen logischen Unterschied zwischen den beiden, daher können Sie diesen Artikel immer noch mit Bezug auf den Middleware-Code in Version 5.5 lesen.
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中文网!
相关推荐:
Das obige ist der detaillierte Inhalt vonWas ist Laravel-Middleware? Interpretation der Laravel-Middleware (Middleware). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!