亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專(zhuān)欄INFORMATION COLUMN

Laravel核心解讀--中間件(Middleware)

enda / 982人閱讀

摘要:解析出后將進(jìn)入應(yīng)用的請(qǐng)求對(duì)象傳遞給的方法,在方法負(fù)責(zé)處理流入應(yīng)用的請(qǐng)求對(duì)象并返回響應(yīng)對(duì)象。攜帶了本次迭代的值。通過(guò)這種方式讓請(qǐng)求對(duì)象依次流過(guò)了要通過(guò)的中間件,達(dá)到目的地的方法。

中間件(Middleware)在Laravel中起著過(guò)濾進(jìn)入應(yīng)用的HTTP請(qǐng)求對(duì)象(Request)和完善離開(kāi)應(yīng)用的HTTP響應(yīng)對(duì)象(Reponse)的作用, 而且可以通過(guò)應(yīng)用多個(gè)中間件來(lái)層層過(guò)濾請(qǐng)求、逐步完善相應(yīng)。這樣就做到了程序的解耦,如果沒(méi)有中間件那么我們必須在控制器中來(lái)完成這些步驟,這無(wú)疑會(huì)造成控制器的臃腫。

舉一個(gè)簡(jiǎn)單的例子,在一個(gè)電商平臺(tái)上用戶既可以是一個(gè)普通用戶在平臺(tái)上購(gòu)物也可以在開(kāi)店后是一個(gè)賣(mài)家用戶,這兩種用戶的用戶體系往往都是一套,那么在只有賣(mài)家用戶才能訪問(wèn)的控制器里我們只需要應(yīng)用兩個(gè)中間件來(lái)完成賣(mài)家用戶的身份認(rèn)證:

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

在auth中間件里做了通用的用戶認(rèn)證,成功后HTTP Request會(huì)走到merchant_auth中間件里進(jìn)行商家用戶信息的認(rèn)證,兩個(gè)中間件都通過(guò)后HTTP Request就能進(jìn)入到要去的控制器方法中了。利用中間件,我們就能把這些認(rèn)證代碼抽離到對(duì)應(yīng)的中間件中了,而且可以根據(jù)需求自由組合多個(gè)中間件來(lái)對(duì)HTTP Request進(jìn)行過(guò)濾。

再比如Laravel自動(dòng)給所有路由應(yīng)用的VerifyCsrfToken中間件,在HTTP Requst進(jìn)入應(yīng)用走過(guò)VerifyCsrfToken中間件時(shí)會(huì)驗(yàn)證Token防止跨站請(qǐng)求偽造,在Http Response 離開(kāi)應(yīng)用前會(huì)給響應(yīng)添加合適的Cookie。(laravel5.5開(kāi)始CSRF中間件只自動(dòng)應(yīng)用到web路由上)

上面例子中過(guò)濾請(qǐng)求的叫前置中間件,完善響應(yīng)的叫做后置中間件。用一張圖可以標(biāo)示整個(gè)流程:

上面概述了下中間件在laravel中的角色,以及什么類(lèi)型的代碼應(yīng)該從控制器挪到中間件里,至于如何定義和使用自己的laravel 中間件請(qǐng)參考官方文檔。

下面我們主要來(lái)看一下Laravel中是怎么實(shí)現(xiàn)中間件的,中間件的設(shè)計(jì)應(yīng)用了一種叫做裝飾器的設(shè)計(jì)模式,如果你還不知道什么是裝飾器模式可以查閱設(shè)計(jì)模式相關(guān)的書(shū),也可以簡(jiǎn)單參考下這篇文章。

Laravel實(shí)例化Application后,會(huì)從服務(wù)容器里解析出Http Kernel對(duì)象,通過(guò)類(lèi)的名字也能看出來(lái)Http Kernel就是Laravel里負(fù)責(zé)HTTP請(qǐng)求和響應(yīng)的核心。

/**
 * @var AppHttpKernel $kernel
 */
$kernel = $app->make(IlluminateContractsHttpKernel::class);

$response = $kernel->handle(
    $request = IlluminateHttpRequest::capture()
);

$response->send();

$kernel->terminate($request, $response);

index.php里可以看到,從服務(wù)容器里解析出Http Kernel,因?yàn)樵?b>bootstrap/app.php里綁定了IlluminateContractsHttpKernel接口的實(shí)現(xiàn)類(lèi)AppHttpKernel所以$kernel實(shí)際上是AppHttpKernel類(lèi)的對(duì)象。
解析出Http Kernel后Laravel將進(jìn)入應(yīng)用的請(qǐng)求對(duì)象傳遞給Http Kernel的handle方法,在handle方法負(fù)責(zé)處理流入應(yīng)用的請(qǐng)求對(duì)象并返回響應(yīng)對(duì)象。

