Facades
簡介
在整個 Laravel 文件中,您會看到透過「Facades」與 Laravel 功能互動的程式碼範例。Facades 為應用程式服務容器中可用的類別提供了「靜態」介面。Laravel 內建了許多 Facades,這些 Facades 提供了對幾乎所有 Laravel 功能的存取。
Laravel Facades 充當服務容器中底層類別的「靜態代理」,提供了簡潔、富有表現力的語法優勢,同時比傳統的靜態方法具有更高的可測試性和靈活性。如果您還不太完全理解 Facades 的運作方式,這也完全沒關係——只需順其自然,繼續學習 Laravel 即可。
所有 Laravel 的 Facades 都定義在 Illuminate\Support\Facades
命名空間中。因此,我們可以像這樣輕鬆地存取 Facade:
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Route;
Route::get('/cache', function () {
return Cache::get('key');
});
在整個 Laravel 文件中,許多範例將使用 Facades 來演示框架的各種功能。
輔助函式
為了補充 Facades,Laravel 提供了各種全域「輔助函式」,讓與常見 Laravel 功能的互動變得更加容易。您可能會用到的一些常見輔助函式包括 view
、response
、url
、config
等。Laravel 提供的每個輔助函式都在其對應功能文件中有所說明;然而,完整的列表可在專用的輔助函式文件中找到。
例如,我們可以使用 response
函式來產生 JSON 回應,而不是使用 Illuminate\Support\Facades\Response
Facade。由於輔助函式是全域可用的,您無需匯入任何類別即可使用它們:
use Illuminate\Support\Facades\Response;
Route::get('/users', function () {
return Response::json([
// ...
]);
});
Route::get('/users', function () {
return response()->json([
// ...
]);
});
何時使用 Facades
Facades 有許多好處。它們提供了簡潔、易於記憶的語法,讓您無需記住必須手動注入或設定的冗長類別名稱,即可使用 Laravel 的功能。此外,由於它們獨特地使用了 PHP 的動態方法,因此易於測試。
然而,使用 Facades 時必須謹慎。Facades 的主要危險是類別「職責蔓延」。由於 Facades 非常易於使用且不需要注入,因此很容易讓您的類別不斷增長,並在單一類別中使用許多 Facades。使用依賴注入,可以透過大型建構子所提供的視覺回饋,提示您的類別正變得過於龐大,從而減輕這種潛在風險。因此,在使用 Facades 時,請特別注意類別的大小,以確保其職責範圍保持狹窄。如果您的類別變得過於龐大,請考慮將其拆分為多個較小的類別。
Facades 與依賴注入的比較
依賴注入的主要好處之一是能夠交換注入類別的實作。這在測試期間非常有用,因為您可以注入模擬 (mock) 或替身 (stub),並斷言替身上的各個方法是否被呼叫。
通常,要模擬或替換一個真正的靜態類別方法是不可能的。然而,由於 Facades 使用動態方法將方法呼叫代理到從服務容器解析的物件,我們實際上可以像測試注入的類別實例一樣測試 Facades。例如,給定以下路由:
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
return Cache::get('key');
});
使用 Laravel 的 Facade 測試方法,我們可以編寫以下測試來驗證 Cache::get
方法是否以我們預期的參數被呼叫:
use Illuminate\Support\Facades\Cache;
test('basic example', function () {
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$response = $this->get('/cache');
$response->assertSee('value');
});
use Illuminate\Support\Facades\Cache;
/**
* A basic functional test example.
*/
public function test_basic_example(): void
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$response = $this->get('/cache');
$response->assertSee('value');
}
Facades 與輔助函式的比較
除了 Facades 之外,Laravel 還包含各種「輔助」函式,可以執行常見任務,例如產生視圖 (views)、觸發事件 (events)、分派任務 (jobs) 或傳送 HTTP 回應。許多這些輔助函式執行與對應 Facade 相同的功能。例如,以下 Facade 呼叫和輔助函式呼叫是等效的:
return Illuminate\Support\Facades\View::make('profile');
return view('profile');
Facades 與輔助函式之間絕對沒有實際差異。使用輔助函式時,您仍然可以像測試對應 Facade 一樣測試它們。例如,給定以下路由:
Route::get('/cache', function () {
return cache('key');
});
cache
輔助函式將呼叫 Cache
Facade 底層類別的 get
方法。因此,即使我們使用輔助函式,我們也可以編寫以下測試來驗證該方法是否以我們預期的參數被呼叫:
use Illuminate\Support\Facades\Cache;
/**
* A basic functional test example.
*/
public function test_basic_example(): void
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$response = $this->get('/cache');
$response->assertSee('value');
}
Facades 的運作方式
在 Laravel 應用程式中,Facade 是一個提供從容器中存取物件的類別。使其運作的機制在於 Facade
類別。Laravel 的 Facades,以及您創建的任何自訂 Facades,都將繼承基礎 Illuminate\Support\Facades\Facade
類別。
Facade
基礎類別利用 __callStatic()
魔術方法,將 Facade 的呼叫延遲到從容器中解析的物件。在下面的範例中,對 Laravel 快取系統進行了呼叫。瞥一眼這段程式碼,人們可能會認為靜態 get
方法正在 Cache
類別上被呼叫:
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Cache;
use Illuminate\View\View;
class UserController extends Controller
{
/**
* Show the profile for the given user.
*/
public function showProfile(string $id): View
{
$user = Cache::get('user:'.$id);
return view('profile', ['user' => $user]);
}
}
請注意,在檔案頂部我們正在「匯入」Cache
Facade。此 Facade 充當存取 Illuminate\Contracts\Cache\Factory
介面底層實作的代理。我們使用 Facade 進行的任何呼叫都將傳遞給 Laravel 快取服務的底層實例。
如果我們查看 Illuminate\Support\Facades\Cache
類別,您會發現沒有靜態方法 get
:
class Cache extends Facade
{
/**
* Get the registered name of the component.
*/
protected static function getFacadeAccessor(): string
{
return 'cache';
}
}
相反地,Cache
Facade 繼承了基礎 Facade
類別並定義了 getFacadeAccessor()
方法。此方法的作用是回傳服務容器綁定的名稱。當使用者參考 Cache
Facade 上的任何靜態方法時,Laravel 會從服務容器中解析 cache
綁定,並針對該物件執行所請求的方法 (在此案例中為 get
)。
即時 Facades
透過使用即時 Facades,您可以將應用程式中的任何類別視為一個 Facade。為了說明其用法,我們先來看一段不使用即時 Facades 的程式碼。例如,假設我們的 Podcast
模型有一個 publish
方法。然而,為了發佈 Podcast,我們需要注入一個 Publisher
實例:
<?php
namespace App\Models;
use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* Publish the podcast.
*/
public function publish(Publisher $publisher): void
{
$this->update(['publishing' => now()]);
$publisher->publish($this);
}
}
將 Publisher 實作注入到方法中,可以讓我們輕鬆地獨立測試該方法,因為我們可以模擬注入的 Publisher。然而,這要求我們每次呼叫 publish
方法時,都必須傳遞一個 Publisher 實例。透過使用即時 Facades,我們可以保持相同的可測試性,而無需明確傳遞 Publisher
實例。要生成一個即時 Facade,請在匯入類別的命名空間前加上 Facades
:
<?php
namespace App\Models;
use App\Contracts\Publisher; // [tl! remove]
use Facades\App\Contracts\Publisher; // [tl! add]
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* Publish the podcast.
*/
public function publish(Publisher $publisher): void // [tl! remove]
public function publish(): void // [tl! add]
{
$this->update(['publishing' => now()]);
$publisher->publish($this); // [tl! remove]
Publisher::publish($this); // [tl! add]
}
}
當使用即時 Facade 時,Publisher 實作將會從服務容器中解析,使用介面或類別名稱中出現在 Facades
前綴後面的部分。在測試時,我們可以利用 Laravel 內建的 Facade 測試輔助函式來模擬此方法呼叫:
<?php
use App\Models\Podcast;
use Facades\App\Contracts\Publisher;
use Illuminate\Foundation\Testing\RefreshDatabase;
pest()->use(RefreshDatabase::class);
test('podcast can be published', function () {
$podcast = Podcast::factory()->create();
Publisher::shouldReceive('publish')->once()->with($podcast);
$podcast->publish();
});
<?php
namespace Tests\Feature;
use App\Models\Podcast;
use Facades\App\Contracts\Publisher;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PodcastTest extends TestCase
{
use RefreshDatabase;
/**
* A test example.
*/
public function test_podcast_can_be_published(): void
{
$podcast = Podcast::factory()->create();
Publisher::shouldReceive('publish')->once()->with($podcast);
$podcast->publish();
}
}
Facade 類別參考
下方列出了所有 Facade 及其底層類別。這是個快速查閱指定 Facade 根目錄 API 文件的實用工具。在適用情況下,也包含了 Service Container 綁定 鍵值。