首頁 後端開發 php教程 Laravel中介軟體(Middleware)的核心解讀

Laravel中介軟體(Middleware)的核心解讀

Feb 07, 2018 pm 02:58 PM
laravel middleware 中介軟體


本文主要和大家分享Laravel中間件(Middleware)的核心解讀,中間件(Middleware)在Laravel中起著過濾進入應用的HTTP請求對象(Request)和完善離開應用的HTTP回應物件(Reponse)的作用, 而且可以透過應用多個中間件來層層過濾請求、逐步完善對應。這樣就做到了程式的解耦,如果沒有中間件那麼我們必須在控制器中來完成這些步驟,這無疑會造成控制器的臃腫。

舉一個簡單的例子,在一個電商平台上用戶既可以是一個普通用戶在平台上購物也可以在開店後是一個賣家用戶,這兩種用戶的用戶體系往往都是一套,那麼在只有賣家使用者才能存取的控制器裡我們只需要應用兩個中間件來完成賣家使用者的身分認證:

     ##      #

class MerchantController extends Controller{
    public function __construct()
    {
        $this->middleware('auth');
        $this->middleware('mechatnt_auth');
    }
}

在auth中間件裡做了通用的用戶認證,成功後HTTP Request會走到merchant_auth中間件裡進行商家用戶資訊的認證,兩個中間件都通過後HTTP Request就能進入到要去的控制器方法中了。利用中間件,我們就能把這些認證代碼抽離到對應的中間件中了,而且可以根據需求自由組合多個中間件來對HTTP Request進行過濾。

再例如Laravel自動給所有路由應用的

VerifyCsrfToken中間件,在HTTP Requst進入應用程式走過VerifyCsrfToken中間件時會驗證Token防止跨站請求偽造,在Http Response 離開應用前會為響應添加合適的Cookie。 (laravel5.5開始CSRF中間件只會自動應用在web路由上)

上面範例中過濾請求的叫前置中間件,完善回應的叫做後置中間件。用一張圖可以標示整個流程:


Laravel中介軟體(Middleware)的核心解讀

上面概述了下中間件在laravel中的角色,以及什麼類型的程式碼應該從控制器挪到中間件裡,至於如何定義並使用自己的laravel 中間件請參考

官方文件

下面我們主要來看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());
}

這個方法的前半部是對Application進行了初始化,在上一篇講解服務提供器的文章裡有對這一部分的詳細講解。 Laravel透過Pipeline(管道)物件來傳輸請求對象,在Pipeline中請求對象依序透過Http Kernel裡定義的中間件的前置操作到達控制器的某個action或直接閉包處理得到回應對象。

看下Pipeline裡這幾個方法:

           
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);
    };
}

上面的函數看起來比較暈,我們先來看下array_reduce裡對它的callback函數參數的解釋:

mixed array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] )
array_reduce() 将回调函数 callback 迭代地作用到 array 数组中的每一个单元中,从而将数组简化为单一的值。
callback ( mixed $carry , mixed $item )carry携带上次迭代里的值; 如果本次迭代是第一次,那么这个值是 initial。item 携带了本次迭代的值。
getInitialSlice方法,他的回傳值是作為傳遞給callbakc函數的$carray參數的初始值,這個值現在是一個閉包,我把getInitialSlice和Http Kernel的dispatchToRouter這兩個方法合併一下,現在$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);
};

接下來我們看看array_reduce的callback:

//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中间件实现原理详解

laravel中间件中的Closure $next是什么意思

关于laravel中间件

以上是Laravel中介軟體(Middleware)的核心解讀的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

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

熱門話題

如何用PHP開發問答社區平台 PHP互動社區變現模式詳解 如何用PHP開發問答社區平台 PHP互動社區變現模式詳解 Jul 23, 2025 pm 07:21 PM

1.PHP開發問答社區首選Laravel MySQL Vue/React組合,因生態成熟、開發效率高;2.高性能需依賴緩存(Redis)、數據庫優化、CDN和異步隊列;3.安全性必須做好輸入過濾、CSRF防護、HTTPS、密碼加密及權限控制;4.變現可選廣告、會員訂閱、打賞、佣金、知識付費等模式,核心是匹配社區調性和用戶需求。

Laravel路由參數傳遞與控制器方法匹配指南 Laravel路由參數傳遞與控制器方法匹配指南 Jul 23, 2025 pm 07:24 PM

