跳到內容

外觀模式

簡介

在整個 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 功能的互動更加容易。您可能會互動的一些常見輔助函數是 viewresponseurlconfig 等。 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 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}

請注意,在檔案頂部附近,我們正在「匯入」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): void
14 {
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 Model
10{
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 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}

外觀類別參考

下面您將找到每個外觀模式及其底層類別。這是一個有用的工具,可用於快速深入研究給定外觀模式根目錄的 API 文件。服務容器綁定金鑰也包含在適用之處。

外觀模式 類別 服務容器綁定
App Illuminate\Foundation\Application app
Artisan Illuminate\Contracts\Console\Kernel artisan
Auth (Instance) Illuminate\Contracts\Auth\Guard auth.driver
Auth Illuminate\Auth\AuthManager auth
Blade Illuminate\View\Compilers\BladeCompiler blade.compiler
Broadcast (Instance) Illuminate\Contracts\Broadcasting\Broadcaster  
Broadcast Illuminate\Contracts\Broadcasting\Factory  
Bus Illuminate\Contracts\Bus\Dispatcher  
Cache (Instance) Illuminate\Cache\Repository cache.store
快取 Illuminate\Cache\CacheManager cache
Config Illuminate\Config\Repository config
Context Illuminate\Log\Context\Repository  
Cookie Illuminate\Cookie\CookieJar cookie
Crypt Illuminate\Encryption\Encrypter encrypter
Date Illuminate\Support\DateFactory date
DB (Instance) Illuminate\Database\Connection db.connection
DB Illuminate\Database\DatabaseManager db
Event Illuminate\Events\Dispatcher events
Exceptions (Instance) Illuminate\Contracts\Debug\ExceptionHandler  
Exceptions Illuminate\Foundation\Exceptions\Handler  
File Illuminate\Filesystem\Filesystem files
Gate Illuminate\Contracts\Auth\Access\Gate  
Hash Illuminate\Contracts\Hashing\Hasher hash
Http Illuminate\Http\Client\Factory  
Lang Illuminate\Translation\Translator translator
Log Illuminate\Log\LogManager log
Mail Illuminate\Mail\Mailer mailer
Notification Illuminate\Notifications\ChannelManager  
Password (Instance) Illuminate\Auth\Passwords\PasswordBroker auth.password.broker
Password Illuminate\Auth\Passwords\PasswordBrokerManager auth.password
Pipeline (Instance) Illuminate\Pipeline\Pipeline  
Process Illuminate\Process\Factory  
Queue (Base Class) Illuminate\Queue\Queue  
Queue (Instance) Illuminate\Contracts\Queue\Queue queue.connection
Queue Illuminate\Queue\QueueManager queue
RateLimiter Illuminate\Cache\RateLimiter  
Redirect Illuminate\Routing\Redirector redirect
Redis (Instance) Illuminate\Redis\Connections\Connection redis.connection
Redis Illuminate\Redis\RedisManager redis
Request Illuminate\Http\Request request
Response (Instance) Illuminate\Http\Response  
Response Illuminate\Contracts\Routing\ResponseFactory  
Route Illuminate\Routing\Router router
Schedule Illuminate\Console\Scheduling\Schedule  
Schema Illuminate\Database\Schema\Builder  
Session (Instance) Illuminate\Session\Store session.store
Session Illuminate\Session\SessionManager session
Storage (Instance) Illuminate\Contracts\Filesystem\Filesystem filesystem.disk
Storage Illuminate\Filesystem\FilesystemManager filesystem
URL Illuminate\Routing\UrlGenerator url
Validator (Instance) Illuminate\Validation\Validator  
Validator Illuminate\Validation\Factory validator
View (Instance) Illuminate\View\View  
View Illuminate\View\Factory view
Vite Illuminate\Foundation\Vite