Facade 布局是在面向?qū)ο缶幊讨薪?jīng)常使用的一種軟件設(shè)計(jì)布局方式。Facade 實(shí)際上是一種包括復(fù)雜函數(shù)庫(kù)的類,提供了更加簡(jiǎn)潔易讀的接口。Facade 布局還能為一組結(jié)構(gòu)復(fù)雜、設(shè)計(jì)簡(jiǎn)陋的 API 提供統(tǒng)一、設(shè)計(jì)周到的 API。
Laravel 框架與該布局的特點(diǎn)相似,也稱為 Facades。在本教程中,我們會(huì)學(xué)習(xí)如何在其他框架應(yīng)用 Laravel 的 “Facades”。在繼續(xù)學(xué)習(xí)之前,讓我們簡(jiǎn)單了解一下 Ioc 容器。
首先,我們了解 Laravel 的 facades 內(nèi)部工作結(jié)構(gòu)。之后再討論如何將之改造并用于其他環(huán)境。
Laravel 中的 Facades
Laravel facade 是一種為容器內(nèi)部服務(wù)提供類似靜態(tài)接口的類。據(jù)其文檔描述,F(xiàn)acades 是可觸及容器服務(wù)底層實(shí)現(xiàn)方式的代理。
不過,在 PHP 社區(qū),有關(guān)其名稱的爭(zhēng)論一直不斷。一些人堅(jiān)持修改此名稱以避免開發(fā)者的困惑,因?yàn)槠洳⑽赐耆珜?shí)現(xiàn) Facade 布局。如果你也受此名稱困擾,大可以為其取個(gè)別名。但是,請(qǐng)注意,下文將會(huì)用到的 Laravel 框架基類(base class)將會(huì)稱為 Facade。
How Facades Are implemented in Laravel
Facades 在 Laravel 中如何實(shí)現(xiàn)
你可能也知道,容器內(nèi)的每個(gè)服務(wù)都有個(gè)唯一名稱。在 laravel 應(yīng)用中,可使用 App::make()
方法或 app()
輔助函數(shù)從容器中直接獲取服務(wù)。
<?php
App::make('some_service')->methodName();
前面已經(jīng)提過,Laravel 使用 facade 類的好處是讓開發(fā)者使用服務(wù)時(shí)更加便捷。使用 facade 類之后,下面的代碼就能達(dá)到相同的效果:
// ...
someService::methodName();
// ...
在 Laravel 中,所有服務(wù)都包含一個(gè) facade 類。這些 facade 類繼承自 Illuminate/Support
包中的 Facade 基類。它們只需實(shí)現(xiàn) getFacadeAccessor
方法即可,后者會(huì)返回容器內(nèi)的服務(wù)名。
在上面的示例中,someService
代表 facade 類。methodName
其實(shí)是容器內(nèi)原服務(wù)的一個(gè)方法。如果跳出 Laravel 的語(yǔ)境查看上面的示例,則表示一個(gè)名為 someService
的類引出名為 methodName()
的靜態(tài)方法。但 Laravel 并不是這樣實(shí)現(xiàn)接口的。在下一節(jié),我們將介紹 Laravel 的 Facade 基類在幕后的運(yùn)作方式。
Base Facade
Facade 類包含一個(gè)名為 $app
的私有屬性,其值為服務(wù)容器的引用。如果要在 Laravel 之外使用 facades,必須使容器明確使用 setFacadeApplication()
方法。
在 facade 基類內(nèi)部,__callStatic
魔術(shù)方法用于處理實(shí)際并不存在的靜態(tài)方法的調(diào)用。如果調(diào)用 Laravel facade 類的靜態(tài)方法, __callStatic
方法便會(huì)激活,因?yàn)?facade 類并未實(shí)現(xiàn)該方法。因此,__callStatic
會(huì)從容器獲取各自的服務(wù),進(jìn)而調(diào)用之。
以下是 facade 基類中 __callStatic
方法的實(shí)現(xiàn)方式:
<?php
// ...
/**
* Handle dynamic, static calls to the object.
*
* @param string $method
* @param array $args
* @return mixed
*/
public static function __callStatic($method, $args)
{
$instance = static::getFacadeRoot();
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);
}
}
在上面的方法中,getFacadeRoot()
會(huì)從容器獲取服務(wù)。
Facade 類解析
每個(gè) facade 類均繼承自基類。我們只需實(shí)現(xiàn) getFacadeAccessor()
方法,該方法用于返回容器中的服務(wù)名。
<?php namespace App\Facades;
use Illuminate\Support\Facades\Facade as BaseFacade;
class SomeServiceFacade extends BaseFacade {
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor() { return 'some.service'; }
}
別名
由于 Laravel facades 是 PHP 類,在使用之前我們得導(dǎo)入它們。PHP 支持命名空間與自動(dòng)導(dǎo)入,因此只要調(diào)用全限定名,即可自動(dòng)載入這些類。PHP 還支持使用 use
指令給類取別名:
use App\Facades\SomeServiceFacade
SomeServiceFacade:SomeMethod();
然而,在需要某個(gè)特定的 facade 類時(shí),我們必須在每個(gè)腳本文件都寫一遍上面的代碼。Laravel 在處理 facade 別名時(shí)有其獨(dú)特的方法——?jiǎng)e名載入器(alias loader)。
Laravel 如何給 Facades 加別名
所有的別名都保存在 app.php
配置文件的 aliases
數(shù)組中,該文件保存在 /config
目錄下。
查看該數(shù)組,會(huì)發(fā)現(xiàn)每個(gè)別名都與一個(gè)全限定類名對(duì)應(yīng)。這意味著我們可以給 facade 類選定任意的名字。
// ..
'aliases' => [
// ...
'FancyName' => 'App\Facades\SomeServiceFacade',
],
現(xiàn)在,讓我們看看 Laravel 如何使用該數(shù)組給 facade 類取別名。在引導(dǎo)階段,Laravel 會(huì)使用來自 Illuminate\Foundation
包的 AliasLoader
服務(wù)。AliasLoader
以該別名數(shù)組為參數(shù),遍歷其所有元素,使用 PHP 的 spl_autoload_register 創(chuàng)建一個(gè) __autoload
函數(shù)隊(duì)列。各個(gè) __autoload
函數(shù)會(huì)用 PHP 的 class_alias 函數(shù)為各個(gè) facade 類創(chuàng)建別名。
因此,我們無需像使用 use
指令時(shí)那樣在使用類前導(dǎo)入之并為其創(chuàng)建別名。當(dāng)我們?cè)噲D使用一個(gè)不存在的類時(shí),PHP 會(huì)檢查 __autoload
隊(duì)列以得到合適的 autoloader。這時(shí),AliasLoader
已經(jīng)記下所有的 __autoload
函數(shù)。各個(gè) autoloader 會(huì)選定一個(gè)類名并根據(jù)別名數(shù)組推導(dǎo)出對(duì)應(yīng)的初始類名。最后,它會(huì)為其創(chuàng)建別名。請(qǐng)參考下面的方法調(diào)用:
<?php
// FancyName is resolved to App\Facades\SomeServiceFacade according to the aliases array
FancyName::someMethod()
在幕后,FancyName
會(huì)對(duì)應(yīng)至 App\Facades\SomeServiceFacade
。
在其他框架使用 Facades
現(xiàn)在,我們已經(jīng)了解 Laravel 如何處理 facades 與別名,我們可以將 Laravel 的 facade 方法運(yùn)用到其他環(huán)境中。接下來,我們會(huì)在 Silex 框架使用 facades。然而,只要遵循同樣的理念,你也可以將之用在別的框架。
Silex 擁有繼承自 Pimple
的容器。使用 $app
對(duì)象即可調(diào)用容器內(nèi)的服務(wù):
<?php
$app['some.service']->someMethod()
有了 facade 類,我們可以為 Silex 服務(wù)提供一個(gè)類似靜態(tài)的接口。此外,我們也可以使用 AliasLoader
服務(wù)為這些 facades 創(chuàng)建有意義的別名。因此,我們可以重組上面的代碼:
<?php
SomeService::someMethod();
必備條件
為了使用 facade 基類,我們要使用 composer
指令安裝 Illuminate\Support
包:
composer require illuminate\support
此包還包含其他服務(wù)。但目前我們只需要 facade 基類。
創(chuàng)建 Facades
只需繼承 Facade 基類并實(shí)現(xiàn) getFacadeAccessor
方法,即可為服務(wù)創(chuàng)建 facade。
在本文中,所有 facades 都會(huì)保存在 src/Facades
路徑下。例如:名為 some.service
的服務(wù),其 facade 類如下:
<?php
namespace App\Facades
use Illuminate\Support\Facades\Facade;
class SomeServiceFacade extends Facade {
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor() { return 'some.service'; }
}
請(qǐng)注意,此類位于 app\facades
命名空間下。
現(xiàn)在只剩下設(shè)定 facade 類的應(yīng)用容器。如前所述,在靜態(tài)語(yǔ)境下調(diào)用 facade 類的方法,會(huì)觸發(fā) __callStatic
方法。該方法會(huì)用 getFacadeAccessor()
返回的數(shù)據(jù)識(shí)別容器內(nèi)的服務(wù),并試圖獲取之。在 Laravel 之外使用 facade 基類時(shí),容器對(duì)象并不是自動(dòng)設(shè)定的,需要手動(dòng)設(shè)定。
為此,使用 facade 基類的 setFacadeApplication
方法,可以設(shè)定 facade 類的應(yīng)用容器。
在 app.php
文件,添加以下代碼:
<?php
Illumiante\Support\Facade::setFacadeApplication($app);
這會(huì)給繼承自 facade 基類的所有 facades 設(shè)定容器。
現(xiàn)在,無需直接從容器獲取服務(wù),我們可以使用剛剛創(chuàng)建的 facade 類來獲取,該類還允許我們調(diào)用靜態(tài)語(yǔ)境下的所有方法。
實(shí)現(xiàn)別名
為了給 facade 類創(chuàng)建別名,我們將使用之前介紹過的 AliasLoader
。AliasLoader
類由 illuminate\foundation
包提供,可以下載整個(gè)包,也可以拷貝部分代碼保持為文件。
如果你想拷貝源文件,建議將其保存在 src/Facades
目錄下。你可以根據(jù)項(xiàng)目的結(jié)構(gòu)為 AliasLoader
類創(chuàng)建命名空間。
在本例中,我們將拷貝代碼并將其保存在 app/facades
命名空間下。
創(chuàng)建別名數(shù)組
在 config
目錄下創(chuàng)建 aliases.php
文件,并填入 alias-facade 綁定:
<?php
return [
'FancyName' => 'App\Facades\SomeService',
];
FancyName
是我們給 App\Facades\SomeService
建立的別名。
注冊(cè)別名
AliasLoader
是一種單例服務(wù)。要?jiǎng)?chuàng)建或得到別名載入器(alias loader)的實(shí)例,需調(diào)用 getInstance
方法并以別名數(shù)組為參數(shù)。最后,為了注冊(cè)這些別名,需調(diào)用其 register
方法。
再次打開 app.php
文件,加入以下代碼:
<?php
// ...
$aliases = require __DIR__ . '/../../config/aliases.php';
App\Facades\AliasLoader::getInstance($aliases)->register();
現(xiàn)在,大功告成了!我們可以這樣使用該服務(wù):
<?php
FancyName::methodName();
進(jìn)行包裝
一個(gè) Facade 類只需實(shí)現(xiàn) getFacadeAccessor
方法即可,后者會(huì)返回容器內(nèi)的服務(wù)名。若要在 Laravel 環(huán)境外使用 facade,必須使用 setFacadeApplication()
方法明確設(shè)定服務(wù)容器。
要引用 facade 類,我們可以使用全限定類名或使用 PHP 的 use
指令導(dǎo)入之。或者,遵循 Laravel 給 facades 創(chuàng)建別名的方法,使用 alias loader。
原文鏈接:http://www.sitepoint.com/how-laravel-facades-work-and-how-to-use-them-elsewhere/ (作者:Reza Lavaryan)本文系 OneAPM 工程師編譯整理。
OneAPM for PHP 能夠深入到所有 PHP 應(yīng)用內(nèi)部完成應(yīng)用性能管理 能夠深入到所有 PHP 應(yīng)用內(nèi)部完成應(yīng)用性能管理和監(jiān)控,包括代碼級(jí)別性能問題的可見性、性能瓶頸的快速識(shí)別與追溯、真實(shí)用戶體驗(yàn)監(jiān)控、服務(wù)器監(jiān)控和端到端的應(yīng)用性能管理。想閱讀更多技術(shù)文章,請(qǐng)?jiān)L問 OneAPM 官方技術(shù)博客。
本文轉(zhuǎn)自 OneAPM 官方博客