介紹
Facades 為應(yīng)用的 IoC 服務(wù)容器 的類提供了一個(gè)靜態(tài)的接口。Laravel 里面自帶了一些 Facades,如Cache等。Laravel 的門面作為服務(wù)容器中底層類的“靜態(tài)代理”,相比于傳統(tǒng)靜態(tài)方法,在維護(hù)時(shí)能夠提供更加易于測(cè)試、更加靈活、簡(jiǎn)明優(yōu)雅的語法。
解釋
在 Laravel 應(yīng)用這個(gè)上下文里面,一個(gè) Facade 就是一個(gè)類,使用這個(gè)類可以訪問到來自容器里的一個(gè)對(duì)象,這個(gè)功能就是在 Facade 類里面定義的。Laravel 的 Facades 還有任何你自己定義的 Facades,都會(huì)去繼承 Facade 這個(gè)類。
你的 Facade 類只需要實(shí)施一個(gè)的方法:getFacadeAccessor。要在容器里 resolve 什么出來,都是在這個(gè)方法里去做的。Facade 這個(gè)基類里面使用了__callStatic() 魔術(shù)方法,可以延遲到 resolved 對(duì)象上的,來自 Facade 的調(diào)用。
所以,當(dāng)你使用 Facade 調(diào)用的時(shí)候,比如像這樣:Cache:get,laravel 會(huì)從 Ioc 服務(wù)容器 里面 resolves 緩存管理類,然后再去調(diào)用這個(gè)類上面的 get 方法。Laravel 的 Facades 可以去定位服務(wù),它是一種使用 Laravel 的 Ioc 服務(wù)容器 的更方便的語法。
優(yōu)點(diǎn)
Facade 有諸多優(yōu)點(diǎn),其提供了簡(jiǎn)單、易記的語法,讓我們無需記住長(zhǎng)長(zhǎng)的類名即可使用 Laravel 提供的功能特性,此外,由于他們對(duì) PHP 動(dòng)態(tài)方法的獨(dú)到用法,使得它們很容易測(cè)試。
實(shí)際使用
下面的例子,去調(diào)用了一下 Laravel 的緩存系統(tǒng)。先看一下下面這行代碼,你可能會(huì)覺得,這是直接去調(diào)用 Cache 這個(gè)類上面的一個(gè)叫 get 的靜態(tài)的方法。
$value = Cache::get('key');
不過,如果你查看 Illuminate\Support\Facades\Cache 這個(gè)類,你會(huì)發(fā)現(xiàn)這里根本就沒有 get 這個(gè)靜態(tài)方法:
class Cache extends Facade {
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor() { return 'cache'; }
}
Cache 這個(gè)類繼承了 Facade 這個(gè)基類,它里面定義了一個(gè)叫 getFacadeAccessor() 的方法。注意,這個(gè)方法的干的事就是去返回一個(gè) Ioc 綁定的名字,這里就是 cache。
當(dāng)用戶在引用任何在 Cache 這個(gè) Facade 上的靜態(tài)方法的時(shí)候,Laravel 就會(huì)從 Ioc 服務(wù)容器 里面去 resolves cache 這個(gè)綁定,并且會(huì)去執(zhí)行在對(duì)象上的這個(gè)所請(qǐng)求的方法(這里就是 get 這個(gè)方法)。
所以,我們?cè)谡{(diào)用 Cache::get 的時(shí)候,它的真正的意思是這樣的:
$value = $app->make('cache')->get('key');
導(dǎo)入 Facades
注意,在使用
facade的時(shí)候,如果控制器里面用到了命名空間,你需要把 Facade 類導(dǎo)入到這個(gè)命名空間里。所有的 Facades 都是在全局命名空間下:
<?php namespace App\Http\Controllers;
use Cache;
class PhotosController extends Controller {
/**
* Get all of the application photos.
*
* @return Response
*/
public function index()
{
$photos = Cache::get('photos');
//
}
}
創(chuàng)建 Facades
創(chuàng)建 Facade 只需要三個(gè)東西:
- 一個(gè) IoC 綁定。
- 一個(gè) Facade 類。
- 一個(gè) Facade 別名的配置。
在下面我們定義了一個(gè)類:PaymentGateway\Payment 。
namespace PaymentGateway;
class Payment {
public function process()
{
//
}
}
我們需要能在 Ioc 服務(wù)容器 里面去 resolve 這個(gè)類。所以,先要去添加一個(gè) Service Provider 綁定:
App::bind('payment', function()
{
return new \PaymentGateway\Payment;
});
去注冊(cè)這個(gè)綁定最好的方法就是去創(chuàng)建一個(gè)新的 Service Provider ,把它命名為 PaymentServiceProvider ,然后把它綁定到 register 方法上。再去配置 laravel 在 config/app.php 這個(gè)配置文件里加載你的 Service Provider。
下一步就是去創(chuàng)建自己的 Facade 類:
use Illuminate\Support\Facades\Facade;
class Payment extends Facade {
protected static function getFacadeAccessor() {
return 'payment';
}
}
最后,如果你愿意,可以去給 Facade 添加一個(gè)別名,放到 config/app.php 配置文件里的 aliases 數(shù)組里。
可以去調(diào)用 Payment 類的一個(gè)實(shí)例上的 process 這個(gè)方法了。像這樣:
Payment::process();
何時(shí)使用 Facade
注意
在使用 Facade 也有需要注意的地方,一個(gè)最主要的危險(xiǎn)就是類范圍蠕變。由于Facade 如此好用并且不需要注入,在單個(gè)類中使用過多Facade,會(huì)讓類很容易變得越來越大。使用依賴注入則會(huì)讓此類問題緩解,因?yàn)橐粋€(gè)巨大的構(gòu)造函數(shù)會(huì)讓我們很容易判斷出類在變大。因此,使用Facade的時(shí)候要尤其注意類的大小,以便控制其有限職責(zé)。
注:構(gòu)建與 Laravel 交互的第三方擴(kuò)展包時(shí),最好注入 Laravel 契約而不是使用門面,因?yàn)閿U(kuò)展包在 Laravel 之外構(gòu)建,你將不能訪問 Laravel 的門面測(cè)試輔助函數(shù)。
Facade vs. 依賴注入
依賴注入的最大優(yōu)點(diǎn)是可以替換注入類的實(shí)現(xiàn),這在測(cè)試時(shí)很有用,因?yàn)槟憧梢宰⑷胍粋€(gè)模擬或存根并且在存根上斷言不同的方法。
但是在靜態(tài)類方法上進(jìn)行模擬或存根卻行不通,不過,由于Facade 使用了動(dòng)態(tài)方法對(duì)服務(wù)容器中解析出來的對(duì)象方法調(diào)用進(jìn)行了代理,我們也可以像測(cè)試注入類實(shí)例那樣測(cè)試門面。例如,給定以下路由:
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
return Cache::get('key');
});
我們可以這樣編寫測(cè)試來驗(yàn)證 Cache::get 方法以我們期望的方式被調(diào)用:
use Illuminate\Support\Facades\Cache;
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$this->visit('/cache')
->see('value');
}
Facade vs. 輔助函數(shù)
除了Facade之外,Laravel 還內(nèi)置了許多輔助函數(shù)用于執(zhí)行通用任務(wù),比如生成視圖、觸發(fā)事件、分配任務(wù),以及發(fā)送 HTTP 響應(yīng)等。很多輔助函數(shù)提供了和相應(yīng) Facade 一樣的功能,例如,下面這個(gè)Facade調(diào)用和輔助函數(shù)調(diào)用是等價(jià)的:
return View::make('profile');
return view('profile');
Facade和輔助函數(shù)之間并不存在實(shí)質(zhì)性差別,使用輔助函數(shù)的時(shí)候,可以像測(cè)試相應(yīng)門面那樣測(cè)試它們。例如,給定以下路由:
Route::get('/cache', function () {
return cache('key');
});
在調(diào)用底層, cache 方法會(huì)去調(diào)用 Cache Facade上的 get方法,因此,盡管我們使用這個(gè)輔助函數(shù),我們還是可以編寫如下測(cè)試來驗(yàn)證這個(gè)方法以我們期望的方式和參數(shù)被調(diào)用:
use Illuminate\Support\Facades\Cache;
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$this->visit('/cache')
->see('value');
}
Facade 工作原理
在 Laravel 應(yīng)用中,Facade就是一個(gè)為容器中對(duì)象提供訪問方式的類。該機(jī)制原理由 Facade 類實(shí)現(xiàn)。Laravel 自帶的 Facade,以及我們創(chuàng)建的自定義門面,都會(huì)繼承自 Illuminate\Support\Facades\Facade 基類??梢詤⒖? Facade 實(shí)現(xiàn)原理
Facade 類只需要實(shí)現(xiàn)一個(gè)方法:getFacadeAccessor。正是 getFacadeAccessor方法定義了從容器中解析什么,然后 Facade 基類使用魔術(shù)方法 __callStatic() 從你的門面中調(diào)用解析對(duì)象。
下面的例子中,我們將會(huì)調(diào)用 Laravel 的緩存系統(tǒng),瀏覽代碼后,也許你會(huì)覺得我們調(diào)用了 Cache 的靜態(tài)方法 get:
<?php
namespace App\Http\Controllers;
use Cache;
use App\Http\Controllers\Controller;
class UserController extends Controller{
/**
* 為指定用戶顯示屬性
*
* @param int $id
* @return Response
*/
public function showProfile($id)
{
$user = Cache::get('user:'.$id);
return view('profile', ['user' => $user]);
}
}
注意我們?cè)陧敳课恢靡肓?Cache Facade。該門面作為代理訪問底層 Illuminate\Contracts\Cache\Factory 接口的實(shí)現(xiàn)。我們對(duì)門面的所有調(diào)用都會(huì)被傳遞給 Laravel 緩存服務(wù)的底層實(shí)例。
如果我們查看 Illuminate\Support\Facades\Cache 類的源碼,將會(huì)發(fā)現(xiàn)其中并沒有靜態(tài)方法 get:
class Cache extends Facade
{
/**
* 獲取組件注冊(cè)名稱
*
* @return string
*/
protected static function getFacadeAccessor() {
return 'cache';
}
}
Cache Facade 繼承 Facade 基類并定了 getFacadeAccessor方法,該方法的工作就是返回服務(wù)容器綁定類的別名,當(dāng)用戶引用 Cache
類的任何靜態(tài)方法時(shí),Laravel 從服務(wù)容器中解析 cache
綁定,然后在解析出的對(duì)象上調(diào)用所有請(qǐng)求方法(本例中是 get
)
門面類列表
下面列出了每個(gè)門面及其對(duì)應(yīng)的底層類,這對(duì)深入給定根門面的 API 文檔而言是個(gè)很有用的工具。服務(wù)容器綁定鍵也被包含進(jìn)來:
門面 Facade
|
類 class
|
服務(wù)容器綁定 |
|---|---|---|
| App | Illuminate\Foundation\Application | app |
| Artisan | Illuminate\Contracts\Console\Kernel | artisan |
| Auth | Illuminate\Auth\AuthManager | auth |
| Blade | Illuminate\View\Compilers\BladeCompiler | blade.compiler |
| Bus | Illuminate\Contracts\Bus\Dispatcher | |
| Cache | Illuminate\Cache\Repository | cache |
| Config | Illuminate\Config\Repository | config |
| Cookie | Illuminate\Cookie\CookieJar | cookie |
| Crypt | Illuminate\Encryption\Encrypter | encrypter |
| DB | Illuminate\Database\DatabaseManager | db |
| DB(Instance) | Illuminate\Database\Connection | |
| Event | Illuminate\Events\Dispatcher | events |
| File | Illuminate\Filesystem\Filesystem | files |
| Gate | Illuminate\Contracts\Auth\Access\Gate | |
| Hash | Illuminate\Contracts\Hashing\Hasher | hash |
| Lang | Illuminate\Translation\Translator | translator |
| Log | Illuminate\Log\Writer | log |
| Illuminate\Mail\Mailer | mailer |
|
| Notification | Illuminate\Notifications\ChannelManager | |
| Password | Illuminate\Auth\Passwords\PasswordBrokerManager | auth.password |
| Queue | Illuminate\Queue\QueueManager | queue |
| Queue(Instance) | Illuminate\Contracts\Queue\Queue | queue |
| Queue(Base Class) | Illuminate\Queue\Queue | |
| Redirect | Illuminate\Routing\Redirector | redirect |
| Redis | Illuminate\Redis\Database | redis |
| Request | Illuminate\Http\Request | request |
| Response | Illuminate\Contracts\Routing\ResponseFactory | |
| Route | Illuminate\Routing\Router | router |
| Schema | Illuminate\Database\Schema\Blueprint | |
| Session | Illuminate\Session\SessionManager | session |
| Session(Instance) | Illuminate\Session\Store | |
| Storage | Illuminate\Contracts\Filesystem\Factory | filesystem |
| URL | Illuminate\Routing\UrlGenerator | url |
| Validator | Illuminate\Validation\Factory | validator |
| Validator(Instance) | Illuminate\Validation\Validator | |
| View | Illuminate\View\Factory | view |
| View(Instance) | Illuminate\View\View |