/**
 * Handle an incoming HTTP request.
 *
 * @param  IlluminateHttpRequest  $request
 * @return IlluminateHttpResponse
 */
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 EventsRequestHandled($request, $response)
    );

    return $response;
}    

中間件過(guò)濾應(yīng)用的過(guò)程就發(fā)生在$this->sendRequestThroughRouter($request)里:

/**
 * Send the given request through the middleware / router.
 *
 * @param  IlluminateHttpRequest  $request
 * @return IlluminateHttpResponse
 */
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());
}

這個(gè)方法的前半部分是對(duì)Application進(jìn)行了初始化,在上一篇講解服務(wù)提供器的文章里有對(duì)這一部分的詳細(xì)講解。Laravel通過(guò)Pipeline(管道)對(duì)象來(lái)傳輸請(qǐng)求對(duì)象,在Pipeline中請(qǐng)求對(duì)象依次通過(guò)Http Kernel里定義的中間件的前置操作到達(dá)控制器的某個(gè)action或者直接閉包處理得到響應(yīng)對(duì)象。

看下Pipeline里這幾個(gè)方法:

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 就是要通過(guò)的中間件
    $pipes = array_reverse($this->pipes);

    //$this->passable就是Request對(duì)象
    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管道的終點(diǎn)或者叫目的地
protected function dispatchToRouter()
{
    return function ($request) {
        $this->app->instance("request", $request);

        return $this->router->dispatch($request);
    };
}

上面的函數(shù)看起來(lái)比較暈,我們先來(lái)看下array_reduce里對(duì)它的callback函數(shù)參數(shù)的解釋?zhuān)?/p>

mixed array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] )

array_reduce() 將回調(diào)函數(shù) callback 迭代地作用到 array 數(shù)組中的每一個(gè)單元中,從而將數(shù)組簡(jiǎn)化為單一的值。

callback ( mixed $carry , mixed $item )
carry
攜帶上次迭代里的值; 如果本次迭代是第一次,那么這個(gè)值是 initial。item 攜帶了本次迭代的值。

getInitialSlice方法,他的返回值是作為傳遞給callbakc函數(shù)的$carry參數(shù)的初始值,這個(gè)值現(xiàn)在是一個(gè)閉包,我把getInitialSlice和Http Kernel的dispatchToRouter這兩個(gè)方法合并一下,現(xiàn)在$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);
};

接下來(lái)我們看看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的父類(lèi)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)) {
                //解析中間件名稱(chēng)和參數(shù) ("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這個(gè)方法的名稱(chēng)換成了carry, 兩者在邏輯上沒(méi)有區(qū)別,所以依然可以參照著5.5版本里中間件的代碼來(lái)看本文。

getSlice會(huì)返回一個(gè)閉包函數(shù), $stack在第一次調(diào)用getSlice時(shí)它的值是$firstSlice, 之后的調(diào)用中就它的值就是這里返回的值個(gè)閉包了:

$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返回的閉包里又會(huì)去調(diào)用父類(lèi)的getSlice方法,他返回的也是一個(gè)閉包,在閉包會(huì)里解析出中間件對(duì)象、中間件參數(shù)(無(wú)則為空數(shù)組), 然后把$passable(請(qǐng)求對(duì)象), $stack和中間件參數(shù)作為中間件handle方法的參數(shù)進(jìn)行調(diào)用。

上面封裝的有點(diǎn)復(fù)雜,我們簡(jiǎn)化一下,其實(shí)getSlice的返回值就是:

$stack = function ($passable) use ($stack, $pipe) {
                //解析中間件和中間件參數(shù),中間件參數(shù)用$parameter代表,無(wú)參數(shù)時(shí)為空數(shù)組
               $parameters = array_merge([$passable, $stack], $parameters)
               return $pipe->handle($parameters)
};

array_reduce每次調(diào)用callback返回的閉包都會(huì)作為參數(shù)$stack傳遞給下一次對(duì)callback的調(diào)用,array_reduce執(zhí)行完成后就會(huì)返回一個(gè)嵌套了多層閉包的閉包,每層閉包用到的外部變量$stack都是上一次之前執(zhí)行reduce返回的閉包,相當(dāng)于把中間件通過(guò)閉包層層包裹包成了一個(gè)洋蔥。

在then方法里,等到array_reduce執(zhí)行完返回最終結(jié)果后就會(huì)對(duì)這個(gè)洋蔥閉包進(jìn)行調(diào)用:

return call_user_func( array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable);

這樣就能依次執(zhí)行中間件handle方法,在handle方法里又會(huì)去再次調(diào)用之前說(shuō)的reduce包裝的洋蔥閉包剩余的部分,這樣一層層的把洋蔥剝開(kāi)直到最后。通過(guò)這種方式讓請(qǐng)求對(duì)象依次流過(guò)了要通過(guò)的中間件,達(dá)到目的地Http Kernel 的dispatchToRouter方法。

通過(guò)剝洋蔥的過(guò)程我們就能知道為什么在array_reduce之前要先對(duì)middleware數(shù)組進(jìn)行反轉(zhuǎn), 因?yàn)榘b是一個(gè)反向的過(guò)程, 數(shù)組$pipes中的第一個(gè)中間件會(huì)作為第一次reduce執(zhí)行的結(jié)果被包裝在洋蔥閉包的最內(nèi)層,所以只有反轉(zhuǎn)后才能保證初始定義的中間件數(shù)組中第一個(gè)中間件的handle方法會(huì)被最先調(diào)用。

