Skip to content

Facades

簡介

在整個 Laravel 文件中,您會看到透過「Facades」與 Laravel 功能互動的程式碼範例。Facades 為應用程式服務容器中可用的類別提供了「靜態」介面。Laravel 內建了許多 Facades,這些 Facades 提供了對幾乎所有 Laravel 功能的存取。

Laravel Facades 充當服務容器中底層類別的「靜態代理」,提供了簡潔、富有表現力的語法優勢,同時比傳統的靜態方法具有更高的可測試性和靈活性。如果您還不太完全理解 Facades 的運作方式,這也完全沒關係——只需順其自然,繼續學習 Laravel 即可。

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

php
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Route;

Route::get('/cache', function () {
    return Cache::get('key');
});

在整個 Laravel 文件中,許多範例將使用 Facades 來演示框架的各種功能。

輔助函式

為了補充 Facades,Laravel 提供了各種全域「輔助函式」,讓與常見 Laravel 功能的互動變得更加容易。您可能會用到的一些常見輔助函式包括 viewresponseurlconfig 等。Laravel 提供的每個輔助函式都在其對應功能文件中有所說明;然而,完整的列表可在專用的輔助函式文件中找到。

例如,我們可以使用 response 函式來產生 JSON 回應,而不是使用 Illuminate\Support\Facades\Response Facade。由於輔助函式是全域可用的,您無需匯入任何類別即可使用它們:

php
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。例如,給定以下路由:

php
use Illuminate\Support\Facades\Cache;

Route::get('/cache', function () {
    return Cache::get('key');
});

使用 Laravel 的 Facade 測試方法,我們可以編寫以下測試來驗證 Cache::get 方法是否以我們預期的參數被呼叫:

php
use Illuminate\Support\Facades\Cache;

test('basic example', function () {
    Cache::shouldReceive('get')
        ->with('key')
        ->andReturn('value');

    $response = $this->get('/cache');

    $response->assertSee('value');
});
php
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 呼叫和輔助函式呼叫是等效的:

php
return Illuminate\Support\Facades\View::make('profile');

return view('profile');

Facades 與輔助函式之間絕對沒有實際差異。使用輔助函式時,您仍然可以像測試對應 Facade 一樣測試它們。例如,給定以下路由:

php
Route::get('/cache', function () {
    return cache('key');
});

cache 輔助函式將呼叫 Cache Facade 底層類別的 get 方法。因此,即使我們使用輔助函式,我們也可以編寫以下測試來驗證該方法是否以我們預期的參數被呼叫:

php
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
<?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

php
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
<?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
<?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
<?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
<?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 綁定 鍵值。

Facade類別Service Container 綁定
AppIlluminate\Foundation\Applicationapp
ArtisanIlluminate\Contracts\Console\Kernelartisan
Auth (Instance)Illuminate\Contracts\Auth\Guardauth.driver
AuthIlluminate\Auth\AuthManagerauth
BladeIlluminate\View\Compilers\BladeCompilerblade.compiler
Broadcast (Instance)Illuminate\Contracts\Broadcasting\Broadcaster 
BroadcastIlluminate\Contracts\Broadcasting\Factory 
BusIlluminate\Contracts\Bus\Dispatcher 
Cache (Instance)Illuminate\Cache\Repositorycache.store
CacheIlluminate\Cache\CacheManagercache
ConfigIlluminate\Config\Repositoryconfig
ContextIlluminate\Log\Context\Repository 
CookieIlluminate\Cookie\CookieJarcookie
CryptIlluminate\Encryption\Encrypterencrypter
DateIlluminate\Support\DateFactorydate
DB (Instance)Illuminate\Database\Connectiondb.connection
DBIlluminate\Database\DatabaseManagerdb
EventIlluminate\Events\Dispatcherevents
Exceptions (Instance)Illuminate\Contracts\Debug\ExceptionHandler 
ExceptionsIlluminate\Foundation\Exceptions\Handler 
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 
Password (Instance)Illuminate\Auth\Passwords\PasswordBrokerauth.password.broker
PasswordIlluminate\Auth\Passwords\PasswordBrokerManagerauth.password
Pipeline (Instance)Illuminate\Pipeline\Pipeline 
ProcessIlluminate\Process\Factory 
Queue (Base Class)Illuminate\Queue\Queue 
Queue (Instance)Illuminate\Contracts\Queue\Queuequeue.connection
QueueIlluminate\Queue\QueueManagerqueue
RateLimiterIlluminate\Cache\RateLimiter 
RedirectIlluminate\Routing\Redirectorredirect
Redis (Instance)Illuminate\Redis\Connections\Connectionredis.connection
RedisIlluminate\Redis\RedisManagerredis
RequestIlluminate\Http\Requestrequest
Response (Instance)Illuminate\Http\Response 
ResponseIlluminate\Contracts\Routing\ResponseFactory 
RouteIlluminate\Routing\Routerrouter
ScheduleIlluminate\Console\Scheduling\Schedule 
SchemaIlluminate\Database\Schema\Builder 
Session (Instance)Illuminate\Session\Storesession.store
SessionIlluminate\Session\SessionManagersession
Storage (Instance)Illuminate\Contracts\Filesystem\Filesystemfilesystem.disk
StorageIlluminate\Filesystem\FilesystemManagerfilesystem
URLIlluminate\Routing\UrlGeneratorurl
Validator (Instance)Illuminate\Validation\Validator 
ValidatorIlluminate\Validation\Factoryvalidator
View (Instance)Illuminate\View\View 
ViewIlluminate\View\Factoryview
ViteIlluminate\Foundation\Vite