laravel5.5框架解析[3]——響應(yīng)Request的流程

laravel5.5框架解析系列文章屬于對(duì)laravel5.5框架源碼分析,如有需要,建議按順序閱讀該系列文章, 不定期更新,歡迎關(guān)注

掌握l(shuí)aravel應(yīng)用的代碼執(zhí)行流程, 對(duì)解決項(xiàng)目構(gòu)建過(guò)程中遇到的一些疑難雜癥大有裨益.

index.php

作為一個(gè)單入口的應(yīng)用, 想要了解執(zhí)行流程當(dāng)然是去看index.php咯

// 記錄一下框架啟動(dòng)時(shí)間, 可以看一次請(qǐng)求花了多長(zhǎng)時(shí)間來(lái)響應(yīng)
define('LARAVEL_START', microtime(true));

// composer自動(dòng)加載
require __DIR__.'/../vendor/autoload.php';

// 這個(gè)bootstrap文件里創(chuàng)建了一個(gè)Application實(shí)例
$app = require_once __DIR__.'/../bootstrap/app.php';

// 通過(guò)容器創(chuàng)建了一個(gè)http kernel
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

// Request類通過(guò)全局變量創(chuàng)建了一個(gè)Request實(shí)例,
// 通過(guò)調(diào)用kernel的handle方法, 就得到了一個(gè)response
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);
// 把response內(nèi)容發(fā)送到瀏覽器
$response->send();

// 執(zhí)行一些耗時(shí)的后續(xù)工作
$kernel->terminate($request, $response);

好簡(jiǎn)單有木有:)

如何啟動(dòng)Application

創(chuàng)建應(yīng)用實(shí)例

創(chuàng)建Application實(shí)例的bootstrap.php代碼如下


// 傳入項(xiàng)目目錄,實(shí)例化Application, 即容器. Application繼承自Container
$app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);


// 綁定http kernel實(shí)現(xiàn)類, 單例模式
$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

// 綁定 console kernel 實(shí)現(xiàn)類
$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

// 綁定異常處理實(shí)現(xiàn)類
$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

return $app;

好像沒(méi)啥步驟啊, 為啥還把創(chuàng)建Application單獨(dú)寫(xiě)一個(gè)文件, 不放在index.php里? 因?yàn)閕ndex.php是由cgi進(jìn)程執(zhí)行的,但是你可能需要在cli環(huán)境下運(yùn)行Application喲, 比如測(cè)試, 和artisan. 所以就單獨(dú)放在文件, 需要的地方再require.

你可能奇怪了怎么沒(méi)有注冊(cè)綁定那些service啊? 請(qǐng)繼續(xù)往下看

Application 初始化

Application代碼

public function __construct($basePath = null)
{
    // 項(xiàng)目目錄, 很多地方要用
    if ($basePath) {
        $this->setBasePath($basePath);
    }
   
    // 基本綁定
    $this->registerBaseBindings();
    
    // 基本service綁定
    $this->registerBaseServiceProviders();

    // 別名
    $this->registerCoreContainerAliases();
}

protected function registerBaseBindings()
{
    // 把實(shí)例存在類里邊
    static::setInstance($this);
    
    // 把自己放到容器
    $this->instance('app', $this);
    
    // 把自己放到容器again, 并綁到Container的實(shí)現(xiàn)
    $this->instance(Container::class, $this);
    
    // PackageManifest,這個(gè)東西是laravel5.5新增的,
    // 5.5 安裝拓展包, 不需要手動(dòng)配provider了(如果拓展包支持的話),
    // 他會(huì)從拓展包的composer.json extra配置中讀取.
    // 個(gè)人認(rèn)為其實(shí)可以通過(guò)composer插件的方式安裝拓展包,
    // 這樣可以在安裝階段配置service provider, 而不是運(yùn)行階段
    $this->instance(PackageManifest::class, new PackageManifest(
        new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
    ));
}

// 加載基本的service provider, 啟動(dòng)模塊
protected function registerBaseServiceProviders()
{
    // 事件模塊
    $this->register(new EventServiceProvider($this));
    
    // log模塊
    $this->register(new LogServiceProvider($this));

    // 路由模塊
    $this->register(new RoutingServiceProvider($this));
}

