Facade

簡介

在 Laravel 的說明文件中,我們可以看到範例程式碼都使用「Facade」來操作 Laravel 的功能。Facade 提供了一個「靜態 (Static)」介面來存取 Service Container 中提供的類別。Laravel 隨附了許多 Facade,幾乎可以存取所有的 Laravel 功能。

Laravel Facade 是一個用來存取 Service Container 中類別的一個「靜態代理 (Static Proxy)」,讓我們能使用簡潔、語意化的語法,卻由不像傳統靜態方法一樣要犧牲可測試性與靈活性。若你還不了解 Facade 如何運作,完全沒關係 —— 只要繼續使用並持續學習 Laravel 就好。

Laravel 中所有的 Facade 都定義在 Illuminate\Support\Facades Namespace 下。因此,我們可以像這樣輕鬆地存取 Facade:

1use Illuminate\Support\Facades\Cache;
2use Illuminate\Support\Facades\Route;
3 
4Route::get('/cache', function () {
5 return Cache::get('key');
6});
1use Illuminate\Support\Facades\Cache;
2use Illuminate\Support\Facades\Route;
3 
4Route::get('/cache', function () {
5 return Cache::get('key');
6});

在 Laravel 說明文件中,有許多的範例都使用 Facade 來示範 Laravel 的許多功能:

輔助函式

為了能與 Facade 互補,Laravel 還提供了一系列的全域「輔助函式」,能讓你更輕鬆的使用常見的 Laravel 功能。其中,你可能常使用到的輔助函式包含:viewresponseurlconfig⋯⋯等。Laravel 中提供的每個輔助函式都有說明文件來說明其功能。關於輔助函式完整的列表列在專門的輔助函式說明文件

舉例來說,除了使用 Illuminate\Support\Facades\Response Facade 來產生 JSON 回應以外,我們也可以使用 response 韓式。由於輔助函式在全域都可使用,因此我們不需要特別 Import 任何函式就可以使用:

1use Illuminate\Support\Facades\Response;
2 
3Route::get('/users', function () {
4 return Response::json([
5 // ...
6 ]);
7});
8 
9Route::get('/users', function () {
10 return response()->json([
11 // ...
12 ]);
13});
1use Illuminate\Support\Facades\Response;
2 
3Route::get('/users', function () {
4 return Response::json([
5 // ...
6 ]);
7});
8 
9Route::get('/users', function () {
10 return response()->json([
11 // ...
12 ]);
13});

什麼時候要使用 Facade?

Facade 提供了許多的好處。Facade 提供了簡介、好記憶的語法,能讓你不需記著長長的類別名稱、不需手動插入或設定類別,就能使用 Laravel 的功能。此外,由於 Facade 使用了獨特的 PHP 動態方法,因此要測試 Facade 也很簡單。

不過,在使用 Facade 的時候有幾點需要注意。第一個要注意的點是,Facade 是類別的「作用範圍陷阱 (Scope Creep)」。由於 Facade 很容易使用,而且不需要做相依性插入,所以我們很容易讓類別不斷增長、並在單一類別中使用太多的 Facade。在使用相依性插入時,這種問題很容易一眼看出,因為我們看到類別的 Constructor (建構函式) 就知道類別太肥大了。因此,在使用 Facade 時,請特別注意類別的大小,讓類別的功能範圍保持專一。若類別變的太大,請考慮將其拆分為多個小類別。

Facades Vs. 相依性插入

使用相依性插入的主要好處就是我們能替換掉要插入類別的實作。在測試時這點特別適用,因為這樣我們就能插入 Mock (模擬) 或 Stub (虛設常式),並在 Stub 上檢查各種方法是否真的有被呼叫。

一般來說,對真正的靜態類別方法來說,我們是不可能去 Mock 或 Stub 的。不過,因為 Facade 使用動態方法來代理這些方法呼叫到 Service Container 解析的物件上,因此我們就可以測試這些 Facade,就像我們在測試插入的類別實體一樣。舉例來說,假設有下列 Route:

1use Illuminate\Support\Facades\Cache;
2 
3Route::get('/cache', function () {
4 return Cache::get('key');
5});
1use Illuminate\Support\Facades\Cache;
2 
3Route::get('/cache', function () {
4 return Cache::get('key');
5});

