Laravel 的 Facade 外觀系統的分析

來源:互聯網
上載者:User
這篇文章主要介紹了關於Laravel 的 Facade 外觀系統的分析,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下

今天我們將學習 Laravel 核心架構中的另一個主題「Facade(外觀)」。

本文將從以下幾個方面出發,全面講解 Laravel 中 Facade 的運行原理,為了便於理解後續中所有 Facade 譯作「外觀」:

  • 簡單介紹「外觀」設計模式;

  • Laravel「外觀」的載入原理;

  • Laravel「外觀」基本使用。

什麼是「外觀」設計模式

面板模式定義

為子系統中的一組介面提供一個統一的入口。面板模式定義了一個高層介面,這個介面使得這一子系統更加容易使用。

面板模式是一種使用頻率非常高的結構型設計模式,它通過引入一個外觀角色來簡化用戶端與子系統之間的互動,
為複雜的子系統調用提供一個統一的入口,降低子系統與用戶端的耦合度,且用戶端調用非常方便。 - 設計模式 Java 版

核心 就是在 用戶端(使用者)子系統(介面或服務) 之間引入一個「外觀」角色。

將使用者與子系統從直接耦合,轉變成由「外觀」類提供統一的介面給使用者使用,以降低用戶端與子系統之間的耦合度。

結構:

關於「面板模式」可以閱讀 設計模式 Java 版 - 面板模式

Laravel 外觀組件

Laravel 中的「外觀」組件實際上是服務容器中底層類的「靜態代理」,它將 Laravel 核心中定義的「Contracts(在 Laravel 中又
稱為服務、契約或者通常我們所說的介面)」,以靜態可調用的方式封裝到各個「外觀」服務中供我們使用。

外觀載入原理

在講解如何使用外觀組件之前,我們依舊先去深入分析「外觀」組件是如何被 Laravel 載入到項目中的。這一步是
用好「外觀」組件的前提。

外觀組件配置

所有內建的外觀組件的配置資料,同 Laravel 其它服務一樣被定義在 config/app.php 檔案中。讓我們來瀏覽一下 aliases 節點的配置資料吧:

    ...    'aliases' => [        'App' => Illuminate\Support\Facades\App::class,        'Artisan' => Illuminate\Support\Facades\Artisan::class,        ...    ],    ...

外觀配置定義格式遵循 「別名」:「外觀類」 的資料格式。當一個 HTTP 要求被接收時,將在處理要求階段將這些「外觀」組件載入到服務中。

接下來將深入分析外觀服務的載入過程。

載入外觀服務

「外觀」服務的載入工作由定義在 Illuminate\Foundation\Http\Kernel 核心中的 \Illuminate\Foundation\Bootstrap\RegisterFacades::class 啟動程式完成。

引導啟動外觀服務

如果你已經閱讀我的另一篇文章 深入剖析 Laravel 服務提供者實現原理,你應該對引導程式不會太陌生。

引導程式將在處理 HTTP 要求是完成引導啟動 bootstrap()。所以這裡我們需要深入到 RegisterFacades 類的內部去瞭解更多細節上的處理。