// 這就把web組件都給綁定到實(shí)現(xiàn)類并注冊(cè)了一個(gè)別名, 然后你就可以各種app('config'), $app['config'], $app->make('config')
public function registerCoreContainerAliases()
{
    foreach ([
        'app'                  => [\Illuminate\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class,  \Psr\Container\ContainerInterface::class],
        'auth'                 => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
        'auth.driver'          => [\Illuminate\Contracts\Auth\Guard::class],
        'blade.compiler'       => [\Illuminate\View\Compilers\BladeCompiler::class],
        'cache'                => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
        'cache.store'          => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class],
        'config'               => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class],
        'cookie'               => [\Illuminate\Cookie\CookieJar::class, \Illuminate\Contracts\Cookie\Factory::class, \Illuminate\Contracts\Cookie\QueueingFactory::class],
        'encrypter'            => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class],
        'db'                   => [\Illuminate\Database\DatabaseManager::class],
        'db.connection'        => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
        'events'               => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
        'files'                => [\Illuminate\Filesystem\Filesystem::class],
        'filesystem'           => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
        'filesystem.disk'      => [\Illuminate\Contracts\Filesystem\Filesystem::class],
        'filesystem.cloud'     => [\Illuminate\Contracts\Filesystem\Cloud::class],
        'hash'                 => [\Illuminate\Contracts\Hashing\Hasher::class],
        'translator'           => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
        'log'                  => [\Illuminate\Log\Writer::class, \Illuminate\Contracts\Logging\Log::class, \Psr\Log\LoggerInterface::class],
        'mailer'               => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class],
        'auth.password'        => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class],
        'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class],
        'queue'                => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class],
        'queue.connection'     => [\Illuminate\Contracts\Queue\Queue::class],
        'queue.failer'         => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class],
        'redirect'             => [\Illuminate\Routing\Redirector::class],
        'redis'                => [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class],
        'request'              => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class],
        'router'               => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class],
        'session'              => [\Illuminate\Session\SessionManager::class],
        'session.store'        => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
        'url'                  => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class],
        'validator'            => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
        'view'                 => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
    ] as $key => $aliases) {
        foreach ($aliases as $alias) {
            $this->alias($key, $alias);
        }
    }
}

Application 貌似啟動(dòng)完成, 看一下這個(gè)時(shí)候容器里有啥
在index.php里 執(zhí)行

$app = require_once __DIR__.'/../bootstrap/app.php';
// ------------- 加入下面這些 -----------
$rf = new ReflectionClass(\Illuminate\Container\Container::class);
$p = $rf->getProperty('resolved');
$p->setAccessible(true);
dd($p->getValue($app));

瀏覽器輸出結(jié)果

[]

哈哈, 啥都沒(méi)有. 雖然只是注冊(cè)了服務(wù),并未resolve, 可是之前不是有注冊(cè)service provider嗎, 難道這個(gè)也不需要resolve?
實(shí)際上此時(shí)service provider都還沒(méi)有被resolve并執(zhí)行. 那么真正執(zhí)行在哪兒呢, 實(shí)際上在http kernel的handle 流程里, 請(qǐng)看下節(jié)

handle($request)

一個(gè)request到底經(jīng)歷了怎樣的千難萬(wàn)險(xiǎn),才得以歷練出正確的response呢? 來(lái)一探究竟
Illuminate\Foundation\Http\Kernel :

public function handle($request)
{
    try {
        // 開(kāi)啟請(qǐng)求method覆蓋, 就是文檔里提到的如何在不支持的瀏覽器里發(fā)送delete等請(qǐng)求
        $request->enableHttpMethodParameterOverride();
        // 把請(qǐng)求發(fā)送給路由, 得到response, 詳情在下邊
        $response = $this->sendRequestThroughRouter($request);
    } catch (Exception $e) { // Request->Response途中遇到異常, 在這進(jìn)行處理
        // 記錄日志
        $this->reportException($e);
        // 生成異常相應(yīng), 以便發(fā)送到瀏覽器
        $response = $this->renderException($request, $e);
    } catch (Throwable $e) {
        // 致命錯(cuò)誤
        $this->reportException($e = new FatalThrowableError($e));

        $response = $this->renderException($request, $e);
    }
    // 觸發(fā)相應(yīng)事件
    $this->app['events']->dispatch(
        new Events\RequestHandled($request, $response)
    );

    return $response;
}