使用 Laravel 的 Facade 測試方法,我們就能撰寫下列測試,並驗證 Cache::get 方法是否有使用預期的引數呼叫:

1use Illuminate\Support\Facades\Cache;
2 
3/**
4 * A basic functional test example.
5 */
6public function test_basic_example(): void
7{
8 Cache::shouldReceive('get')
9 ->with('key')
10 ->andReturn('value');
11 
12 $response = $this->get('/cache');
13 
14 $response->assertSee('value');
15}
1use Illuminate\Support\Facades\Cache;
2 
3/**
4 * A basic functional test example.
5 */
6public function test_basic_example(): void
7{
8 Cache::shouldReceive('get')
9 ->with('key')
10 ->andReturn('value');
11 
12 $response = $this->get('/cache');
13 
14 $response->assertSee('value');
15}

Facades Vs. 輔助函式

除了 Facade 外,Laravel 也提供了多個「輔助」函式,可用來處理像是產生 View、觸發事件、分派任務、送出 HTTP Response⋯⋯等常見的工作,其中許多的輔助函式都與對應的 Facade 提供相同的功能。舉例來說,下列的 Facade 呼叫與輔助函式的呼叫是相同的:

1return Illuminate\Support\Facades\View::make('profile');
2 
3return view('profile');
1return Illuminate\Support\Facades\View::make('profile');
2 
3return view('profile');

在實務上,使用 Facade 方法與輔助函式並沒有不同,使用輔助函式時,我們還是可以像對 Facade 一樣測試這些功能。舉例來說,假設有下列 Route:

1Route::get('/cache', function () {
2 return cache('key');
3});
1Route::get('/cache', function () {
2 return cache('key');
3});

在 Laravel 中,cache 輔助函式會去呼叫 Cache Facade 底層類別的 get 方法。因此,雖然我們在使用的是輔助函式,但我們可以撰寫下列這樣的測試來驗證該方法是否有用我們給定的引數呼叫:

1use Illuminate\Support\Facades\Cache;
2 
3/**
4 * A basic functional test example.
5 */
6public function test_basic_example(): void
7{
8 Cache::shouldReceive('get')
9 ->with('key')
10 ->andReturn('value');
11 
12 $response = $this->get('/cache');
13 
14 $response->assertSee('value');
15}
1use Illuminate\Support\Facades\Cache;
2 
3/**
4 * A basic functional test example.
5 */
6public function test_basic_example(): void
7{
8 Cache::shouldReceive('get')
9 ->with('key')
10 ->andReturn('value');
11 
12 $response = $this->get('/cache');
13 
14 $response->assertSee('value');
15}

Facade 是如何運作的?

在 Laravel 程式中,Facade 能讓我們從 Container 內存取物件。為什麼我們能這麼做?答案就藏在 Facade 類別中。Laravel 的 Facade 與你自己建立的自訂 Facade 都繼承一個基礎的 Illuminate\Support\Facades\Facade 類別。

Facade 的基礎類別使用 __callStatic() 魔法方法來將所有對 Facade 的呼叫轉移到從 Container 中解析出來的物件上。在上面的例子中,我們呼叫了 Laravel 快取系統。看一眼這個程式碼,有人可能會假設我們是在呼叫 Cache 類別的靜態 get 方法:

1<?php
2 
3namespace App\Http\Controllers;
4 
5use App\Http\Controllers\Controller;
6use Illuminate\Support\Facades\Cache;
7use Illuminate\View\View;
8 
9class UserController extends Controller
10{
11 /**
12 * Show the profile for the given user.
13 */
14 public function showProfile(string $id): View
15 {
16 $user = Cache::get('user:'.$id);
17 
18 return view('profile', ['user' => $user]);
19 }
20}
1<?php
2 
3namespace App\Http\Controllers;
4 
5use App\Http\Controllers\Controller;
6use Illuminate\Support\Facades\Cache;
7use Illuminate\View\View;
8 
9class UserController extends Controller
10{
11 /**
12 * Show the profile for the given user.
13 */
14 public function showProfile(string $id): View
15 {
16 $user = Cache::get('user:'.$id);
17 
18 return view('profile', ['user' => $user]);
19 }
20}

