這篇文章主要介紹了Laravel 的Facade 外觀系統的分析,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下
今天我們將學習Laravel 核心架構中的另一個主題“Facade(外觀)”。
本文將從以下幾個面向出發,全面講解Laravel 中Facade 的運作原理,為了便於理解後續中所有Facade 譯作「外觀」:
簡單介紹「外觀」設計模式;
Laravel「外觀」的載入原理;
Laravel「外觀」基本使用。
為子系統中的一組介面提供一個統一的入口。外觀模式定義了一個高層接口,這個接口使得這個子系統更容易使用。
外觀模式是一種使用頻率非常高的結構型設計模式,它透過引入一個外觀角色來簡化客戶端與子系統之間的交互,
為複雜的子系統呼叫提供一個統一的入口,降低子系統與客戶端的耦合度,且客戶端呼叫非常方便。 - 設計模式Java 版
核心 就是在客戶端(使用者) 與子系統(介面或服務) 之間引入一個“外觀”角色。
將使用者與子系統從直接耦合,轉變成由「外觀」類別提供統一的介面給使用者使用,以降低客戶端與子系統之間的耦合度。
關於「外觀模式」可以閱讀設計模式Java 版- 外觀模式
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 類別的內部去了解更多細節上的處理。
<?php namespace 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 載入器是如何將所有的「外觀」服務載入到系統中的。
<?php namespace 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 的神秘面纱,研究一下 Laravel 是如何实现 Facade 设计模式的。
我们拿 IlluminateSupportFacadesApp 外观服务开刀,去解开类似 App::make() 静态方法使用的奥秘。
深入 FacadesApp:
<?php namespace Illuminate\Support\Facades; class App extends Facade { /** * Get the registered name of the component. */ protected static function getFacadeAccessor() { return 'app'; } }
我们看到它的实现内部仅仅定义了一个 getFacadeAccessor 方法,该方法的功能是获取已注册组件的名称 app;除此之外,一无所有。
看来在这里我们得不到什么有用的信息了。继续调查基类 IlluminateSupportFacadesFacade。如果你有去通便浏览全部的源码。
<?php namespace 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 静态方法获取实际服务的实例对象;
然后,调用实例对象的相关方法并返回处理结果。
<?php namespace 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 基类去一探究竟。
另外补充一个知识点就是关于 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); } }
外观服务的一个典型使用场景是在定义路由时使用 Route::get('/', ...)。这样一看似乎「Laravel 别名服务」也就不这么神秘了。
以上就是本文的全部內容,希望對大家的學習有所幫助,更多相關內容請關注PHP中文網!
相關推薦:
Laravel服務提供器(ServiceProvider)的解讀
以上是Laravel 的 Facade 外觀系統的分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!