上面說(shuō)了Pipeline傳送請(qǐng)求對(duì)象的目的地是Http Kernel 的dispatchToRouter方法,其實(shí)到遠(yuǎn)沒(méi)有到達(dá)最終的目的地,現(xiàn)在請(qǐng)求對(duì)象了只是剛通過(guò)了AppHttpKernel類(lèi)里$middleware屬性里羅列出的幾個(gè)中間件:

protected $middleware = [
    IlluminateFoundationHttpMiddlewareCheckForMaintenanceMode::class,
    IlluminateFoundationHttpMiddlewareValidatePostSize::class,
    AppHttpMiddlewareTrimStrings::class,
    IlluminateFoundationHttpMiddlewareConvertEmptyStringsToNull::class,
    AppHttpMiddlewareTrustProxies::class,
];

當(dāng)請(qǐng)求對(duì)象進(jìn)入Http Kernel的dispatchToRouter方法后,請(qǐng)求對(duì)象在被Router dispatch派發(fā)給路由時(shí)會(huì)進(jìn)行收集路由上應(yīng)用的中間件和控制器里應(yīng)用的中間件。

namespace IlluminateFoundationHttp;
class Kernel implements KernelContract
{
    protected function dispatchToRouter()
    {
        return function ($request) {
            $this->app->instance("request", $request);

            return $this->router->dispatch($request);
        };
    }
}


namespace IlluminateRouting;
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 EventsRouteMatched($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;
        //收集路由和控制器里應(yīng)用的中間件
        $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()
                        );
                    });
    
    }
}

收集完路由和控制器里應(yīng)用的中間件后,依然是利用Pipeline對(duì)象來(lái)傳送請(qǐng)求對(duì)象通過(guò)收集上來(lái)的這些中間件然后到達(dá)最終的目的地,在那里會(huì)執(zhí)行路由對(duì)應(yīng)的控制器方法生成響應(yīng)對(duì)象,然后響應(yīng)對(duì)象會(huì)依次來(lái)通過(guò)上面應(yīng)用的所有中間件的后置操作,最終離開(kāi)應(yīng)用被發(fā)送給客戶端。

限于篇幅和為了文章的可讀性,收集路由和控制器中間件然后執(zhí)行路由對(duì)應(yīng)的處理方法的過(guò)程我就不在這里詳述了,感興趣的同學(xué)可以自己去看Router的源碼,本文的目的還是主要為了梳理laravel是如何設(shè)計(jì)中間件的以及如何執(zhí)行它們的,希望能對(duì)感興趣的朋友有幫助。

本文已經(jīng)收錄在系列文章Laravel源碼學(xué)習(xí)里,歡迎訪問(wèn)閱讀。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/28227.html

