[TOC]
# 簡介
> 注:對門面這個概念不理解?可參考PHP 設計模式系列 —— 門面模式(Facade)。
門面為應用`服務容器`中的綁定類提供了一個“靜態”接口。Laravel 內置了很多門面,你可能在不知道的情況下正在使用它們。Laravel 的門面作為服務容器中底層類的“靜態代理”,相比于傳統靜態方法,在維護時能夠提供更加易于測試、更加靈活、簡明優雅的語法。
Laravel 的所有門面都定義在 `Illuminate\Support\Facades` 命名空間下,所以我們可以輕松訪問到門面:
~~~
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
? ? return Cache::get('key');
});
~~~
在整個 Laravel 文檔中,很多例子使用了門面來演示框架的各種功能特性。
# 何時使用門面
門面有諸多優點,其提供了簡單、易記的語法,讓我們無需記住長長的類名即可使用 Laravel 提供的功能特性,此外,由于他們對 PHP 動態方法的獨到用法,使得它們很容易測試。
但是,使用門面也有需要注意的地方,一個最主要的危險就是類范圍蠕變。由于門面如此好用并且不需要注入,在單個類中使用過多門面,會讓類很容易變得越來越大。使用依賴注入則會讓此類問題緩解,因為一個巨大的構造函數會讓我們很容易判斷出類在變大。因此,使用門面的時候要尤其注意類的大小,以便控制其有限職責。
> 注:構建與 Laravel 交互的第三方擴展包時,最好注入 Laravel `契約`而不是使用門面,因為擴展包在 Laravel 之外構建,你將不能訪問 Laravel 的門面測試輔助函數。
## 門面 vs. 依賴注入
依賴注入的最大優點是可以替換注入類的實現,這在測試時很有用,因為你可以注入一個模擬或存根并且在存根上斷言不同的方法。
但是在靜態類方法上進行模擬或存根卻行不通,不過,由于門面使用了動態方法對服務容器中解析出來的對象方法調用進行了代理,我們也可以像測試注入類實例那樣測試門面。例如,給定以下路由:
~~~
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');
}
~~~
## 門面 vs 輔助函數
除了門面之外,Laravel 還內置了許多輔助函數用于執行通用任務,比如生成視圖、觸發事件、分配任務,以及發送 HTTP 響應等。很多輔助函數提供了和相應門面一樣的功能,例如,下面這個門面調用和輔助函數調用是等價的:
~~~
return View::make('profile');
return view('profile');
~~~
門面和輔助函數之間并不存在實質性差別,使用輔助函數的時候,可以像測試相應門面那樣測試它們。例如,給定以下路由:
~~~
Route::get('/cache', function () {
? ? return cache('key');
});
~~~
在調用底層, `cache` 方法會去調用 `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');
}
~~~
# 門面工作原理
在 Laravel 應用中,門面就是一個為容器中對象提供訪問方式的類。該機制原理由 `Facade` 類實現。Laravel 自帶的門面,以及我們創建的自定義門面,都會繼承自 `Illuminate\Support\Facades\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` 門面。該門面作為代理訪問底層 `Illuminate\Contracts\Cache\Factory` 接口的實現。我們對門面的所有調用都會被傳遞給 Laravel 緩存服務的底層實例。
如果我們查看 `Illuminate\Support\Facades\Cache` 類的源碼,將會發現其中并沒有靜態方法 `get`:
~~~
class Cache extends Facade
{
/**
* 獲取組件注冊名稱
*
* @return string
*/
protected static function getFacadeAccessor() {
return 'cache';
}
}
~~~
`Cache` 門面繼承 `Facade` 基類并定義了 `getFacadeAccessor` 方法,該方法的工作就是返回服務容器綁定類的別名,當用戶引用 `Cache` 類的任何靜態方法時,Laravel 從`服務容器`中解析 `cache` 綁定,然后在解析出的對象上調用所有請求方法(本例中是 `get`)。
# 實時門面
使用實時門面,可以將應用中的任意類當做門面來使用。為了說明如何使用這個功能,我們先看一個替代方案。例如我們假設 `Podcast` 模型有一個 `publish` 方法,盡管如此,為了發布博客,我們需要注入 `Publisher` 實例:
~~~
<?php
namespace App;
use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* Publish the podcast.
*
* @param Publisher $publisher
* @return void
*/
public function publish(Publisher $publisher)
{
$this->update(['publishing' => now()]);
$publisher->publish($this);
}
}
~~~
因為可以模擬注入的發布服務,所以注入發布實現到該方法后允許我們輕松在隔離狀態下測試該方法。不過,這要求我們每次調用 `publish` 方法都要傳遞一個發布服務實例,使用實時門面,我們可以在維持這種易于測試的前提下不必顯式傳遞 `Publisher` 實例。要生成一個實時門面,在導入類前面加上 `Facades` 命名空間前綴即可:
~~~
<?php
namespace App;
use Facades\App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* Publish the podcast.
*
* @return void
*/
public function publish()
{
$this->update(['publishing' => now()]);
Publisher::publish($this);
}
}
~~~
使用實時門面后,發布服務實現將會通過使用 `Facades` 前綴后的接口或類名在服務容器中解析。在測試的時候,我們可以使用 Laravel 自帶的門面測試輔助函數來模擬這個方法調用:
~~~
<?php
namespace Tests\Feature;
use App\Podcast;
use Tests\TestCase;
use Facades\App\Contracts\Publisher;
use Illuminate\Foundation\Testing\RefreshDatabase;
class PodcastTest extends TestCase
{
use RefreshDatabase;
/**
* A test example.
*
* @return void
*/
public function test_podcast_can_be_published()
{
$podcast = factory(Podcast::class)->create();
Publisher::shouldReceive('publish')->once()->with($podcast);
$podcast->publish();
}
}
~~~
# 門面類列表
下面列出了每個門面及其對應的底層類,這對深入給定根門面的 API 文檔而言是個很有用的工具。`服務容器綁定鍵`也被包含進來:
| 門面 | 類 | 服務容器綁定 |
|---|---|---|
| App | Illuminate\Foundation\Application | `app` |
| Artisan | Illuminate\Contracts\Console\Kernel | `artisan` |
| Auth | Illuminate\Auth\AuthManager | `auth` |
| Auth(實例) | Illuminate\Contracts\Auth\Guard | `auth.driver` |
| Blade | Illuminate\View\Compilers\BladeCompiler | `blade.compiler` |
| Broadcast | Illuminate\Contracts\Broadcasting\Factory | |
| Broadcast(實例) | Illuminate\Contracts\Broadcasting\Broadcaster | |
| Bus | Illuminate\Contracts\Bus\Dispatcher | |
| Cache | Illuminate\Cache\CacheManager | `cache` |
| Cache(實例) | Illuminate\Cache\Repository | `cache.store` |
| Config | Illuminate\Config\Repository | `config` |
| Cookie | Illuminate\Cookie\CookieJar | `cookie` |
| Crypt | Illuminate\Encryption\Encrypter | `encrypter` |
| DB | Illuminate\Database\DatabaseManager | `db` |
| DB(實例) | Illuminate\Database\Connection | `db.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` |
| Mail | Illuminate\Mail\Mailer | `mailer` |
| Notification | Illuminate\Notifications\ChannelManager | |
| Password | Illuminate\Auth\Passwords\PasswordBrokerManager | `auth.password` |
| Password(實例) | Illuminate\Auth\Passwords\PasswordBroker | `auth.password.broker` |
| Queue | Illuminate\Queue\QueueManager | `queue` |
| Queue(實例) | Illuminate\Contracts\Queue\Queue | `queue.connection` |
| Queue(基類) | Illuminate\Queue\Queue | |
| Redirect | Illuminate\Routing\Redirector | `redirect` |
| Redis | Illuminate\Redis\RedisManager | `redis` |
| Redis(實例) | Illuminate\Redis\Connections\Connection | `redis.connection` |
| Request | Illuminate\Http\Request | `request` |
| Response | Illuminate\Contracts\Routing\ResponseFactory | |
| Response(實例) | Illuminate\Http\Response | |
| Route | Illuminate\Routing\Router | `router` |
| Schema | Illuminate\Database\Schema\Builder | |
| Session | Illuminate\Session\SessionManager | `session` |
| Session(實例) | Illuminate\Session\Store | `session.store` |
| Storage | Illuminate\Filesystem\FilesystemManager | `filesystem` |
| Storage (實例) | Illuminate\Contracts\Filesystem\Filesystem | `filesystem.disk` |
| URL | Illuminate\Routing\UrlGenerator | `url` |
| Validator | Illuminate\Validation\Factory | `validator` |
| Validator(實例) | Illuminate\Validation\Validator | |
| View | Illuminate\View\Factory | `view` |
| View(實例) | Illuminate\View\View | |
- 序言
- 新版特性
- 快速入門
- 升級指南
- 貢獻指南
- API文檔
- 安裝配置
- 目錄結構
- Homestead
- Valet
- 部署
- 核心概念
- 請求生命周期
- 服務容器
- 服務提供者
- 門面(Facades)
- 契約(Contracts)
- 框架基礎
- 路由
- 中間件
- CSRF 保護
- 控制器
- 請求
- 響應
- 視圖
- 生成 URL
- Session
- 驗證
- 錯誤處理
- 日志
- 前端開發
- Blade 模板
- 本地化
- 前端腳手架
- 編譯前端資源(Laravel Mix)
- 安全系列
- 登錄認證
- API 認證
- 授權
- 加密
- 哈希
- 重置密碼
- 進階系列
- Artisan 控制臺
- 集合
- 廣播
- 緩存
- 事件
- 文件存儲
- 輔助函數
- 郵件
- 通知
- 擴展包開發
- 隊列
- 任務調度
- 數據庫操作
- 快速入門
- 查詢構建器
- 分頁
- 遷移
- 數據填充
- Redis
- Eloquent ORM
- 快速入門
- 關聯關系
- 集合
- 訪問器 & 修改器
- API 資源類
- 序列化
- 應用測試
- 快速入門
- HTTP 測試
- 瀏覽器測試
- 數據庫測試
- 模擬
- 官方擴展包
- Cashier(訂閱支付解決方案)
- Envoy(遠程操作解決方案)
- Horizon(隊列系統解決方案)
- Passport(API 認證解決方案)
- Scout(全文搜索解決方案)
- Socialite(第三方登錄解決方案)
- 相關下載
- Laravel 5.6 中文文檔離線版
- Laravel 5.6 一鍵安裝包