<?phpnamespace Illuminate\Foundation\Bootstrap;use Illuminate\Foundation\AliasLoader;use Illuminate\Support\Facades\Facade;use Illuminate\Foundation\PackageManifest;use Illuminate\Contracts\Foundation\Application;/** * @link https://github.com/laravel/framework/blob/56a58e0fa3d845bb992d7c64ac9bb6d0c24b745a/src/Illuminate/Foundation/Bootstrap/RegisterFacades.php */class RegisterFacades{    /**     * Bootstrap the given application. 引導啟動服務     */    public function bootstrap(Application $app)    {        // 清除已解析的「外觀」服務執行個體        Facade::clearResolvedInstances();        // 將 Laravel 服務容器注入到「外觀」服務        Facade::setFacadeApplication($app);        // 載入所有外觀服務        AliasLoader::getInstance(array_merge(            $app->make('config')->get('app.aliases', []),            $app->make(PackageManifest::class)->aliases()        ))->register();    }}

載入外觀服務有 AliasLoader 組件完成:

  • 首先,會從設定檔 config/app.php 中讀取所有的「外觀」服務配置 aliases

  • 再從資訊清單檔中讀取別名服務 $app->make(PackageManifest::class)->aliases()

  • 將兩個配置數組合并後注入到 AliasLoader 完成 註冊(register)

註冊外觀服務

最後我們來瞧瞧 AliasLoader 載入器是如何將所有的「外觀」服務載入到系統中的。

<?phpnamespace Illuminate\Foundation;/** * @link https://github.com/laravel/framework/blob/56a58e0fa3d845bb992d7c64ac9bb6d0c24b745a/src/Illuminate/Foundation/AliasLoader.php */class AliasLoader{    /**     * Get or create the singleton alias loader instance. 擷取或建立「別名載入器」單例執行個體。     */    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;    }    /**     * Set the registered aliases. 設定需註冊別名資料。     */    public function setAliases(array $aliases)    {        $this->aliases = $aliases;    }    /**     * Register the loader on the auto-loader stack. 將載入器註冊到自動載入中。     */    public function register()    {        if (! $this->registered) {            $this->prependToLoaderStack();            $this->registered = true;        }    }    /**     * Prepend the load method to the auto-loader stack. 設定自動載入方法。     */    protected function prependToLoaderStack()    {        // 將 AliasLoader 的 load 方法作為 __autoload 的實現        spl_autoload_register([$this, 'load'], true, true);    }    /**     * Load a class alias if it is registered.從註冊過的服務中載入這個「外觀」服務。     */    public function load($alias)    {        if (static::$facadeNamespace && strpos($alias, static::$facadeNamespace) === 0) {            $this->loadFacade($alias);            return true;        }        if (isset($this->aliases[$alias])) {            return class_alias($this->aliases[$alias], $alias);        }    }}

注意 這裡是知識點,在 AliasLoader->register() 完成「外服服務註冊」涉及 PHP 兩個知識的應用:

  • PHP 內建魔術方法 __autoload 的使用;

  • PHP 如何給類建立別名。

➤ 1. 外觀服務的動態引入

我們知道 __autoload 魔術方法的作用是嘗試載入未經定義的類,這樣當我們使用一個未經引入的類時,則會自動的給我們引入這個類。

更優的解決方案是通過 spl_autoload_register 函數,將自訂的類載入程式作為 __autoload 的實現,以替代預設 __autoload() 模式函數或方法的行為。

所有 prependToLoaderStack() 方法:

    /**     * Prepend the load method to the auto-loader stack. 設定自動載入方法。     */    protected function prependToLoaderStack()    {        // 將 AliasLoader 的 load 方法作為 __autoload 的實現        spl_autoload_register([$this, 'load'], true, true);    }

就是去完成這樣的作用,將 AliasLoader->load() 方法作為自動載入程式的實現,在使用「外觀」服務時動態引入這個類。

➤ 2. 支援外觀服務別名

我們已經瞭解到當「外觀」服務被使用時,由 AliasLoader->load() 去自動載入這個類。

與此同時,load 方法通過 class_alias($original, $alias) 函數完成別名註冊。

這樣,當我們使用 App 類時實際上就是在使用 Illuminate\Support\Facades\App 類。

很完美麼,我們的「狗蛋」終於與「世界上最好的語言」畫上了等號。你就是我,我就是你。

到這裡其實已經完成了「外觀」服務工作原理分析工作的 70%

探秘 Facade

最後我們將揭開 Facade 的神秘面紗,研究一下 Laravel 是如何? Facade 設計模式的。

我們拿 IlluminateSupportFacadesApp 外觀服務開刀,去解開類似 App::make() 靜態方法使用的奧秘。

深入 FacadesApp

<?phpnamespace Illuminate\Support\Facades;class App extends Facade{    /**     * Get the registered name of the component.     */    protected static function getFacadeAccessor()    {        return 'app';    }}

我們看到它的實現內部僅僅定義了一個 getFacadeAccessor 方法,該方法的功能是擷取登入組件的名稱 app;除此之外,一無所有。

看來在這裡我們得不到什麼有用的資訊了。繼續調查基類 IlluminateSupportFacadesFacade。如果你有去通便瀏覽全部的源碼。

<?phpnamespace Illuminate\Support\Facades;use Mockery;use RuntimeException;use Mockery\MockInterface;/** * @link https://github.com/laravel/framework/blob/5.6/src/Illuminate/Support/Facades/Facade.php */abstract class Facade{    /**     * Handle dynamic, static calls to the object.     */    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 基類並沒有定義類似 make 的方法,那麼這裡能夠靜態調用 App::make() 看來是需要從 __callStatic 著手才行。

不過在這裡我們需要再次釐清一個事實:「外觀」模式的功能是什麼?

將使用者與子系統從直接耦合,轉變成由「外觀」類提供統一的介面給使用者使用,以降低用戶端與子系統之間的耦合度。

這句話的意思就是我「外觀」啥也不提供,就是一層對服務(或者說組件或介面)的封裝,然後以統一的方式提供給你們外部調用。

好了現在我們來看看 Facade::__callStatic 是如何擷取實際的服務並調用響應的方法的吧。

  • 首先,通過 getFacadeRoot 靜態方法擷取實際服務的執行個體對象;

  • 然後,調用執行個體對象的相關方法並返回處理結果。

<?phpnamespace Illuminate\Support\Facades;use Mockery;use RuntimeException;use Mockery\MockInterface;abstract class Facade{    /**     * Get the root object behind the facade. 從 facade 中解析出真實服務的對象     */    public static function getFacadeRoot()    {        return static::resolveFacadeInstance(static::getFacadeAccessor());    }    /**     * Resolve the facade root instance from the container.me 從 Laravel 服務容器中解析出真實服務的對象     */    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];    }}

getFacadeRoot 解析對象的功能中我們可以看到:它會調用實現「外觀」的 getFacadeAccessor 方法擷取到組件(服務或者說介面)的名稱;然後從 Laravel 服務容器 static::$app[$name](app 是在 RegisterFacades 中註冊到「外觀」中) 中解析出相關服務。

到這裡,我們就將「外觀」服務的基本工作原理給分析透徹了。

另外有關「外觀」組件的一些細枝末節,如:

  • 在文檔「Facades Vs. 輔助函數」一節提到的測實驗證是如何?的 Cache::shouldReceive('get')

  • 什麼是「即時 Facades」。

還是需要你自行深入到 Facade 基類去一探究竟。

掃盲 ArrayAccess 介面

另外補充一個知識點就是關於 static::$app[$name] 這一句代碼。你不經要問,這有啥好補充的呢,不就是一個簡單擷取資料麼。

擷取資料不假,簡單也不假。

不過你仔細看一下,你會發現 static::$app 靜態成員變數難道不是一個 \Illuminate\Contracts\Foundation\Application 實現執行個體麼,怎麼可以從對象中以數組的方式擷取值呢?

這是因為我們的服務容器 Illuminate\Container\Container 實現了 ArrayAccess 介面。

該介面的功能是提供像訪問數組一樣訪問對象的能力的介面,這樣就可以像數組一樣訪問對象訪問成員。

/** *@link https://github.com/laravel/framework/blob/5.6/src/Illuminate/Container/Container.php */class Container implements ArrayAccess, ContainerContract{    /**     * Get the value at a given offset. 擷取一個位移位置的值,實際上從容器中解析出服務。     */    public function offsetGet($key)    {        return $this->make($key);    }}

Laravel「外觀」基本使用

外觀服務的一個典型使用情境是在定義路由時使用 Route::get('/', ...)。這樣一看似乎「Laravel 別名服務」也就不這麼神秘了。

以上就是本文的全部內容,希望對大家的學習有所協助,更多相關內容請關注topic.alibabacloud.com!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.