Laravel中介軟體(Middleware)的核心解讀
本文主要和大家分享Laravel中間件(Middleware)的核心解讀,中間件(Middleware)在Laravel中起著過濾進入應用的HTTP請求對象(Request)和完善離開應用的HTTP回應物件(Reponse)的作用, 而且可以透過應用多個中間件來層層過濾請求、逐步完善對應。這樣就做到了程式的解耦,如果沒有中間件那麼我們必須在控制器中來完成這些步驟,這無疑會造成控制器的臃腫。
舉一個簡單的例子,在一個電商平台上用戶既可以是一個普通用戶在平台上購物也可以在開店後是一個賣家用戶,這兩種用戶的用戶體系往往都是一套,那麼在只有賣家使用者才能存取的控制器裡我們只需要應用兩個中間件來完成賣家使用者的身分認證:
## #
class MerchantController extends Controller{ public function __construct() { $this->middleware('auth'); $this->middleware('mechatnt_auth'); } }
VerifyCsrfToken中間件,在HTTP Requst進入應用程式走過
VerifyCsrfToken中間件時會驗證Token防止跨站請求偽造,在Http Response 離開應用前會為響應添加合適的Cookie。 (laravel5.5開始CSRF中間件只會自動應用在web路由上)
官方文件。
下面我們主要來看Laravel中是怎麼實現中間件的,中間件的設計應用了一種叫做裝飾器的設計模式。 Laravel實例化Application後,會從服務容器解析出Http Kernel對象,透過類別的名字也能看出來Http Kernel就是Laravel裡負責HTTP請求與回應的核心。
/** * @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);
index.php裡面可以看到,從服務容器裡解析出Http Kernel,因為在
bootstrap/app.php裡綁定了
Illuminate\Contracts\Http\Kernel介面的實作類別
App\Http\Kernel所以$kernel其實是
App\Http\Kernel類別的物件。
解析出Http Kernel後Laravel將進入應用的請求物件傳遞給Http Kernel的handle方法,在handle方法負責處理流入應用程式的請求物件並傳回回應物件。
/** * 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)裡:
/** * 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()); }
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); }; }
mixed array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] ) array_reduce() 将回调函数 callback 迭代地作用到 array 数组中的每一个单元中,从而将数组简化为单一的值。 callback ( mixed $carry , mixed $item )carry携带上次迭代里的值; 如果本次迭代是第一次,那么这个值是 initial。item 携带了本次迭代的值。
$destination = function ($request) { $this->app->instance('request', $request); return $this->router->dispatch($request); }; $firstSlice = function ($passable) use ($destination) { return call_user_func($destination, $passable); };
//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是什么意思
以上是Laravel中介軟體(Middleware)的核心解讀的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

熱門話題

Laravel9和CodeIgniter4的最新版本提供了更新的功能和改進。 Laravel9採用MVC架構,提供資料庫遷移、驗證及模板引擎等功能。 CodeIgniter4採用HMVC架構,提供路由、ORM和快取。在性能方面,Laravel9的基於服務提供者設計模式和CodeIgniter4的輕量級框架使其具有出色的性能。在實際應用中,Laravel9適用於需要靈活性和強大功能的複雜項目,而CodeIgniter4適用於快速開發和小型應用程式。

比較Laravel和CodeIgniter的資料處理能力:ORM:Laravel使用EloquentORM,提供類別物件關係映射,而CodeIgniter使用ActiveRecord,將資料庫模型表示為PHP類別的子類別。查詢建構器:Laravel具有靈活的鍊式查詢API,而CodeIgniter的查詢建構器更簡單,基於陣列。資料驗證:Laravel提供了一個Validator類,支援自訂驗證規則,而CodeIgniter的驗證功能內建較少,需要手動編碼自訂規則。實戰案例:用戶註冊範例展示了Lar

Laravel - Artisan 指令 - Laravel 5.7 提供了處理和測試新指令的新方法。它包括測試 artisan 命令的新功能,下面提到了演示?

對於初學者來說,CodeIgniter的學習曲線更平緩,功能較少,但涵蓋了基本需求。 Laravel提供了更廣泛的功能集,但學習曲線稍陡。在性能方面,Laravel和CodeIgniter都表現出色。 Laravel有更廣泛的文件和活躍的社群支持,而CodeIgniter更簡單、輕量級,具有強大的安全功能。在建立部落格應用程式的實戰案例中,Laravel的EloquentORM簡化了資料操作,而CodeIgniter需要更多的手動配置。

在選擇大型專案框架時,Laravel和CodeIgniter各有優勢。 Laravel針對企業級應用程式而設計,提供模組化設計、相依性注入和強大的功能集。 CodeIgniter是一款輕量級框架,更適合小型到中型項目,強調速度和易用性。對於具有複雜需求和大量用戶的大型項目,Laravel的強大功能和可擴展性更為合適。而對於簡單專案或資源有限的情況下,CodeIgniter的輕量級和快速開發能力則較為理想。

對於小型項目,Laravel適用於大型項目,需要強大的功能和安全性。 CodeIgniter適用於非常小的項目,需要輕量級和易用性。

比較了Laravel的Blade和CodeIgniter的Twig模板引擎,根據專案需求和個人偏好進行選擇:Blade基於MVC語法,鼓勵良好程式碼組織和模板繼承。 Twig是第三方函式庫,提供靈活語法、強大過濾器、擴充支援和安全沙箱。

Java框架支援中間件重複使用和資源共享,包括以下策略:透過連接池管理預先建立的中間件連接。利用線程局部儲存將中間件連接與當前線程關聯。使用執行緒池管理可重複使用的執行緒。透過本機或分散式快取儲存經常存取的資料副本。