本文旨在解決Laravel框架中路由參數傳遞與控制器方法匹配的常見錯誤。我們將詳細解釋為何在路由定義中將參數直接寫入控制器方法名會導致“方法不存在”的錯誤,並提供正確的路由定義語法,確保控制器能正確接收並處理路由參數。此外,文章還將探討在刪除操作中使用HTTPDELETE方法的最佳實踐。

Laravel Livewire中動態訪問模型關聯屬性的data_get實踐 Laravel Livewire中動態訪問模型關聯屬性的data_get實踐 Jul 23, 2025 pm 06:51 PM

本文旨在解決LaravelLivewire組件中動態渲染數據時,如何通過字符串路徑高效且安全地訪問模型關聯的深層屬性。當需要根據配置字符串(如"user.name")獲取關聯模型的特定字段時,直接使用對象屬性訪問會失敗。文章將詳細介紹Laravel的data_get輔助函數,並提供代碼示例,展示如何利用它優雅地解決這一問題,確保數據獲取的靈活性和健壯性。

如何在PHP環境中設置環境變量 PHP運行環境變量添加說明 如何在PHP環境中設置環境變量 PHP運行環境變量添加說明 Jul 25, 2025 pm 08:33 PM

PHP設置環境變量主要有三種方式:1.通過php.ini全局配置;2.通過Web服務器(如Apache的SetEnv或Nginx的fastcgi_param)傳遞;3.在PHP腳本中使用putenv()函數。其中,php.ini適用於全局且不常變的配置,Web服務器配置適用於需要隔離的場景,putenv()適用於臨時性的變量。持久化策略包括配置文件(如php.ini或Web服務器配置)、.env文件配合dotenv庫加載、CI/CD流程中動態注入變量。安全管理敏感信息應避免硬編碼,推薦使用.en

如何用PHP開發AI智能表單系統 PHP智能表單設計與分析 如何用PHP開發AI智能表單系統 PHP智能表單設計與分析 Jul 25, 2025 pm 05:54 PM

選擇合適的PHP框架需根據項目需求綜合考慮:Laravel適合快速開發,提供EloquentORM和Blade模板引擎,便於數據庫操作和動態表單渲染;Symfony更靈活,適合複雜系統;CodeIgniter輕量,適用於對性能要求較高的簡單應用。 2.確保AI模型準確性需從高質量數據訓練、合理選擇評估指標(如準確率、召回率、F1值)、定期性能評估與模型調優入手,並通過單元測試和集成測試保障代碼質量,同時持續監控輸入數據以防止數據漂移。 3.保護用戶隱私需採取多項措施:對敏感數據進行加密存儲(如AES

如何讓PHP容器支持自動構建 PHP環境持續集成CI配置方式 如何讓PHP容器支持自動構建 PHP環境持續集成CI配置方式 Jul 25, 2025 pm 08:54 PM

要讓PHP容器支持自動構建,核心在於配置持續集成(CI)流程。 1.使用Dockerfile定義PHP環境,包括基礎鏡像、擴展安裝、依賴管理和權限設置;2.配置GitLabCI等CI/CD工具,通過.gitlab-ci.yml文件定義build、test和deploy階段,實現自動構建、測試和部署;3.集成PHPUnit等測試框架,確保代碼變更後自動運行測試;4.使用Kubernetes等自動化部署策略,通過deployment.yaml文件定義部署配置;5.優化Dockerfile,採用多階段構

Laravel 路由參數傳遞:正確定義控制器方法與路由綁定 Laravel 路由參數傳遞:正確定義控制器方法與路由綁定 Jul 23, 2025 pm 07:06 PM

本文深入探討Laravel路由中控制器方法參數傳遞的正確姿勢。針對常見的將路由參數直接寫入控制器方法名導致的錯誤,詳細闡述了正確的路由定義語法,並強調了Laravel自動參數綁定的機制。同時,文章建議使用更符合RESTful規範的HTTPDELETE方法處理刪除操作,以提升應用的可維護性和語義化。

Laravel的編寫單元和功能測試。 Laravel的編寫單元和功能測試。 Jul 23, 2025 am 02:38 AM

themaindifferenceBeteNInitAndAndFeateTestsInlaraveSthatunIttestSfocusOnisolatedComponentsLikeClassEsesemementemementementsormethods,whileFeatureTestestSsssSssSerusteSeruseSerInteractions.unitTestScheckInternTernTernTernTernTernTernallogicSuchAsamEthogicSuchAdeTurningThecorRectValueTheCorrectValue,areSeSford and doffore,andDonteveve

See all articles