在 laravel 5.4 新增了一個 realtime facade 的功能,相比 5.4 之前的 facade,我們使用時不再需要顯式的新增一個 Facade 類,而是通過在使用的時候加上 Facades
名稱空間就行,比如有一個 App\Services\RealTimeService
,我們可以像下面這樣使用 realtime facade:
use Facades\App\Services\RealTimeService;
...
RealTimeService::someAction();
...
Laravel 5.4 是如何實現的呢?一起來看看。
Facade 的工作原理
Facade 的工作原理很簡單,通過 __callStatic
魔術方法,在呼叫 Facade 的靜態方法時動態的通過依賴注入容器獲取訪問器的例項,然後呼叫例項的相應方法,實現 “靜態代理” 的效果。
public static function __callStatic($method, $args)
{
$instance = static::getFacadeRoot();
return $instance->$method(...$args);
}
- Facade 的
getFacadeAccessor
的方法返回的是訪問器在容器中的註冊名,容器通過這個註冊名獲取訪問器 - 通過 Facade 呼叫的類是同一個例項,類似於單例
使用 Facade 的“正確”姿勢
Laravel 中內建了很多 Facade,比如通常用的Route
,Cache
,Request
,Session
等等,很多小夥伴在使用的時候都會寫完整的名稱空間,包括 Laravel generator 生成的檔案有些也使用了完整的類名。
use Illuminate\Support\Facades\Route;
Route::get(...);
實際上這些內建的 facade 是可以直接通過別名來使用的,比如上面的例子就可以這樣使用:
use Route;
Route::get(...);
文件中對 Facade 的介紹也是這樣使用,而不會寫完整的名稱空間。我在剛學習 laravel 的時候用的是 sublime,因為沒有自動匯入功能,為了少寫些程式碼,facade 都是直接用別名,所以對這個印象很深(現在很多新手都是用 phpstorm,有自動匯入功能,這種用法反而一直不知道)。那個時候一直很奇怪,明明沒有這個類,到底是如何實現的?
Facade 別名如何實現的
先看看為什麼可以直接使用根名稱空間的 Facade 別名.
在 config\app.php
中有個 aliases
陣列,這個陣列定義了別名以及實際對應的 Facade。
...
'Route' => Illuminate\Support\Facades\Route::class,
...
在 Http\Kernel
處理請求之前,會呼叫 Illuminate\Foundation\Application
的 bootstrapWith
方法,這個方法會做一些容器的引導工作,比如載入環境變數、載入配置、配置異常 handle、註冊 Facades、註冊 ServiceProvider、啟動 ServiceProvider 等等,具體的類如下:
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
\Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
\Illuminate\Foundation\Bootstrap\HandleExceptions::class,
\Illuminate\Foundation\Bootstrap\RegisterFacades::class,
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,
\Illuminate\Foundation\Bootstrap\BootProviders::class,
很顯然,Facade 相關注冊工作在 \Illuminate\Foundation\Bootstrap\RegisterFacades::class
中定義,我們具體看看這個類:
public function bootstrap(Application $app)
{
Facade::clearResolvedInstances();
Facade::setFacadeApplication($app);
AliasLoader::getInstance($app->make('config')->get('app.aliases', []))->register();
}
獲取 Illuminate\Foundation\AliasLoader
的例項並呼叫 register
方法註冊。
AliasLoader
維護了一個 Facade 別名以及對應的 Facade 的陣列,也就是 config\app.php
中 aliases
陣列,類似於:
'App' => Illuminate\Support\Facades\App::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
...
AliasLoader
的 register
方法註冊了 AliasLoader
的 load
方法到 SPL __autoload 函式佇列中。
spl_autoload_register([$this, 'load'], true, true);
AliasLoader
的 load
方法通過 class_alias
函式,為實際的 Facade 建立一個 Facade 別名。
if (isset($this->aliases[$alias])) {
return class_alias($this->aliases[$alias], $alias);
}
所以,看到這裡就很明白了: Facade 別名是通過 class_alias
建立,使用 spl_autoload_register 函式讓別名實現自動載入。
realtime Facade 如何實現的
5.4 版本中的 realtime Facade 是如何實現的?與上面的 Facade 別名是一樣的。
AliasLoader
的 load
方法註冊到 SPL __autoload函式佇列後,所有的類在查詢時都會通過這個方法。當查詢的類名是以 Facades\\
開頭的時候,這個時候就會在 storage\framework\cache
中自動建立一個 facade-hash(類名).php
的檔案,並 require 這個檔案。
比如開頭的例子,use Facades\App\Services\RealTimeService
時會在 storage\framework\cache
中建立一個 facade-ababc11941d3eff6bccd4a8e2495a335336eec0b.php
的檔案,我們看看這個檔案的內容:
<?php
namespace Facades\App\Services;
use Illuminate\Support\Facades\Facade;
/**
* @see \App\Services\RunTimeFacade
*/
class RunTimeFacade extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'App\Services\RunTimeFacade';
}
}
靠,這不就是相當於建立了一個 App\Services\RunTimeFacade
的 Facade 嗎?
總結
-
5.4 以前的根名稱空間 Facade 和 5.4 中的 Realtime Facade 都是通過把
Illuminate\Foundation\AliasLoader@load
加入到 SPL __autoload 函式佇列中來實現的 -
Realtime Facade 其實就是自動建立了一個 Facade
本作品採用《CC 協議》,轉載必須註明作者和本文連結