// 請(qǐng)求變響應(yīng)
protected function sendRequestThroughRouter($request)
{
    // request 保存到容器
    $this->app->instance('request', $request);
    
    // 門(mén)面緩存清除, 因?yàn)殚T(mén)面會(huì)從容器中取實(shí)例然后緩存,
    // 剛剛刷新了容器中的Request, 為了讓facade能更新實(shí)例, 就清楚緩存
    Facade::clearResolvedInstance('request');

    // 這個(gè)就是前面提到的會(huì)在handle流程里初始化Application的service provider
    // 為什么Application 會(huì)放在這啟動(dòng)呢? 因?yàn)椴煌闆r下Application需要的加載不同的基礎(chǔ)service provider,
    // 所以就沒(méi)有放在Application中啟動(dòng), 而是提供了bootstrapWith的公開(kāi)方法,
    // 供外部按需傳入service provider 進(jìn)行啟動(dòng), 這里傳入了下面這些provider
    //  用于加載 .env 配置文件
    // \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
    // 加載config文件夾下配置
    // \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
    // 注冊(cè)異常處理, laravel5.5 用whoops來(lái)渲染異常
    // \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
    // 注冊(cè)門(mén)面服務(wù)
    // \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
    // laravel5.5 新功能, 注冊(cè)通過(guò)composer.json來(lái)提供provider類的 provider
    // \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
    // register 完畢之后, 最后boot注冊(cè)過(guò)的service provider
    // \Illuminate\Foundation\Bootstrap\BootProviders::class,
    $this->bootstrap();

    // 最后通過(guò)pipeline, 把Request經(jīng)過(guò)全局中間件, 發(fā)送到路由分發(fā)過(guò)程
    // 后面的文章會(huì)有pipeline實(shí)現(xiàn)原理分析
    return (new Pipeline($this->app))
                ->send($request)
                ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                ->then($this->dispatchToRouter());
}

下面是如何將Request發(fā)送到匹配路由 Illuminate\Routing\Router

public function dispatchToRoute(Request $request)
{
    // 通過(guò)Request匹配到路由, 匹配不到直接拋異常, 而不是返回null.
    // 是不是返回null ,在這里在拋 404 更合理呢?
    $route = $this->findRoute($request);
    
    // 把route綁到request上, 這樣在其他地方, 你可以通過(guò)request 獲取到匹配的路由
    $request->setRouteResolver(function () use ($route) {
        return $route;
    });
    
    // 觸發(fā)路由匹配事件
    $this->events->dispatch(new Events\RouteMatched($route, $request));
    // 執(zhí)行路由, 得到相應(yīng)
    $response = $this->runRouteWithinStack($route, $request);
    // 把控制器或者路由級(jí)中間件返回的結(jié)果(可能是array,string, int 或其他類型), 轉(zhuǎn)換成Response實(shí)例
    return $this->prepareResponse($request, $response);
}
// router中執(zhí)行路由的過(guò)程
protected function runRouteWithinStack(Route $route, Request $request)
{
    // 是否跳過(guò)中間件, 特殊情況下, 或者測(cè)試時(shí)有可能需要跳過(guò)
    $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
                            $this->container->make('middleware.disable') === true;
    //取出路由中間件
    $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
    
    // 把Request過(guò)一遍路由中間件, 然后執(zhí)行路由,
    // 這里也調(diào)用了prepareResponse, 上面那也調(diào)用了, 為什么會(huì)調(diào)用2次? 有趣吧,哈哈.
    // 這里的response其實(shí)是返回給全局中間件那里去了, 而全局中間件可能會(huì)根據(jù)response的內(nèi)容,
    // 決定不予發(fā)送給瀏覽器, 而是自己發(fā)送了一個(gè)其他響應(yīng), 比如返回一個(gè)數(shù)組[code=>500, msg=>'sth. went wrong'],
    // 所以, 經(jīng)過(guò)全局回來(lái)的response還要再prepare一遍. 且保證中間件的handle流程里$next()返回的是一個(gè)Response實(shí)例
    return (new Pipeline($this->container))
                    ->send($request)
                    ->through($middleware)
                    ->then(function ($request) use ($route) {
                        return $this->prepareResponse(
                            $request, $route->run()
                        );
                    });
}

terminate()

pipeline管道是有始有終的, 從哪里進(jìn)去, 就得從哪里出來(lái), 不過(guò)大變活人, Request進(jìn)去, Response出來(lái)了.所以相應(yīng)最后到了http kernel 的handle方法里,在index.php中,相應(yīng)被發(fā)送到瀏覽器
最后執(zhí)行terminal方法 Http\Kernel