相關(guān)文章

  • Laravel核心解讀--控制器

    摘要:下面是剛才說(shuō)的這些步驟對(duì)應(yīng)的核心代碼收集路由和控制器里應(yīng)用的中間件我們?cè)谇懊娴奈恼吕镆呀?jīng)詳細(xì)的解釋過(guò)中間件和路由的原理了,接下來(lái)就看看當(dāng)請(qǐng)求最終找到了路由對(duì)應(yīng)的控制器方法后是如何為控制器方法注入正確的參數(shù)并調(diào)用控制器方法的。 控制器 控制器能夠?qū)⑾嚓P(guān)的請(qǐng)求處理邏輯組成一個(gè)單獨(dú)的類(lèi), 通過(guò)前面的路由和中間件兩個(gè)章節(jié)我們多次強(qiáng)調(diào)Laravel應(yīng)用的請(qǐng)求在進(jìn)入應(yīng)用后首現(xiàn)會(huì)通過(guò)Http Ker...

    fxp 評(píng)論0 收藏0
  • Laravel核心解讀 -- Response

    摘要:設(shè)置生成對(duì)象后就要執(zhí)行對(duì)象的方法了,該方法定義在類(lèi)中,其主要目的是對(duì)進(jìn)行微調(diào)使其能夠遵從協(xié)議。最后會(huì)把完整的響應(yīng)發(fā)送給客戶端。本文已經(jīng)收錄在系列文章源碼學(xué)習(xí)里,歡迎訪問(wèn)閱讀。 Response 前面兩節(jié)我們分別講了Laravel的控制器和Request對(duì)象,在講Request對(duì)象的那一節(jié)我們看了Request對(duì)象是如何被創(chuàng)建出來(lái)的以及它支持的方法都定義在哪里,講控制器時(shí)我們?cè)敿?xì)地描述了...

    TigerChain 評(píng)論0 收藏0
  • Laravel核心解讀--完結(jié)篇

    摘要:過(guò)去一年時(shí)間寫(xiě)了多篇文章來(lái)探討了我認(rèn)為的框架最核心部分的設(shè)計(jì)思路代碼實(shí)現(xiàn)。為了大家閱讀方便,我把這些源碼學(xué)習(xí)的文章匯總到這里。數(shù)據(jù)庫(kù)算法和數(shù)據(jù)結(jié)構(gòu)這些都是編程的內(nèi)功,只有內(nèi)功深厚了才能解決遇到的復(fù)雜問(wèn)題。 過(guò)去一年時(shí)間寫(xiě)了20多篇文章來(lái)探討了我認(rèn)為的Larave框架最核心部分的設(shè)計(jì)思路、代碼實(shí)現(xiàn)。通過(guò)更新文章自己在軟件設(shè)計(jì)、文字表達(dá)方面都有所提高,在剛開(kāi)始決定寫(xiě)Laravel源碼分析地...

    laoLiueizo 評(píng)論0 收藏0
  • Laravel核心解讀--服務(wù)提供器(ServiceProvider)

    摘要:調(diào)用了的可以看出,所有服務(wù)提供器都在配置文件文件的數(shù)組中。啟動(dòng)的啟動(dòng)由類(lèi)負(fù)責(zé)引導(dǎo)應(yīng)用的屬性中記錄的所有服務(wù)提供器,就是依次調(diào)用這些服務(wù)提供器的方法,引導(dǎo)完成后就代表應(yīng)用正式啟動(dòng)了,可以開(kāi)始處理請(qǐng)求了。 服務(wù)提供器是所有 Laravel 應(yīng)用程序引導(dǎo)中心。你的應(yīng)用程序自定義的服務(wù)、第三方資源包提供的服務(wù)以及 Laravel 的所有核心服務(wù)都是通過(guò)服務(wù)提供器進(jìn)行注冊(cè)(register)和引...

    Richard_Gao 評(píng)論0 收藏0
  • Laravel 啟動(dòng)流程

    摘要:年月日階段劃分請(qǐng)求到響應(yīng)的整個(gè)執(zhí)行階段歸納為個(gè)程序啟動(dòng)準(zhǔn)備階段文件自動(dòng)加載服務(wù)容器實(shí)例化基礎(chǔ)服務(wù)提供者的注冊(cè)核心類(lèi)的實(shí)例化請(qǐng)求實(shí)例化階段實(shí)例化實(shí)例請(qǐng)求處理階段準(zhǔn)備請(qǐng)求處理的環(huán)境將請(qǐng)求實(shí)例通過(guò)中間件處理及通過(guò)路由和控制器的分發(fā)控制響應(yīng)發(fā)送和 Last-Modified: 2019年5月10日16:19:07 階段劃分 Laravel 5.5請(qǐng)求到響應(yīng)的整個(gè)執(zhí)行階段歸納為 4 個(gè): ...

    VPointer 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<