Laravel 5.4 real-time facade 探究

oustn發表於2017-02-12

在 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,比如通常用的RouteCacheRequestSession 等等,很多小夥伴在使用的時候都會寫完整的名稱空間,包括 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\ApplicationbootstrapWith 方法,這個方法會做一些容器的引導工作,比如載入環境變數、載入配置、配置異常 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.phpaliases 陣列,類似於:

'App' => Illuminate\Support\Facades\App::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
...

AliasLoaderregister 方法註冊了 AliasLoaderload 方法到 SPL __autoload 函式佇列中。

spl_autoload_register([$this, 'load'], true, true);

AliasLoaderload 方法通過 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 別名是一樣的。

AliasLoaderload 方法註冊到 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 協議》,轉載必須註明作者和本文連結

相關文章