public function terminate($request, $response)
{
    // 注冊(cè)的中間件如果有terminate調(diào)用terminate方法
    // session 存盤(pán)就是在中間件terminate中完成的, 所以很多人在controller
    // 中使用了dd()函數(shù), 就發(fā)現(xiàn)session出問(wèn)題了. 因?yàn)閐d()會(huì)使程序直接退出,
    // 這時(shí)候請(qǐng)使用dump()來(lái)輸出變量
    $this->terminateMiddleware($request, $response);
    // Application的terminate, 他會(huì)調(diào)用通過(guò)terminating方法注冊(cè)的回調(diào)
    $this->app->terminate();
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市啄糙,隨后出現(xiàn)的幾起案子隧饼,更是在濱河造成了極大的恐慌静陈,老刑警劉巖诞丽,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件僧免,死亡現(xiàn)場(chǎng)離奇詭異捏浊,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)浊洞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)法希,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)靶瘸,“玉大人奕锌,你說(shuō)我怎么就攤上這事村生。” “怎么了辽话?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵油啤,是天一觀的道長(zhǎng)蟀苛。 經(jīng)常有香客問(wèn)我,道長(zhǎng)幽告,這世上最難降的妖魔是什么裆甩? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任嗤栓,我火速辦了婚禮箍邮,結(jié)果婚禮上锭弊,老公的妹妹穿的比我還像新娘擂错。我一直安慰自己,他們只是感情好桃犬,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布攒暇。 她就那樣靜靜地躺著子房,像睡著了一般。 火紅的嫁衣襯著肌膚如雪田度。 梳的紋絲不亂的頭發(fā)上解愤,一...
    開(kāi)封第一講書(shū)人閱讀 49,741評(píng)論 1 289
  • 那天送讲,我揣著相機(jī)與錄音,去河邊找鬼监右。 笑死异希,一個(gè)胖子當(dāng)著我的面吹牛称簿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播搏色,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼券册,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼垂涯!你這毒婦竟也來(lái)了耕赘?” 一聲冷哼從身側(cè)響起膳殷,我...
    開(kāi)封第一講書(shū)人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤赚窃,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后是掰,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體辱匿,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡匾七,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年昨忆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了丁频。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡扔嵌,死狀恐怖限府,靈堂內(nèi)的尸體忽然破棺而出夺颤,到底是詐尸還是另有隱情痢缎,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布世澜,位于F島的核電站独旷,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏寥裂。R本人自食惡果不足惜嵌洼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一封恰、第九天 我趴在偏房一處隱蔽的房頂上張望麻养。 院中可真熱鬧,春花似錦诺舔、人聲如沸鳖昌。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)畔勤。三九已至,卻和暖如春跛蛋,著一層夾襖步出監(jiān)牢的瞬間糕档,已是汗流浹背莉恼。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留速那,地道東北人俐银。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像端仰,于是被迫代替她去往敵國(guó)和親悉患。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 先說(shuō)幾句廢話榆俺,調(diào)和氣氛售躁。事情的起由來(lái)自客戶需求頻繁變更,偉大的師傅決定橫刀立馬的改革使用新的框架(created ...
    wsdadan閱讀 3,040評(píng)論 0 12
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理茴晋,服務(wù)發(fā)現(xiàn)陪捷,斷路器,智...
    卡卡羅2017閱讀 134,628評(píng)論 18 139
  • 前陣子看了點(diǎn)Laravel源碼诺擅,越看越亂市袖,網(wǎng)上大部分中文文檔都是直譯,比較生澀難懂烁涌,還是決定看英文文檔順便就我的理...
    Bill_Wang閱讀 338評(píng)論 0 2
  • 最近在和同學(xué)參與一個(gè)創(chuàng)業(yè)項(xiàng)目苍碟,用到了laravel,仔細(xì)研究了一下撮执,發(fā)現(xiàn)laravel封裝了很多開(kāi)箱即用的方法微峰,通...
    MakingChoice閱讀 3,300評(píng)論 0 0
  • 反思日志0617 周六 晴 畫(huà)圖 第一次獨(dú)立完成畫(huà)圖,文老師點(diǎn)評(píng)橙和橙紅抒钱,群青和青紫需要再調(diào)一點(diǎn)紅蜓肆。文老師果然厲害...
    娟妹紙李娟閱讀 160評(píng)論 5 0