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