Laravel是我最熟悉的框架,卻一直沒認(rèn)真的梳理過它的源碼,甚是遺憾,遂在此梳理一遍laravel的源碼。
Laravel版本:5.7
Composer版本:1.6.5
請求生命周期
大部分框架都有一個(gè)入口文件,Laravel也不例外,入口文件即public/index.php,我們在Nginx中配置server的時(shí)候就是將index屬性指向到public/index.php的絕對路徑。
我們先從入口文件講起:
define('LARAVEL_START', microtime(true));
定義了框架運(yùn)行的開始時(shí)間,方便打印整個(gè)request請求的執(zhí)行時(shí)間,5.5版本之前是沒有的,這里是5.5新加的
require __DIR__.'/../vendor/autoload.php';
加載composer提供的類,進(jìn)入到vender/autoload_real.php文件中可以看到這個(gè)文件具體加載了什么東西
//此處開始按照psr-4規(guī)范設(shè)置加載器 //設(shè)置命名空間、md解析器、測試框架、console高亮顯示器等
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
//設(shè)置tinker、log、Symfony組件等(Laravel的路由、http核心等是基于Symfony框架的)
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
//設(shè)置映射類
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
最后返回一個(gè)autoloader,這時(shí)Composer提供的東西全部加載完畢。
讓我們繼續(xù)返回到public/index.php
$app = require_once __DIR__.'/../bootstrap/app.php';
下一步就是開始初始化框架,可以看到在bootstrap/app.php引導(dǎo)文件中,第一行便是初始化一個(gè)Laravel容器,在初始化的過程中做了哪些工作呢?
這是Application.php的構(gòu)造器:
public function __construct($basePath = null)
{
if ($basePath) {
$this->setBasePath($basePath);
}
$this->registerBaseBindings();
$this->registerBaseServiceProviders();
$this->registerCoreContainerAliases();
}
首先設(shè)置整個(gè)項(xiàng)目的根目錄,并且將config_path,storage_path,database_path,resource_path,bootstrap_path等綁定到容器的instance屬性
下一步便是注冊基本綁定,注冊BaseServiceProviders
/**
* Register all of the base service providers.
* @return void
*/
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}
可以看到容器初始化的時(shí)候已經(jīng)注冊了三個(gè)基本服務(wù):event,log,routing
再次回到Application.php,構(gòu)造器的最后一步是注冊alias,這時(shí)一個(gè)完整的app容器才算注冊完成。容器有了,接下來如何處理http請求和控制臺請求,如何處理異常呢?
答案是繼續(xù)往容器里綁定服務(wù):
//綁定http處理服務(wù)
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class );
//綁定從控制臺發(fā)起的請求處理服務(wù)
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class );
//綁定異常處理服務(wù)
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class );
至此我們可以看到,整個(gè)Laravel框架幾乎都是通過這樣的思想來注冊服務(wù)的,當(dāng)需要用哪個(gè)服務(wù)的時(shí)候,ok,綁定一下。如果要移除一個(gè)服務(wù),從容器實(shí)例中解除綁定即可。大大降低了系統(tǒng)的耦合,提高了靈活性。
再次回到public/index.php,
由于容器已經(jīng)綁定了將 Illuminate\Contracts\Http\Kernel::class,綁定為App\Http\Kernel::class,
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
當(dāng)容器傳入make一個(gè)http核心時(shí),實(shí)際是創(chuàng)建了一個(gè)類型為App\Http\Kernel::class的kernal對象。通過____construct發(fā)放可以看到處理http請求之前的中間件都已準(zhǔn)備完畢。
下一步開始創(chuàng)建request對象:
public static function capture()
{
static::enableHttpMethodParameterOverride();
return static::createFromBase(SymfonyRequest::createFromGlobals());
}
public static function createFromGlobals()
{
$request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $_SERVER);
if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
&& \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH'))
) {
parse_str($request->getContent(), $data);
$request->request = new ParameterBag($data);
}
return $request;
}
可以看到_GET,
_COOKIE,
_SERVER 來創(chuàng)建的。
將$request對象傳入httpKernal之后,執(zhí)行handle方法,handle方法中需要先加載環(huán)境變量、配置文件等
protected $bootstrappers = [
//從env文件加載環(huán)境變量
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
//從app/config目錄加載各種配置文件
\Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
//自定義異常處理函數(shù),php執(zhí)行終止函數(shù)
\Illuminate\Foundation\Bootstrap\HandleExceptions::class,
\Illuminate\Foundation\Bootstrap\RegisterFacades::class,
//注冊provider
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,
//啟動provider
\Illuminate\Foundation\Bootstrap\BootProviders::class,
];
各種配置、中間件加載完畢后將request傳入Pipeline
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
pipeline翻譯過來就是管道,request對象通過pipeline被傳送到middleware,經(jīng)過middleware的層層傳遞,進(jìn)入到then方法,這也是pipeline類的關(guān)鍵。
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
);
return $pipeline($this->passable);
}
雖然只有短短的幾行代碼,理解起來卻特別晦澀。
我們一個(gè)一個(gè)解析
$this->pipes 經(jīng)過之前through方法的處理,其實(shí)就是middleware數(shù)組,將這個(gè)數(shù)組的順序反轉(zhuǎn)。(至于為什么要用array_reverse將中間件執(zhí)行的順序反過來,我也沒搞懂)
destination)
/**
* Get the final piece of the Closure onion.
*
* @param \Closure $destination
* @return \Closure
*/
protected function prepareDestination(Closure $destination)
{
return function ($passable) use ($destination) {
try {
return $destination($passable);
} catch (Exception $e) {
return $this->handleException($passable, $e);
} catch (Throwable $e) {
return $this->handleException($passable, new FatalThrowableError($e));
}
};
}
destination參數(shù)變?yōu)榱朔祷刂档囊粋€(gè)屬性,可以這么理解:上面的prepareDestination方法相當(dāng)于把 $destination參數(shù)格式化了一下,變?yōu)榉祷刂档囊粋€(gè)靜態(tài)類型的屬性。
再次回到then方法,array_reduce函數(shù)在php手冊上是這樣解釋的:
mixed array_reduce ( array
$array
, callable$callback
[, mixed$initial
=NULL
] )
array_reduce() 將回調(diào)函數(shù)callback
迭代地作用到array
數(shù)組中的每一個(gè)單元中,從而將數(shù)組簡化為單一的值。
array_reverse(this->carry()返回的是一個(gè)用來處理中間件的數(shù)組,prepareDestination方法的返回值(假設(shè)為p)是array_reduce的可選參數(shù),p有兩個(gè)作用:
- 如果p不為空則在處理中間件數(shù)組之前執(zhí)行
- 如果中間件數(shù)組為空,則返回p
而我們知道,框架默認(rèn)會有5個(gè)中間件(見上圖pipes屬性),所以array_reverse的執(zhí)行結(jié)果一定是1,至此,Pipeline對象的then方法作用已明確:
先用prepareDestination方法格式化經(jīng)過路由分發(fā)后返回的閉包函數(shù),然后用 this->carry()方法挨個(gè)執(zhí)行中間件。
可以看下this->carry()的具體代碼:
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
if (is_callable($pipe)) { // $pipe類型一
//如果$pipe是一個(gè)可以調(diào)用的閉包函數(shù),則直接調(diào)用 //否則將從容器中解析$pipe,然后用響應(yīng)的方法和參數(shù)調(diào)用
return $pipe($passable, $stack);
} elseif (! is_object($pipe)) { // $pipe類型二
//如果$pipe不是對象類型,是字符串,則將此字符串從依賴注入容器中解析成class對象, //然后調(diào)用class的handle方法來處理參數(shù)(handle方法見下方注釋)
[$name, $parameters] = $this->parsePipeString($pipe);
$pipe = $this->getContainer()->make($name);
$parameters = array_merge([$passable, $stack], $parameters);
} else { // $pipe類型三
//如果$pipe是對象類型,則直接將參數(shù)傳入$pipe調(diào)用它的handle方法即可
$parameters = [$passable, $stack];
}
//可以看到method屬性默認(rèn)值為 'handle',
//$pipe如果存在handle方法則執(zhí)行handle //若不存在,說明$pipe是Closure類型,直接執(zhí)行 $pipe
$response = method_exists($pipe, $this->method)
? $pipe->{$this->method}(...$parameters)
: $pipe(...$parameters);
//經(jīng)過中間件處理后的$response 如果是Responsable 類型,則按resopnse標(biāo)準(zhǔn)輸出,
//若不是,則直接輸出 $response
return $response instanceof Responsable
? $response->toResponse($this->container->make(Request::class))
: $response;
};
};
}
回顧一下Laravel框架中間件的實(shí)現(xiàn)方式:
- 通過運(yùn)行
make:middleware
Artisan 命令來創(chuàng)建中間件。
創(chuàng)建后的中間件在app/Http/Middleware/
目錄下,自帶handle方法,是由上面elseif (! is_object($pipe))
下的代碼塊來處理 - 創(chuàng)建中間件組
中間件組也有兩種形式
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [ 'throttle:60,1', 'auth:api', ],
];
api數(shù)組下的字符串類型對應(yīng) 上述 elseif (! is_object($pipe))
下的代碼塊來處理
web數(shù)組下的class類型跟 1相同
- Terminable中間件
這種中間件是用于HTTP 響應(yīng)構(gòu)建完畢之后處理一些工作,創(chuàng)建時(shí)直接在中間件class中添加 terminate 方法即可,這種中間件是由carry方法中的if (is_callable($pipe))
代碼塊來處理
至此,this->carry()方法中正好將中間件的三種類型,都處理了。
繞了這么久終于回到public/index.php了,最后兩行:
發(fā)送response,發(fā)送完畢之后調(diào)用fastcgi_finish_request
方法關(guān)閉fastcgi
kernal調(diào)用terminate
方法結(jié)束請求聲明周期。