Facades是咱們在Laravel應用開發中使用頻率很高的一個組件,叫組件不太合適,其實它們是一組靜態類接口或者說代理,讓開發者能簡單的訪問綁定到服務容器裏的各類服務。Laravel文檔中對Facades的解釋以下:php
Facades 爲應用程序的 服務容器 中可用的類提供了一個「靜態」接口。Laravel 自己附帶許多的 facades,甚至你可能在不知情的情況下已經在使用他們!Laravel 「facades」做爲在服務容器內基類的「靜態代理」,擁有簡潔、易表達的語法優勢,同時維持着比傳統靜態方法更高的可測試性和靈活性。git
咱們常常用的Route就是一個Facade, 它是\Illuminate\Support\Facades\Route
類的別名,這個Facade類代理的是註冊到服務容器裏的router
服務,因此經過Route類咱們就可以方便地使用router服務中提供的各類服務,而其中涉及到的服務解析徹底是隱式地由Laravel完成的,這在必定程度上讓應用程序代碼變的簡潔了很多。下面咱們會大概看一下Facades從被註冊進Laravel框架到被應用程序使用這中間的流程。Facades是和ServiceProvider緊密配合的因此若是你瞭解了中間的這些流程對開發自定義Laravel組件會頗有幫助。github
說到Facades註冊又要回到再介紹其它核心組建時提到過不少次的Bootstrap階段了,在讓請求經過中間件和路由以前有一個啓動應用程序的過程:bootstrap
//Class: \Illuminate\Foundation\Http\Kernel
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());
}
//引導啓動Laravel應用程序
public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
/**依次執行$bootstrappers中每個bootstrapper的bootstrap()函數
$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',
];*/
$this->app->bootstrapWith($this->bootstrappers());
}
}
複製代碼
在啓動應用的過程當中Illuminate\Foundation\Bootstrap\RegisterFacades
這個階段會註冊應用程序裏用到的Facades。數組
class RegisterFacades
{
/**
* Bootstrap the given application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function bootstrap(Application $app)
{
Facade::clearResolvedInstances();
Facade::setFacadeApplication($app);
AliasLoader::getInstance(array_merge(
$app->make('config')->get('app.aliases', []),
$app->make(PackageManifest::class)->aliases()
))->register();
}
}
複製代碼
在這裏會經過AliasLoader
類的實例將爲全部Facades註冊別名,Facades和別名的對應關係存放在config/app.php
文件的$aliases
數組中bash
'aliases' => [
'App' => Illuminate\Support\Facades\App::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
......
'Route' => Illuminate\Support\Facades\Route::class,
......
]
複製代碼
看一下AliasLoader裏是如何註冊這些別名的app
// class: Illuminate\Foundation\AliasLoader
public static function getInstance(array $aliases = [])
{
if (is_null(static::$instance)) {
return static::$instance = new static($aliases);
}
$aliases = array_merge(static::$instance->getAliases(), $aliases);
static::$instance->setAliases($aliases);
return static::$instance;
}
public function register()
{
if (! $this->registered) {
$this->prependToLoaderStack();
$this->registered = true;
}
}
protected function prependToLoaderStack()
{
// 把AliasLoader::load()放入自動加載函數隊列中,並置於隊列頭部
spl_autoload_register([$this, 'load'], true, true);
}
複製代碼
經過上面的代碼段能夠看到AliasLoader將load方法註冊到了SPL __autoload函數隊列的頭部。看一下load方法的源碼:框架
public function load($alias)
{
if (isset($this->aliases[$alias])) {
return class_alias($this->aliases[$alias], $alias);
}
}
複製代碼
在load方法裏把$aliases
配置裏的Facade類建立了對應的別名,好比當咱們使用別名類Route
時PHP會經過AliasLoader的load方法爲Illuminate\Support\Facades\Route
類建立一個別名類Route
,因此咱們在程序裏使用別Route
其實使用的就是Illuminate\Support\Facades\Route
類。ide
把Facades註冊到框架後咱們在應用程序裏就能使用其中的Facade了,好比註冊路由時咱們常常用Route::get('/uri', 'Controller@action);
,那麼Route
是怎麼代理到路由服務的呢,這就涉及到在Facade裏服務的隱式解析了, 咱們看一下Route類的源碼:函數
class Route extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'router';
}
}
複製代碼
只有簡單的一個方法,並無get
, post
, delete
等那些路由方法, 父類裏也沒有,不過咱們知道調用類不存在的靜態方法時會觸發PHP的__callStatic
靜態方法
public static function __callStatic($method, $args)
{
$instance = static::getFacadeRoot();
if (! $instance) {
throw new RuntimeException('A facade root has not been set.');
}
return $instance->$method(...$args);
}
//獲取Facade根對象
public static function getFacadeRoot()
{
return static::resolveFacadeInstance(static::getFacadeAccessor());
}
/**
* 從服務容器裏解析出Facade對應的服務
*/
protected static function resolveFacadeInstance($name)
{
if (is_object($name)) {
return $name;
}
if (isset(static::$resolvedInstance[$name])) {
return static::$resolvedInstance[$name];
}
return static::$resolvedInstance[$name] = static::$app[$name];
}
複製代碼
經過在子類Route Facade裏設置的accessor(字符串router), 從服務容器中解析出對應的服務,router服務是在應用程序初始化時的registerBaseServiceProviders階段(具體能夠看Application的構造方法)被\Illuminate\Routing\RoutingServiceProvider
註冊到服務容器裏的:
class RoutingServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->registerRouter();
......
}
/**
* Register the router instance.
*
* @return void
*/
protected function registerRouter()
{
$this->app->singleton('router', function ($app) {
return new Router($app['events'], $app);
});
}
......
}
複製代碼
router服務對應的類就是\Illuminate\Routing\Router
, 因此Route Facade實際上代理的就是這個類,Route::get實際上調用的是\Illuminate\Routing\Router
對象的get方法
/**
* Register a new GET route with the router.
*
* @param string $uri
* @param \Closure|array|string|null $action
* @return \Illuminate\Routing\Route
*/
public function get($uri, $action = null)
{
return $this->addRoute(['GET', 'HEAD'], $uri, $action);
}
複製代碼
補充兩點:
解析服務時用的static::$app
是在最開始的RegisterFacades
裏設置的,它引用的是服務容器。
static::$app['router'];以數組訪問的形式可以從服務容器解析出router服務是由於服務容器實現了SPL的ArrayAccess接口, 對這個沒有概念的能夠看下官方文檔ArrayAccess
經過梳理Facade的註冊和使用流程咱們能夠看到Facade和服務提供器(ServiceProvider)是緊密配合的,因此若是之後本身寫Laravel自定義服務時除了經過組件的ServiceProvider將服務註冊進服務容器,還能夠在組件中提供一個Facade讓應用程序可以方便的訪問你寫的自定義服務。
本文已經收錄在系列文章Laravel源碼學習裏,歡迎訪問閱讀。