可以注意到,在檔案最上端,我們「Import」了 Cache Facade。這個 Facade 會作為代理來讓我們存取底層 Illuminate\Contracts\Cache\Factory 介面的實作。使用 Facade 呼叫的所有方法都會被傳到 Laravel 快取系統的底層實體上。

若我們打開 Illuminate\Support\Facades\Cache 類別看,會發現裡面沒有靜態的 get 方法:

1class Cache extends Facade
2{
3 /**
4 * Get the registered name of the component.
5 */
6 protected static function getFacadeAccessor(): string
7 {
8 return 'cache';
9 }
10}
1class Cache extends Facade
2{
3 /**
4 * Get the registered name of the component.
5 */
6 protected static function getFacadeAccessor(): string
7 {
8 return 'cache';
9 }
10}

沒有 get 方法,Cache Facade 只有繼承了基礎的 Facade 類別並定義了 getFacadeAccessor() 方法。這個方法的功能就是用來回傳 Service Container 繫結的名稱。當使用者在 Cache Facade 上參照任何靜態方法時,Laravel 會去從 Service Container 中解析出 cache 繫結,然後在這個物件上執行要求的方法 (在這個例子中就是 get)。

即時 Facade

使用即時 Facade 時,我們可以把程式內的任何類別都當作 Facade 來使用。為了說明這個功能,我們先來看一些不使用即時 Facade 的例子。舉例來說,我們先假設 Podcast Model 有個 publish 方法。不過,為了要發布 (Publish) 這個 Podcast,我們還需要插入 Publisher 實體:

1<?php
2 
3namespace App\Models;
4 
5use App\Contracts\Publisher;
6use Illuminate\Database\Eloquent\Model;
7 
8class Podcast extends Model
9{
10 /**
11 * Publish the podcast.
12 */
13 public function publish(Publisher $publisher): void
14 {
15 $this->update(['publishing' => now()]);
16 
17 $publisher->publish($this);
18 }
19}
1<?php
2 
3namespace App\Models;
4 
5use App\Contracts\Publisher;
6use Illuminate\Database\Eloquent\Model;
7 
8class Podcast extends Model
9{
10 /**
11 * Publish the podcast.
12 */
13 public function publish(Publisher $publisher): void
14 {
15 $this->update(['publishing' => now()]);
16 
17 $publisher->publish($this);
18 }
19}

將 Publisher 實作插入到這個方法後,只要 Mock 這個插入的 Publisher,我們就能輕鬆地在分離的狀態下測試這個方法。不過,這樣一來每次我們呼叫 publish 方法也都需要傳入一個 Publisher 實體。使用即時 Facade,我們一樣可以能保有可測試性,又不需要顯式傳入 Publisher 實體。若要產生即時 Facade,請在 Import 類別的 Namespace 前方加上 Facades

1<?php
2 
3namespace App\Models;
4 
5use Facades\App\Contracts\Publisher;
6use Illuminate\Database\Eloquent\Model;
7 
8class Podcast extends Model
9{
10 /**
11 * Publish the podcast.
12 */
13 public function publish(): void
14 {
15 $this->update(['publishing' => now()]);
16 
17 Publisher::publish($this);
18 }
19}
1<?php
2 
3namespace App\Models;
4 
5use Facades\App\Contracts\Publisher;
6use Illuminate\Database\Eloquent\Model;
7 
8class Podcast extends Model
9{
10 /**
11 * Publish the podcast.
12 */
13 public function publish(): void
14 {
15 $this->update(['publishing' => now()]);
16 
17 Publisher::publish($this);
18 }
19}

在使用即時 Facade 時,Laravel 會使用 Facades 前置詞後方的介面或類別名稱來從 Service Container 上解析出 Publisher 實作。測試時,我們可以使用 Laravel 的內建 Facade 測試工具來 Mock 這個方法呼叫:

