外觀模式
簡介
在整個 Laravel 文件中,您將看到透過「外觀模式」與 Laravel 功能互動的程式碼範例。外觀模式為應用程式的服務容器中可用的類別提供「靜態」介面。 Laravel 附帶許多外觀模式,可存取幾乎所有 Laravel 的功能。
Laravel 外觀模式充當服務容器中底層類別的「靜態代理」,提供簡潔、具表現力的語法優勢,同時保持比傳統靜態方法更高的可測試性和靈活性。如果您不完全理解外觀模式如何運作也沒關係 - 順其自然,繼續學習 Laravel 即可。
所有 Laravel 的外觀模式都在 Illuminate\Support\Facades
命名空間中定義。因此,我們可以輕鬆地存取外觀模式,如下所示
1use Illuminate\Support\Facades\Cache;2use Illuminate\Support\Facades\Route;3 4Route::get('/cache', function () {5 return Cache::get('key');6});
在整個 Laravel 文件中,許多範例將使用外觀模式來示範框架的各種功能。
輔助函數
為了補充外觀模式,Laravel 提供了各種全域「輔助函數」,使與常見 Laravel 功能的互動更加容易。您可能會互動的一些常見輔助函數是 view
、response
、url
、config
等。 Laravel 提供的每個輔助函數都記錄在其對應的功能中;但是,完整的列表可在專門的輔助函數文件中找到。
例如,與其使用 Illuminate\Support\Facades\Response
外觀模式來產生 JSON 響應,我們可以直接使用 response
函數。由於輔助函數是全域可用的,因此您無需匯入任何類別即可使用它們
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});
何時使用外觀模式
外觀模式有很多好處。它們提供簡潔、易於記憶的語法,讓您可以使用 Laravel 的功能,而無需記住必須手動注入或設定的長類別名稱。此外,由於它們對 PHP 動態方法的獨特使用,它們易於測試。
但是,使用外觀模式時必須謹慎。外觀模式的主要危險是類別「範圍蔓延」。由於外觀模式非常易於使用且不需要注入,因此很容易讓您的類別繼續增長,並在單個類別中使用許多外觀模式。使用依賴注入,可以透過大型建構子給您的視覺回饋來減輕這種潛在風險,表明您的類別正在變得太大。因此,當使用外觀模式時,請特別注意類別的大小,以使其職責範圍保持狹窄。如果您的類別變得太大,請考慮將其拆分為多個較小的類別。
外觀模式 vs. 依賴注入
依賴注入的主要優點之一是能夠交換注入類別的實作。這在測試期間很有用,因為您可以注入模擬或樁,並斷言在樁上呼叫了各種方法。
通常,不可能模擬或樁一個真正的靜態類別方法。但是,由於外觀模式使用動態方法將方法呼叫代理到從服務容器解析的物件,因此我們實際上可以像測試注入的類別實例一樣測試外觀模式。例如,給定以下路由
1use Illuminate\Support\Facades\Cache;2 3Route::get('/cache', function () {4 return Cache::get('key');5});
使用 Laravel 的外觀模式測試方法,我們可以編寫以下測試來驗證是否使用我們預期的參數呼叫了 Cache::get
方法
1use Illuminate\Support\Facades\Cache; 2 3test('basic example', function () { 4 Cache::shouldReceive('get') 5 ->with('key') 6 ->andReturn('value'); 7 8 $response = $this->get('/cache'); 9 10 $response->assertSee('value');11});
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}
外觀模式 vs. 輔助函數
除了外觀模式之外,Laravel 還包含各種「輔助」函數,它們可以執行常見的任務,例如產生視圖、觸發事件、調度任務或發送 HTTP 響應。許多這些輔助函數執行與相應外觀模式相同的功能。例如,這個外觀模式呼叫和輔助函數呼叫是等效的
1return Illuminate\Support\Facades\View::make('profile');2 3return view('profile');
外觀模式和輔助函數之間絕對沒有實際區別。當使用輔助函數時,您仍然可以像測試相應的外觀模式一樣進行測試。例如,給定以下路由
1Route::get('/cache', function () {2 return cache('key');3});
cache
輔助函數將呼叫 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}
外觀模式如何運作
在 Laravel 應用程式中,外觀模式是一個類別,提供對容器中物件的存取。使此工作運作的機制位於 Facade
類別中。 Laravel 的外觀模式以及您建立的任何自訂外觀模式都將擴展基礎 Illuminate\Support\Facades\Facade
類別。
Facade
基礎類別使用 __callStatic()
魔術方法將來自您的外觀模式的呼叫延遲到從容器解析的物件。在下面的範例中,呼叫是針對 Laravel 快取系統發出的。透過瀏覽此程式碼,人們可能會認為靜態 get
方法正在 Cache
類別上呼叫
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 Controller10{11 /**12 * Show the profile for the given user.13 */14 public function showProfile(string $id): View15 {16 $user = Cache::get('user:'.$id);17 18 return view('profile', ['user' => $user]);19 }20}
請注意,在檔案頂部附近,我們正在「匯入」Cache
外觀模式。此外觀模式充當存取 Illuminate\Contracts\Cache\Factory
介面底層實作的代理。我們使用外觀模式進行的任何呼叫都將傳遞給 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}
相反,Cache
外觀模式擴展了基礎 Facade
類別,並定義了方法 getFacadeAccessor()
。此方法的工作是傳回服務容器綁定的名稱。當使用者引用 Cache
外觀模式上的任何靜態方法時,Laravel 會從服務容器解析 cache
綁定,並針對該物件執行請求的方法(在本例中為 get
)。
即時外觀模式
使用即時外觀模式,您可以將應用程式中的任何類別視為外觀模式。為了說明如何使用它,讓我們先檢查一些不使用即時外觀模式的程式碼。例如,假設我們的 Podcast
模型有一個 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): void14 {15 $this->update(['publishing' => now()]);16 17 $publisher->publish($this);18 }19}
將發布者實作注入到方法中,使我們能夠輕鬆地隔離測試該方法,因為我們可以模擬注入的發布者。但是,它要求我們每次呼叫 publish
方法時都傳遞發布者實例。使用即時外觀模式,我們可以保持相同的可測試性,同時不需要顯式傳遞 Publisher
實例。要產生即時外觀模式,請在匯入類別的命名空間前加上 Facades
1<?php 2 3namespace App\Models; 4 5use App\Contracts\Publisher; 6use Facades\App\Contracts\Publisher; 7use Illuminate\Database\Eloquent\Model; 8 9class Podcast extends Model10{11 /**12 * Publish the podcast.13 */14 public function publish(Publisher $publisher): void 15 public function publish(): void 16 {17 $this->update(['publishing' => now()]);18 19 $publisher->publish($this); 20 Publisher::publish($this); 21 }22}
當使用即時外觀模式時,發布者實作將從服務容器中解析出來,使用介面或類別名稱中出現在 Facades
前綴之後的部分。在測試時,我們可以使用 Laravel 的內建外觀模式測試輔助函數來模擬此方法呼叫
1<?php 2 3use App\Models\Podcast; 4use Facades\App\Contracts\Publisher; 5use Illuminate\Foundation\Testing\RefreshDatabase; 6 7uses(RefreshDatabase::class); 8 9test('podcast can be published', function () {10 $podcast = Podcast::factory()->create();11 12 Publisher::shouldReceive('publish')->once()->with($podcast);13 14 $podcast->publish();15});
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 TestCase11{12 use RefreshDatabase;13 14 /**15 * A test example.16 */17 public function test_podcast_can_be_published(): void18 {19 $podcast = Podcast::factory()->create();20 21 Publisher::shouldReceive('publish')->once()->with($podcast);22 23 $podcast->publish();24 }25}
外觀類別參考
下面您將找到每個外觀模式及其底層類別。這是一個有用的工具,可用於快速深入研究給定外觀模式根目錄的 API 文件。服務容器綁定金鑰也包含在適用之處。