這篇文章主要介紹了關於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 兩個知識的應用:
➤ 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 是如何擷取實際的服務並調用響應的方法的吧。
<?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 中註冊到「外觀」中) 中解析出相關服務。
到這裡,我們就將「外觀」服務的基本工作原理給分析透徹了。
另外有關「外觀」組件的一些細枝末節,如:
還是需要你自行深入到 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!