1<?php
2 
3namespace Tests\Feature;
4 
5use App\Models\Podcast;
6use Facades\App\Contracts\Publisher;
7use Illuminate\Foundation\Testing\RefreshDatabase;
8use Tests\TestCase;
9 
10class PodcastTest extends TestCase
11{
12 use RefreshDatabase;
13 
14 /**
15 * A test example.
16 */
17 public function test_podcast_can_be_published(): void
18 {
19 $podcast = Podcast::factory()->create();
20 
21 Publisher::shouldReceive('publish')->once()->with($podcast);
22 
23 $podcast->publish();
24 }
25}
1<?php
2 
3namespace Tests\Feature;
4 
5use App\Models\Podcast;
6use Facades\App\Contracts\Publisher;
7use Illuminate\Foundation\Testing\RefreshDatabase;
8use Tests\TestCase;
9 
10class PodcastTest extends TestCase
11{
12 use RefreshDatabase;
13 
14 /**
15 * A test example.
16 */
17 public function test_podcast_can_be_published(): void
18 {
19 $podcast = Podcast::factory()->create();
20 
21 Publisher::shouldReceive('publish')->once()->with($podcast);
22 
23 $podcast->publish();
24 }
25}

Facade 類別參照

在下表中,讀者可以找到所有的 Facade 與其底層的類別。對於像在 API 說明文件中找到某個 Facade 來源的時候,下表是很實用的工具。若有 Service container 的繫結索引鍵時,下表中也會列出。

Facade類別Service Container 的繫結
AppIlluminate\Foundation\Applicationapp
ArtisanIlluminate\Contracts\Console\Kernelartisan
AuthIlluminate\Auth\AuthManagerauth
Auth (實體)Illuminate\Contracts\Auth\Guardauth.driver
BladeIlluminate\View\Compilers\BladeCompilerblade.compiler
BroadcastIlluminate\Contracts\Broadcasting\Factory
Broadcast (實體)Illuminate\Contracts\Broadcasting\Broadcaster
BusIlluminate\Contracts\Bus\Dispatcher
CacheIlluminate\Cache\CacheManagercache
Cache (實體)Illuminate\Cache\Repositorycache.store
ConfigIlluminate\Config\Repositoryconfig
CookieIlluminate\Cookie\CookieJarcookie
CryptIlluminate\Encryption\Encrypterencrypter
DateIlluminate\Support\DateFactorydate
DBIlluminate\Database\DatabaseManagerdb
DB (實體)Illuminate\Database\Connectiondb.connection
EventIlluminate\Events\Dispatcherevents
FileIlluminate\Filesystem\Filesystemfiles
GateIlluminate\Contracts\Auth\Access\Gate
HashIlluminate\Contracts\Hashing\Hasherhash
HttpIlluminate\Http\Client\Factory
LangIlluminate\Translation\Translatortranslator
LogIlluminate\Log\LogManagerlog
MailIlluminate\Mail\Mailermailer
NotificationIlluminate\Notifications\ChannelManager
PasswordIlluminate\Auth\Passwords\PasswordBrokerManagerauth.password
Password (實體)Illuminate\Auth\Passwords\PasswordBrokerauth.password.broker
QueueIlluminate\Queue\QueueManagerqueue
Queue (實體)Illuminate\Contracts\Queue\Queuequeue.connection
Queue (基礎類別)Illuminate\Queue\Queue
RedirectIlluminate\Routing\Redirectorredirect
RedisIlluminate\Redis\RedisManagerredis
Redis (實體)Illuminate\Redis\Connections\Connectionredis.connection
RequestIlluminate\Http\Requestrequest
ResponseIlluminate\Contracts\Routing\ResponseFactory
Response (實體)Illuminate\Http\Response
RouteIlluminate\Routing\Routerrouter
SchemaIlluminate\Database\Schema\Builder
SessionIlluminate\Session\SessionManagersession
Session (實體)Illuminate\Session\Storesession.store
StorageIlluminate\Filesystem\FilesystemManagerfilesystem
Storage (實體)Illuminate\Contracts\Filesystem\Filesystemfilesystem.disk
URLIlluminate\Routing\UrlGeneratorurl
ValidatorIlluminate\Validation\Factoryvalidator
Validator (實體)Illuminate\Validation\Validator
ViewIlluminate\View\Factoryview
View (實體)Illuminate\View\View
ViteIlluminate\Foundation\Vite
翻譯進度
100% 已翻譯
更新時間:
2024年6月30日 上午8:26:00 [世界標準時間]
翻譯人員:
  • cornch
幫我們翻譯此頁

留言

尚無留言

“Laravel” is a Trademark of Taylor Otwell.
The source documentation is released under MIT license. See laravel/docs on GitHub for details.
The translated documentations are released under MIT license. See cornch/laravel-docs-l10n on GitHub for details.