外觀模式
簡介
在 Laravel 文件中,您會看到透過「外觀模式」與 Laravel 功能互動的程式碼範例。外觀模式為應用程式的 服務容器中可用的類別提供「靜態」介面。Laravel 提供了許多外觀模式,可存取 Laravel 的幾乎所有功能。
Laravel 外觀模式充當服務容器中底層類別的「靜態代理」,提供簡潔、富有表現力的語法,同時比傳統的靜態方法保持更高的可測試性和靈活性。如果您不完全了解外觀模式的工作原理,也沒關係 - 只需順其自然,繼續學習 Laravel 即可。
所有 Laravel 的外觀模式都在 Illuminate\Support\Facades
命名空間中定義。因此,我們可以輕鬆地存取外觀模式,如下所示
use Illuminate\Support\Facades\Cache;use Illuminate\Support\Facades\Route; Route::get('/cache', function () { return Cache::get('key');});
在整個 Laravel 文件中,許多範例將使用外觀模式來示範框架的各種功能。
輔助函數
為了補充外觀模式,Laravel 提供了各種全域「輔助函數」,可更輕鬆地與常見的 Laravel 功能互動。您可能會互動的一些常見輔助函數包括 view
、response
、url
、config
等。Laravel 提供的每個輔助函數都記錄在其對應的功能中;但是,完整的清單可在專用的 輔助文件中找到。
例如,我們可以簡單地使用 response
函數,而不是使用 Illuminate\Support\Facades\Response
外觀模式來產生 JSON 回應。由於輔助函數是全域可用的,因此您無需匯入任何類別即可使用它們
use Illuminate\Support\Facades\Response; Route::get('/users', function () { return Response::json([ // ... ]);}); Route::get('/users', function () { return response()->json([ // ... ]);});
何時使用外觀模式
外觀模式具有許多好處。它們提供了簡潔、易於記憶的語法,讓您可以使用 Laravel 的功能,而無需記住必須手動注入或設定的長類別名稱。此外,由於它們獨特地使用 PHP 的動態方法,因此易於測試。
但是,使用外觀模式時必須謹慎。外觀模式的主要危險是類別的「範圍蔓延」。由於外觀模式非常容易使用且不需要注入,因此很容易讓您的類別持續成長,並在單一類別中使用許多外觀模式。使用依賴注入,這種潛在問題會因為大型建構子提供視覺回饋,告訴您您的類別正在變得太大而得到緩解。因此,在使用外觀模式時,請特別注意類別的大小,以便其責任範圍保持狹窄。如果您的類別變得太大,請考慮將其分成多個較小的類別。
外觀模式與依賴注入
依賴注入的主要優點之一是能夠交換注入類別的實作。這在測試期間非常有用,因為您可以注入模擬或存根,並斷言在存根上呼叫了各種方法。
通常,不可能模擬或存根真正的靜態類別方法。但是,由於外觀模式使用動態方法將方法呼叫代理到從服務容器解析的物件,因此我們實際上可以像測試注入的類別實例一樣測試外觀模式。例如,假設有以下路由
use Illuminate\Support\Facades\Cache; Route::get('/cache', function () { return Cache::get('key');});
使用 Laravel 的外觀模式測試方法,我們可以編寫以下測試來驗證是否使用我們預期的引數呼叫了 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');}
外觀模式與輔助函數
除了外觀模式之外,Laravel 還包含各種「輔助」函數,可以執行常見的任務,例如產生視圖、觸發事件、分派任務或傳送 HTTP 回應。這些輔助函數中的許多都執行與對應外觀模式相同的功能。例如,此外觀模式呼叫和輔助呼叫是等效的
return Illuminate\Support\Facades\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. */public function test_basic_example(): void{ Cache::shouldReceive('get') ->with('key') ->andReturn('value'); $response = $this->get('/cache'); $response->assertSee('value');}
外觀模式如何運作
在 Laravel 應用程式中,外觀模式是一個類別,可提供對容器中物件的存取權。使這項工作運作的機制位於 Facade
類別中。Laravel 的外觀模式以及您建立的任何自訂外觀模式都將擴充基礎 Illuminate\Support\Facades\Facade
類別。
Facade
基礎類別利用 __callStatic()
魔術方法將呼叫從您的外觀模式延遲到從容器解析的物件。在下面的範例中,會呼叫 Laravel 快取系統。瀏覽此程式碼,可能會假設正在 Cache
類別上呼叫靜態 get
方法
<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller;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
外觀模式。此外觀模式充當存取 Illuminate\Contracts\Cache\Factory
介面的底層實作的代理。我們使用此外觀模式進行的任何呼叫都將傳遞給 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
類別並定義方法 getFacadeAccessor()
。此方法的工作是傳回服務容器繫結的名稱。當使用者參考 Cache
外觀模式上的任何靜態方法時,Laravel 會從 服務容器 解析 cache
繫結,並針對該物件執行請求的方法(在本例中為 get
)。
即時外觀模式
使用即時外觀模式,您可以將應用程式中的任何類別視為外觀模式。為了說明如何使用它,我們先檢查一些不使用即時外觀模式的程式碼。例如,假設我們的 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); }}
將發布者實作注入到方法中,可以讓我們輕鬆地獨立測試該方法,因為我們可以模擬注入的發布者。然而,這要求我們每次呼叫 publish
方法時都要傳遞一個發布者實例。使用即時門面,我們可以在維持相同可測試性的同時,不必顯式地傳遞 Publisher
實例。要產生即時門面,請在匯入的類別命名空間前加上 Facades
前綴。
<?php namespace App\Models; use App\Contracts\Publisher; use Facades\App\Contracts\Publisher; use Illuminate\Database\Eloquent\Model; class Podcast extends Model{ /** * Publish the podcast. */ public function publish(Publisher $publisher): void public function publish(): void { $this->update(['publishing' => now()]); $publisher->publish($this); Publisher::publish($this); }}
當使用即時門面時,發布者實作會使用介面或類別名稱中 Facades
前綴後的部分,從服務容器中解析出來。在測試時,我們可以利用 Laravel 內建的門面測試輔助工具來模擬這個方法呼叫。
<?php use App\Models\Podcast;use Facades\App\Contracts\Publisher;use Illuminate\Foundation\Testing\RefreshDatabase; uses(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(); }}
外觀模式類別參考
下方您將找到每個門面及其底層類別。這是一個有用的工具,可以快速深入研究給定門面根的 API 文件。在適用的情況下,也包含服務容器綁定的鍵。