標(biāo)簽: laravel 源碼分析
在我們學(xué)習(xí)一個框架的過程中,了解一個框架的啟動流程,對于我們理解、使用好框架具有很大幫助,今天我們就來看一下 laravel 框架啟動過程。
框架啟動過程中的相關(guān)類
在 laravel 啟動過程中,主要涉及到以下類:
Illuminate\Foundation\Application
Application 是 laravel 框架最核心的類之一。它首先是一個 IOC 容器,管理整個框架類對象的定義、實例化、存儲;同時它也是整個框架的應(yīng)用類,管理應(yīng)用中的 ServiceProvider,啟動、停止應(yīng)用,啟動針對不同應(yīng)用的啟動器。App\Http\Kernel
針對 http 請求的核心類,繼承自Illuminate\Foundation\Http\Kernel,管理框架中的路由,配置針對 http 應(yīng)用的啟動器,執(zhí)行 http 請求,停止針對 http 請求的應(yīng)用。Illuminate\Routing\Router
框架路由系統(tǒng)的門面類,管理并配置路由,將請求分發(fā)到路由并返回路由執(zhí)行請求的響應(yīng)。Illuminate\Routing\Route
框架路由系統(tǒng)的路由類,每個配置的路由對應(yīng)類的一個實例,里面記錄了匹配此路由請求的 uri、methods 等信息,以及對應(yīng)的執(zhí)行請求的控制器方法或者回調(diào)等信息,實現(xiàn)了判斷一個請求是否匹配此路由的方法。Illuminate\Http\Request
http 請求類,主要作用是封裝 http 請求的query、server、input、 file、cookie、session 等參數(shù)。Illuminate\Http\Response
http 響應(yīng)類,構(gòu)造框架的 http 響應(yīng),里面包括響應(yīng)碼、響應(yīng)內(nèi)容、響應(yīng)頭等信息。實現(xiàn)了發(fā)送響應(yīng)的方法。
框架啟動過程源碼分析
接下來,我們來看框架的啟動過程。我們知道,針對一個 web 應(yīng)用,其入口文件是在 /public/index.php,我們來看這個文件具體內(nèi)容。
//注冊應(yīng)用的自動加載器
require __DIR__.'/../bootstrap/autoload.php';
//創(chuàng)建 Application 類對象,做初始配置并返回
$app = require_once __DIR__.'/../bootstrap/app.php';
//創(chuàng)建針對 http 請求的 $kernel 對象
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
//在 $kernel 中執(zhí)行請求 $request,并得到響應(yīng) $response
//$response 是 Illuminate\Http\Response 類的實例
$response = $kernel->handle(
//根據(jù) http 請求創(chuàng)建 $request 對象
//其為 Illuminate\Http\Request 對象實例
//管理 http 請求的query, server, input, file, cookie, session等信息
$request = Illuminate\Http\Request::capture()
);
//發(fā)送響應(yīng)
$response->send();
//終止針對請求 $request 得到響應(yīng) $response 的 $kernel
$kernel->terminate($request, $response);
我們看了應(yīng)用入口文件的內(nèi)容,了解了框架的大概啟動流程,接下來我們來分析各個流程的具體內(nèi)容。
框架的自動加載文件
在在入口文件中,我們看到,代碼首先加載了框架的自動加載配置文件,我們來看其具體內(nèi)容。
define('LARAVEL_START', microtime(true));
//加載由 composer 生成的框架各個包的自動加載規(guī)則和我們自定義的自動加載規(guī)則
//php 包的自動加載規(guī)則主要遵循 psr-4 標(biāo)準
require __DIR__.'/../vendor/autoload.php';
$compiledPath = __DIR__.'/cache/compiled.php';
if (file_exists($compiledPath)) {
require $compiledPath;
}
框架 Application 類對象的實例化及配置
當(dāng)框架加載完自動加載文件后,開始生成核心類 Application 并做基本配置,我們來看具體代碼。
//創(chuàng)建 Application 類對象
$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);
//添加綁定針對 http 請求的 Kernel 的綁定
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
return $app;
我們來看 Application 的構(gòu)造函數(shù),看一下 Application 類對象的實例化過程
namespace Illuminate\Foundation;
class Application extends Container implements ApplicationContract, HttpKernelInterface
{
/**
* Create a new Illuminate application instance.
*
* @param string|null $basePath
* @return void
*/
public function __construct($basePath = null)
{
//在 Application 的容器里面添加基礎(chǔ)的 binding
//主要是 app 和 Illuminate\Container\Container
$this->registerBaseBindings();
//注冊基礎(chǔ)的 service provider
//主要是啟動事件監(jiān)聽服務(wù)和路由服務(wù)的 service provider
$this->registerBaseServiceProviders();
//為框架里面的核心類注冊別名
$this->registerCoreContainerAliases();
if ($basePath) {
//設(shè)置應(yīng)用的基礎(chǔ)路徑,并將基于基礎(chǔ)路徑得到的常用的其他路徑綁定到容器
$this->setBasePath($basePath);
}
}
}
Kernel 對象實例化及基本功能
在入口文件中,我們看到實例化 Application 類對象后,就開始了創(chuàng)建 Kernel 對象,創(chuàng)建請求的 $request 對象,并在 Kernel 對象中執(zhí)行請求 $request,得到相應(yīng) $response。如下代碼所示:
//創(chuàng)建針對 http 請求的 $kernel 對象
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
//在 $kernel 中執(zhí)行請求 $request,并得到響應(yīng) $response
//$response 是 Illuminate\Http\Response 類的實例
$response = $kernel->handle(
//根據(jù) http 請求創(chuàng)建 $request 對象
//其為 Illuminate\Http\Request 對象實例
//管理 http 請求的query, server, input, file, cookie, session等信息
$request = Illuminate\Http\Request::capture()
);
實例化 Kernel 對象
我們先來看 Kernel 對象的實例化過程
namespace Illuminate\Foundation\Http;
use Illuminate\Contracts\Http\Kernel as KernelContract;
class Kernel implements KernelContract{
/**
* The application implementation.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* The router instance.
*
* @var \Illuminate\Routing\Router
*/
protected $router;
/**
* The application's global HTTP middleware stack.
* 運行 http 請求全局生效的中間件
* These middleware are run during every request to your application.
*
* @var array
*/
protected $middleware = [];
/**
* The application's route middleware groups.
* 應(yīng)用中路由的中間件組
* @var array
*/
protected $middlewareGroups = [];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
* 路由中可能會用到的中間件的簡化別名
* @var array
*/
protected $routeMiddleware = [];
/**
* The priority-sorted list of middleware.
* Forces the listed middleware to always be in the given order.
* 中間件的優(yōu)先級,強制下面這些中間件按照給定的順序執(zhí)行
* @var array
*/
protected $middlewarePriority = [
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Auth\Middleware\Authenticate::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
/**
* Create a new HTTP kernel instance.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @param \Illuminate\Routing\Router $router
* @return void
*/
public function __construct(Application $app, Router $router)
{
$this->app = $app;
$this->router = $router;
//設(shè)置路由的中間件優(yōu)先級
$router->middlewarePriority = $this->middlewarePriority;
//設(shè)置路由的中間件組
foreach ($this->middlewareGroups as $key => $middleware) {
$router->middlewareGroup($key, $middleware);
}
//設(shè)置路由的中間件別名
foreach ($this->routeMiddleware as $key => $middleware) {
$router->middleware($key, $middleware);
}
}
}
我們看到在實例化 Kernel 對象過程中,主要設(shè)置了路由系統(tǒng)的中間件優(yōu)先級、中間件別名和中間件組。
Kernel 對象執(zhí)行請求過程
接下來我們來看在 $kernel 對象中執(zhí)行請求 $request 并返回相應(yīng) $response 的過程。
namespace Illuminate\Foundation\Http;
use Illuminate\Contracts\Http\Kernel as KernelContract;
class Kernel implements KernelContract{
/**
* The application implementation.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* The router instance.
*
* @var \Illuminate\Routing\Router
*/
protected $router;
/**
* Handle an incoming HTTP request.
* 執(zhí)行一個 http 請求
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function handle($request)
{
try {
$request->enableHttpMethodParameterOverride();
//將請求通過中間件分發(fā)給路由
$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']->fire('kernel.handled', [$request, $response]);
return $response;
}
/**
* Send the given request through the middleware / router.
* 發(fā)送 $request,通過全局中間件,并分發(fā)給路由
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
protected function sendRequestThroughRouter($request)
{
//在 Application 類對象上綁定請求類 $request
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
//根據(jù)這個 http 請求啟動應(yīng)用,運行針對 http 請求的啟動器
$this->bootstrap();
//將請求 $request 通過中間件,并分發(fā)給路由
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
/**
* Get the route dispatcher callback.
* 返回在路由上分發(fā)請求的閉包
* @return \Closure
*/
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);
//根據(jù)路由分發(fā)請求
return $this->router->dispatch($request);
};
}
}
我們看到將請求 $response 分發(fā)到路由并執(zhí)行返回其響應(yīng)調(diào)用了路由系統(tǒng)的相關(guān)方法,我們來看具體實現(xiàn)。
路由系統(tǒng)分發(fā)并執(zhí)行請求
在 laravel 框架中,Illuminate\Routing\Router 類主要的作用是配置應(yīng)用的路由,配置路由可能用到的中間件、中間件組,根據(jù)配置的路由表分配請求,根據(jù)分配到的路由運行請求得到相應(yīng)。下面我們來看 Illuminate\Routing\Router 類中和框架啟動相關(guān)的源碼
namespace Illuminate\Routing;
class Router implements RegistrarContract
{
/**
* Dispatch the request to the application.
* 分發(fā)請求到某個配置的路由
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function dispatch(Request $request)
{
$this->currentRequest = $request;
return $this->dispatchToRoute($request);
}
/**
* Dispatch the request to a route and return the response.
* 分發(fā)請求到某個路由,并返回執(zhí)行請求得到的響應(yīng)
* @param \Illuminate\Http\Request $request
* @return mixed
*/
public function dispatchToRoute(Request $request)
{
//首先,在用戶配置的路由組里面找到匹配請求的路由
$route = $this->findRoute($request);
$request->setRouteResolver(function () use ($route) {
return $route;
});
//觸發(fā) RouteMatched 事件
$this->events->fire(new Events\RouteMatched($route, $request));
//讓 $request 通過 $route 配置的中間件,得到運行路由的響應(yīng)
$response = $this->runRouteWithinStack($route, $request);
//根據(jù) $request 和 $response,準備 $response 響應(yīng)對象
return $this->prepareResponse($request, $response);
}
/**
* 運行路由的中間件,讓 $request 通過 $route 配置的中間件
* @param \Illuminate\Routing\Route $route
* @param \Illuminate\Http\Request $request
* @return mixed
*/
protected function runRouteWithinStack(Route $route, Request $request)
{
//是否應(yīng)該跳過中間件
$shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
$this->container->make('middleware.disable') === true;
$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
//讓請求 $request 通過 $route 的中間件,
//并最終返回路由執(zhí)行請求的響應(yīng)
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run($request)
);
});
}
/**
* Create a response instance from the given value.
* 根據(jù)運行路由得到的響應(yīng)配置框架的響應(yīng)
* @param \Symfony\Component\HttpFoundation\Request $request
* @param mixed $response
* @return \Illuminate\Http\Response
*/
public function prepareResponse($request, $response)
{
if ($response instanceof PsrResponseInterface) {
$response = (new HttpFoundationFactory)->createResponse($response);
} elseif (! $response instanceof SymfonyResponse) {
$response = new Response($response);
}
return $response->prepare($request);
}
}
laravel 框架路由系統(tǒng)中,類 Illuminate\Routing\Route 也是重要的核心類之一,在框架中,每配置一個請求的路由,都會生成一個 Illuminate\Routing\Route 類的實例,里面記錄類請求的 uri、methods,以及對應(yīng)的執(zhí)行請求的控制器方法或者回調(diào)等信息。接下來我們來看類 Illuminate\Routing\Route 中和框架啟動相關(guān)的內(nèi)容。
namespace Illuminate\Routing;
class Route
{
/**
* Run the route action and return the response.
* 運行路由配置的 action 并返回其響應(yīng)
* @param \Illuminate\Http\Request $request
* @return mixed
*/
public function run(Request $request)
{
$this->container = $this->container ?: new Container;
try {
//如果路由是有控制器的方法執(zhí)行
//運行控制器對應(yīng)的方法并返回
if ($this->isControllerAction()) {
return $this->runController();
}
//運行并返回路由對應(yīng)的回調(diào)
return $this->runCallable();
} catch (HttpResponseException $e) {
return $e->getResponse();
}
}
}
http應(yīng)用的啟動
在 Kernel 對象執(zhí)行請求過程中,我們看到在將請求分發(fā)到路由之前,我們先啟動了針對這個 http 請求的應(yīng)用(通過語句 $this->bootstrap() 實現(xiàn),$this 表示對象 $kernel )。接下來我們來看應(yīng)用的啟動過程。
namespace Illuminate\Foundation\Http;
use Illuminate\Contracts\Http\Kernel as KernelContract;
class Kernel implements KernelContract{
/**
* The application implementation.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* The bootstrap classes for the application.
* 啟動一個 http 請求的應(yīng)用所需的啟動器的類
* @var array
*/
protected $bootstrappers = [
'Illuminate\Foundation\Bootstrap\DetectEnvironment',
'Illuminate\Foundation\Bootstrap\LoadConfiguration',
'Illuminate\Foundation\Bootstrap\ConfigureLogging',
'Illuminate\Foundation\Bootstrap\HandleExceptions',
'Illuminate\Foundation\Bootstrap\RegisterFacades',
'Illuminate\Foundation\Bootstrap\RegisterProviders',
'Illuminate\Foundation\Bootstrap\BootProviders',
];
/**
* Bootstrap the application for HTTP requests.
* 根據(jù)這個 http 請求啟動應(yīng)用,運行針對 http 請求的啟動器
* @return void
*/
public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
$this->app->bootstrapWith($this->bootstrappers());
}
}
/**
* Get the bootstrap classes for the application.
* 返回針對 http 請求的啟動器類
* @return array
*/
protected function bootstrappers()
{
return $this->bootstrappers;
}
}
我看再來看 Application 類中啟動一組啟動器的相關(guān)代碼
namespace Illuminate\Foundation;
class Application extends Container implements ApplicationContract, HttpKernelInterface
{
/**
* Run the given array of bootstrap classes.
* 啟動 $bootstrappers 數(shù)組里面的啟動器
* @param array $bootstrappers
* @return void
*/
public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true;
foreach ($bootstrappers as $bootstrapper) {
//觸發(fā)啟動器 $bootstrapper 啟動的監(jiān)聽事件
$this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);
$this->make($bootstrapper)->bootstrap($this);
//觸發(fā)啟動器 $bootstrapper 啟動后的監(jiān)聽事件
$this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
}
}
}
根據(jù)以上代碼,我們知道所謂啟動針對 http 請求的應(yīng)用,也就是依次運行在 $kernel 對象中配置的啟動器,每個啟動器實現(xiàn)了啟動應(yīng)用過程中的某一塊功能。
各個啟動器具體功能如下:
Illuminate\Foundation\Bootstrap\DetectEnvironment
檢測并配置環(huán)境。當(dāng)框架沒有緩存配置的時候,加載 .env 文件里面的配置信息Illuminate\Foundation\Bootstrap\LoadConfiguration
加載框架的配置信息。當(dāng)配置文件已經(jīng)緩存,從配置緩存文件中加載配置信息,否則依次加載框架的配置文件里面的配置信息Illuminate\Foundation\Bootstrap\ConfigureLogging
注冊、配置并啟動框架的日志系統(tǒng)Illuminate\Foundation\Bootstrap\HandleExceptions
設(shè)置框架運行時的異常報錯機制Illuminate\Foundation\Bootstrap\RegisterFacades
注冊框架的門面Illuminate\Foundation\Bootstrap\RegisterProviders
注冊框架的 service provider,也就是運行非延遲執(zhí)行的 ServiceProvider 的register方法(執(zhí)行$app->registerConfiguredProviders()語句實現(xiàn))Illuminate\Foundation\Bootstrap\BootProviders
啟動框架的 service provider,也就是運行非延遲執(zhí)行的 ServiceProvider 的boot方法(通過執(zhí)行$app->boot()語句實現(xiàn))
由于框架是依次執(zhí)行各個啟動器,所以我們知道了為什么 laravel 框架中, ServiceProvider 先執(zhí)行 register 方法,后執(zhí)行 boot 方法。
終止應(yīng)用相關(guān)源碼
在將請求分發(fā)到路由并執(zhí)行得到相應(yīng)之后,開始發(fā)送響應(yīng)并在 Kernel 類對象中終止應(yīng)用:
//發(fā)送響應(yīng)內(nèi)容
$response->send();
//終止針對請求 $request 得到響應(yīng) $response 的 $kernel
$kernel->terminate($request, $response);
我們看到終止應(yīng)用是通過 $kernel 對象調(diào)用 terminate 方法得到的,我們來看相應(yīng)源碼
namespace Illuminate\Foundation\Http;
use Illuminate\Contracts\Http\Kernel as KernelContract;
class Kernel implements KernelContract{
/**
* Call the terminate method on any terminable middleware.
* 停止這個針對 http 請求的應(yīng)用
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Response $response
* @return void
*/
public function terminate($request, $response)
{
//獲取請求經(jīng)過的中間件
$middlewares = $this->app->shouldSkipMiddleware() ? [] : array_merge(
$this->gatherRouteMiddleware($request),
$this->middleware
);
//運行各個中間件的 terminate 方法
foreach ($middlewares as $middleware) {
if (! is_string($middleware)) {
continue;
}
list($name, $parameters) = $this->parseMiddleware($middleware);
$instance = $this->app->make($name);
if (method_exists($instance, 'terminate')) {
$instance->terminate($request, $response);
}
}
//終止應(yīng)用,應(yīng)用 Application 對象的 terminate 方法
$this->app->terminate();
}
}
在 $kernel 終止的過程中,調(diào)用了 Application 類對象的 terminate 方法,我們來看一下其相關(guān)方法
namespace Illuminate\Foundation;
class Application extends Container implements ApplicationContract, HttpKernelInterface
{
/**
* Boot the application's service providers.
* 啟動這個 application 的 service provider
* @return void
*/
public function boot()
{
//如果應(yīng)用已經(jīng)啟動,則直接返回
if ($this->booted) {
return;
}
//觸發(fā)應(yīng)用的啟動的回調(diào)函數(shù)
$this->fireAppCallbacks($this->bootingCallbacks);
//啟動框架中的設(shè)置的 service provider
//array_walk 函數(shù)對數(shù)組中的每個元素應(yīng)用用戶自定義函數(shù)
//在函數(shù)中,第一個參數(shù)是值,第二個參數(shù)是鍵。
array_walk($this->serviceProviders, function ($p) {
$this->bootProvider($p);
});
//設(shè)置應(yīng)用已經(jīng)啟動
$this->booted = true;
//觸發(fā)應(yīng)用啟動后的回調(diào)函數(shù)
$this->fireAppCallbacks($this->bootedCallbacks);
}
/**
* Terminate the application.
* 終止這個應(yīng)用
* @return void
*/
public function terminate()
{
//運行應(yīng)用結(jié)束時的回調(diào)
foreach ($this->terminatingCallbacks as $terminating) {
$this->call($terminating);
}
}
}
通過以上的代碼分析,我們可以看到 Application 類對象的 boot 方法是通過 Kernel 對象配置的啟動器 Illuminate\Foundation\Bootstrap\BootProviders 來調(diào)用的,而 terminate 方法則是通過 Kernel 類對象的 terminate 方法來調(diào)用的。
總結(jié)
至此,我們大概了解了 laravel 框架的啟動過程。了解框架的啟動執(zhí)行過程對于我們調(diào)試代碼、理解框架設(shè)計思想具有很大幫助。
不過,框架的設(shè)計是一個整體性的東西,我們不僅要了解框架的啟動過程,還要知道其他模塊的運行原理,比如 Application 對 ServiceProvider 的管理,框架中間件的實現(xiàn)過程,F(xiàn)acade 的設(shè)計思想及實現(xiàn),這些都是框架里面的核心概念和內(nèi)容,理解這些東西,對于我們更好的理解和使用框架具有很大的幫助。大家可以看我相關(guān)的文章,通過這些文章的閱讀,相信對理解框架啟動過程和整體設(shè)計思想具有很大幫助。