在使用Laravel 框架的時候會看到很多 Cache::get() 這樣的用法,稱之爲 Facade,門面。
但是代碼中即沒有看到使用 Cache 相關的命名空間,且在 Composer 自動加載中也沒有相關的自動加載規則。那這是如何實現的呢?讓我們從框架源碼去發現。
Laravel 的入口文件是 public/index.PHP,此文件載入了 autoload.php, app.php 2個文件:
require __DIR__.'/../bootstrap/autoload.php';
$app = require_once __DIR__.'/../bootstrap/app.php';
顧名思義 autoload.php 實現了自動加載,app.php 和容器相關。
初始化容器的過程這裏不詳細解說,不是本文重點。
==============================重點來了================================
初始化容器後,執行了以下代碼:
// 得到 App\Http\Kernel 實例對象
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
// 執行對象handle 方法,此方法繼承自 Illuminate\Foundation\Http\Kernel
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
讓我們去看下 handle() 做了些什麼:
public function handle($request)
{
//......省略......
$response = $this->sendRequestThroughRouter($request);
//......省略......
}
sendRequestThroughRouter 方法:
[php] view plain copy print?
protected function sendRequestThroughRouter($request)
{
//......省略......
// 啓動一些啓動器,諸如異常處理,配置,日誌,Facade,運行環境監測等
$this->bootstrap();
//......省略......
}
bootstrap 方法:
public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
$this->app->bootstrapWith($this->bootstrappers());
}
}
$this->bootstrappers() 中返回 $this->bootstrappers 保存的數據:
[php] view plain copy print?
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',
];
可以看到 :
'Illuminate\Foundation\Bootstrap\RegisterFacades',
此類就是實現 Facade 的一部分,bootstrap 方法中 $this->app->bootstrapWith 會調用類的 bootstrap 方法:
class RegisterFacades
{
public function bootstrap(Application $app)
{
//......省略......
AliasLoader::getInstance($app->make('config')->get('app.aliases'))->register();
}
}
$app->make(‘config’)->get(‘app.aliases’) 返回的是 config/app.php 配置文件中 ‘aliases’ 鍵對應的值,
我們繼續往下看 AliasLoader::getInstance 方法:
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;
}
默認情況下 static::
new static 是一個自 5.3以後的新特性,參見:https://secure.php.net/manual/zh/language.oop5.late-static-bindings.php
回頭再看
AliasLoader::getInstance($app->make('config')->get('app.aliases'))->register();
中調用了 AliasLoader->register 方法:
public function register()
{
if (!$this->registered) {
$this->prependToLoaderStack();
$this->registered = true;
}
}
prependToLoaderStack 方法:
這裏註冊了當前對象中 load 方法爲自動加載函數
protected function prependToLoaderStack()
{
spl_autoload_register([$this, 'load'], true, true);
}
load 方法:
public function load($alias)
{
if (isset($this->aliases[$alias])) {
return class_alias($this->aliases[$alias], $alias);
}
}
這裏的
private function __construct($aliases)
{
$this->aliases = $aliases;
}
這裏 class_alias 是實現 Facade 的核心要點之一,該函數原型:
bool class_alias ( string $original, string $alias[, bool $autoload = TRUE ] )
第三個參數默認爲 true,意味着如果原始類(string $original)沒有加載,則自動加載。
更多該函數的解釋請自行翻閱手冊。
現在可以解答爲什麼 Cache 沒有使用命名空間,composer 沒有設置自動加載的情況可以立即使用了:
自動加載函數會嘗試加載 Cache 這個別名對應的實際類所在的文件(加載這個類文件時會去調用SPL自動加載函數隊列中其他函數,比如 composer 自動加載函數,並遵從規則)
接下來說一下 Cache::get() 中爲什麼可以像靜態方法一樣調用任何類的方法(哪怕不是靜態方法)。
找到並打開 Cache 類文件:vendor/laravel/framework/src/Illuminate/Support/Facades/Cache.php
文件內容是這樣的:
namespace Illuminate\Support\Facades;
/**
* @see \Illuminate\Cache\CacheManager
* @see \Illuminate\Cache\Repository
*/
class Cache extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'cache';
}
}
看一下 父類 Illuminate\Support\Facades,發現父類中實現了魔術方法 __callStatic:
public static function __callStatic($method, $args)
{
$instance = static ::getFacadeRoot();
if (!$instance) {
throw new RuntimeException('A facade root has not been set.');
}
switch (count($args)) {
case 0:
return $instance->$method();
case 1:
return $instance->$method($args[0]);
case 2:
return $instance->$method($args[0], $args[1]);
case 3:
return $instance->$method($args[0], $args[1], $args[2]);
case 4:
return $instance->$method($args[0], $args[1], $args[2], $args[3]);
default:
return call_user_func_array([$instance, $method], $args);
}
}
謎底揭開了,原來是通過魔術方法去實現的。
簡單的寫了一段代碼,用於實現類似 Facade 的調用方式:
namespace Illuminate\Support\Facades {
class Facades {
public function __call($name, $params) {
return call_user_func_array([$this, $name], $params);
}
public static function __callStatic($name, $params) {
return call_user_func_array([new static(), $name], $params);
}
}
class Cache extends Facades {
protected function fn($a, $b) {
echo "function parameters: ${a} and ${b}<br>";
}
protected function static_fn($a, $b) {
echo "static function parameters: ${a} and ${b}<br>";
}
}
}
namespace {
class Autoload {
public $aliases;
public function __construct($aliases = []) {
$this->aliases = $aliases;
}
public function register() {
spl_autoload_register([$this, 'load'], true, true);
return $this;
}
public function load($alias) {
if (isset($this->aliases[$alias])) {
return class_alias($this->aliases[$alias], $alias);
}
}
}
$aliases = [
'Cache' => Illuminate\Support\Facades\Cache::class,
];
$autoloader = (new Autoload($aliases))->register();
Cache::fn(3,6);
Cache::static_fn(4,7);
}