介紹
Facades
為應用的 IoC
服務容器 的類提供了一個靜態的接口。Laravel 里面自帶了一些 Facades
,如Cache
等。Laravel 的門面作為服務容器中底層類的“靜態代理”,相比于傳統靜態方法,在維護時能夠提供更加易于測試、更加靈活、簡明優雅的語法。
解釋
在 Laravel 應用這個上下文里面,一個 Facade
就是一個類,使用這個類可以訪問到來自容器里的一個對象,這個功能就是在 Facade
類里面定義的。Laravel 的 Facades 還有任何你自己定義的 Facades,都會去繼承 Facade
這個類。
你的 Facade 類只需要實施一個的方法:getFacadeAccessor
。要在容器里 resolve
什么出來,都是在這個方法里去做的。Facade
這個基類里面使用了__callStatic()
魔術方法,可以延遲到 resolved
對象上的,來自 Facade 的調用。
所以,當你使用 Facade 調用的時候,比如像這樣:Cache:get
,laravel 會從 Ioc 服務容器 里面 resolves
緩存管理類,然后再去調用這個類上面的 get 方法。Laravel 的 Facades 可以去定位服務,它是一種使用 Laravel 的 Ioc 服務容器 的更方便的語法。
優點
Facade 有諸多優點,其提供了簡單、易記的語法,讓我們無需記住長長的類名即可使用 Laravel 提供的功能特性,此外,由于他們對 PHP 動態方法的獨到用法,使得它們很容易測試。
實際使用
下面的例子,去調用了一下 Laravel 的緩存系統。先看一下下面這行代碼,你可能會覺得,這是直接去調用 Cache
這個類上面的一個叫 get
的靜態的方法。
$value = Cache::get('key');
不過,如果你查看 Illuminate\Support\Facades\Cache
這個類,你會發現這里根本就沒有 get 這個靜態方法:
class Cache extends Facade {
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor() { return 'cache'; }
}
Cache 這個類繼承了 Facade 這個基類,它里面定義了一個叫 getFacadeAccessor() 的方法。注意,這個方法的干的事就是去返回一個 Ioc 綁定的名字,這里就是 cache。
當用戶在引用任何在 Cache 這個 Facade
上的靜態方法的時候,Laravel 就會從 Ioc 服務容器 里面去 resolves cache
這個綁定,并且會去執行在對象上的這個所請求的方法(這里就是 get 這個方法)。
所以,我們在調用 Cache::get 的時候,它的真正的意思是這樣的:
$value = $app->make('cache')->get('key');
導入 Facades
注意,在使用
facade
的時候,如果控制器里面用到了命名空間,你需要把 Facade 類導入到這個命名空間里。所有的 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');
//
}
}
創建 Facades
創建 Facade 只需要三個東西:
- 一個 IoC 綁定。
- 一個 Facade 類。
- 一個 Facade 別名的配置。
在下面我們定義了一個類:PaymentGateway\Payment
。
namespace PaymentGateway;
class Payment {
public function process()
{
//
}
}
我們需要能在 Ioc 服務容器 里面去 resolve 這個類。所以,先要去添加一個 Service Provider
綁定:
App::bind('payment', function()
{
return new \PaymentGateway\Payment;
});
去注冊這個綁定最好的方法就是去創建一個新的 Service Provider
,把它命名為 PaymentServiceProvider
,然后把它綁定到 register
方法上。再去配置 laravel 在 config/app.php
這個配置文件里加載你的 Service Provider
。
下一步就是去創建自己的 Facade
類:
use Illuminate\Support\Facades\Facade;
class Payment extends Facade {
protected static function getFacadeAccessor() {
return 'payment';
}
}
最后,如果你愿意,可以去給 Facade
添加一個別名,放到 config/app.php
配置文件里的 aliases
數組里。
可以去調用 Payment 類的一個實例上的 process
這個方法了。像這樣:
Payment::process();
何時使用 Facade
注意
在使用 Facade
也有需要注意的地方,一個最主要的危險就是類范圍蠕變。由于Facade
如此好用并且不需要注入,在單個類中使用過多Facade
,會讓類很容易變得越來越大。使用依賴注入則會讓此類問題緩解,因為一個巨大的構造函數會讓我們很容易判斷出類在變大。因此,使用Facade
的時候要尤其注意類的大小,以便控制其有限職責。
注:構建與 Laravel 交互的第三方擴展包時,最好注入 Laravel 契約而不是使用門面,因為擴展包在 Laravel 之外構建,你將不能訪問 Laravel 的門面測試輔助函數。
Facade vs. 依賴注入
依賴注入的最大優點是可以替換注入類的實現,這在測試時很有用,因為你可以注入一個模擬或存根并且在存根上斷言不同的方法。
但是在靜態類方法上進行模擬或存根卻行不通,不過,由于Facade
使用了動態方法對服務容器中解析出來的對象方法調用進行了代理,我們也可以像測試注入類實例那樣測試門面。例如,給定以下路由:
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
return Cache::get('key');
});
我們可以這樣編寫測試來驗證 Cache::get
方法以我們期望的方式被調用:
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. 輔助函數
除了Facade
之外,Laravel 還內置了許多輔助函數用于執行通用任務,比如生成視圖、觸發事件、分配任務,以及發送 HTTP 響應等。很多輔助函數提供了和相應 Facade
一樣的功能,例如,下面這個Facade
調用和輔助函數調用是等價的:
return View::make('profile');
return view('profile');
Facade
和輔助函數之間并不存在實質性差別,使用輔助函數的時候,可以像測試相應門面那樣測試它們。例如,給定以下路由:
Route::get('/cache', function () {
return cache('key');
});
在調用底層, cache
方法會去調用 Cache Facade
上的 get
方法,因此,盡管我們使用這個輔助函數,我們還是可以編寫如下測試來驗證這個方法以我們期望的方式和參數被調用:
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 應用中,Facade
就是一個為容器中對象提供訪問方式的類。該機制原理由 Facade
類實現。Laravel 自帶的 Facade
,以及我們創建的自定義門面,都會繼承自 Illuminate\Support\Facades\Facade
基類??梢詤⒖? Facade 實現原理
Facade
類只需要實現一個方法:getFacadeAccessor
。正是 getFacadeAccessor
方法定義了從容器中解析什么,然后 Facade
基類使用魔術方法 __callStatic()
從你的門面中調用解析對象。
下面的例子中,我們將會調用 Laravel
的緩存系統,瀏覽代碼后,也許你會覺得我們調用了 Cache
的靜態方法 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]);
}
}
注意我們在頂部位置引入了 Cache Facade
。該門面作為代理訪問底層 Illuminate\Contracts\Cache\Factory
接口的實現。我們對門面的所有調用都會被傳遞給 Laravel 緩存服務的底層實例。
如果我們查看 Illuminate\Support\Facades\Cache
類的源碼,將會發現其中并沒有靜態方法 get
:
class Cache extends Facade
{
/**
* 獲取組件注冊名稱
*
* @return string
*/
protected static function getFacadeAccessor() {
return 'cache';
}
}
Cache Facade
繼承 Facade
基類并定了 getFacadeAccessor
方法,該方法的工作就是返回服務容器綁定類的別名,當用戶引用 Cache
類的任何靜態方法時,Laravel 從服務容器中解析 cache
綁定,然后在解析出的對象上調用所有請求方法(本例中是 get
)
門面類列表
下面列出了每個門面及其對應的底層類,這對深入給定根門面的 API 文檔而言是個很有用的工具。服務容器綁定鍵也被包含進來:
門面 Facade
|
類 class
|
服務容器綁定 |
---|---|---|
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 |