> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Facade

> 說明 Laravel Facade 的機制與用法、即時 Facade、測試方式，以及與依賴注入的取捨。

## Facade 是什麼

Facade 為應用程式[服務容器](/zh-TW/service-container)中可用的類別提供了「靜態」介面。Laravel 內建有大量 Facade，幾乎能存取所有功能。

Laravel 的 Facade 作為服務容器中實體類別的「靜態代理」運作，比起傳統的靜態方法更容易測試也更具彈性，同時提供簡潔且富有表達力的語法。

所有 Laravel Facade 都定義在 `Illuminate\Support\Facades` 命名空間下。

```php theme={null}
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Route;

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

<Info>
  就算你還沒完全理解 Facade 的機制也沒關係。先熟悉用法，繼續學習 Laravel 即可。
</Info>

## Facade 的運作機制

在 Laravel 中，Facade 是提供對容器中物件存取權的類別。此機制由 `Facade` 基底類別實作。Laravel 的所有 Facade（含自訂 Facade）都繼承基底類別 `Illuminate\Support\Facades\Facade`。

`Facade` 基底類別使用 `__callStatic()` 魔術方法，將對 Facade 的呼叫委派給容器中解析出的物件。

```mermaid theme={null}
flowchart LR
    A["Cache::get('key')"] --> B["Facade::__callStatic()"]
    B --> C["getFacadeAccessor()<br>→ 'cache'"]
    C --> D["服務容器<br>make('cache')"]
    D --> E["實體類別實例<br>CacheManager"]
    E --> F["執行<br>get('key')"]
```

```php theme={null}
<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Cache;
use Illuminate\View\View;

class UserController extends Controller
{
    /**
     * 顯示指定使用者的個人資料
     */
    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 theme={null}
class Cache extends Facade
{
    /**
     * 取得元件的註冊名稱
     */
    protected static function getFacadeAccessor(): string
    {
        return 'cache';
    }
}
```

`Cache` Facade 繼承 `Facade` 並定義了 `getFacadeAccessor()`。此方法回傳的是服務容器的繫結名稱。當使用者呼叫 `Cache` Facade 的靜態方法時，Laravel 會由容器解析出 `cache` 繫結，然後對該物件呼叫實際的方法（此例中的 `get`）。

## 何時該用、何時不該用 Facade

### Facade 的優點

Facade 有許多優點。它讓你不必記憶必須手動注入設定的長類別名稱，就能以簡潔而好記的語法使用 Laravel 的功能。此外，因為 PHP 動態方法的獨特用法，Facade 也非常容易測試。

### 注意作用域擴散

使用 Facade 時最主要的風險是類別的「作用域擴散」。因為 Facade 好用且不需注入，容易讓類別越來越肥大，塞入大量 Facade。若使用依賴注入，龐大的建構子會提供視覺上的警告訊號。使用 Facade 時，請務必留意類別的大小、保持職責範圍夠小。

<Warning>
  當覺得類別開始變得太大時，考慮拆成多個小類別。
</Warning>

### Facade vs. 依賴注入

依賴注入的一大優點是可以替換所注入類別的實作，這在測試時很有用，可以注入 mock 或 stub，並斷言各方法是否被呼叫。

真正靜態的類別方法通常無法 mock 或 stub，但 Facade 是透過動態方法，代理呼叫到容器解析出的物件，因此可像對待被注入的類別實例一樣測試 Facade。

```php theme={null}
use Illuminate\Support\Facades\Cache;

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

對這個路由可以撰寫以下測試，驗證 `Cache::get` 是以預期引數被呼叫。

```php theme={null}
use Illuminate\Support\Facades\Cache;

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

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

    $response->assertSee('value');
});
```

### Facade vs. 輔助函式

除了 Facade，Laravel 還提供「輔助函式」可執行像是產生視圖、觸發事件、派發 job、送出 HTTP 回應等常見任務。許多輔助函式與對應 Facade 的功能相同。

```php theme={null}
// 使用 Facade 的呼叫
return Illuminate\Support\Facades\View::make('profile');

// 使用輔助函式的等價呼叫
return view('profile');
```

Facade 與輔助函式在本質上並無差別。即使使用輔助函式，也可以像對應 Facade 一樣進行測試。

```php theme={null}
use Illuminate\Support\Facades\Cache;

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

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

    $response->assertSee('value');
});
```

## 即時 Facade

透過即時 Facade，可以將應用程式中的任意類別當作 Facade 使用。先看看沒有使用即時 Facade 的程式碼作為對照。

例如 `Podcast` 模型有一個 `publish` 方法，但發佈 podcast 需要注入 `Publisher` 實例。

```php theme={null}
<?php

namespace App\Models;

use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;

class Podcast extends Model
{
    /**
     * 發佈 podcast
     */
    public function publish(Publisher $publisher): void
    {
        $this->update(['publishing' => now()]);

        $publisher->publish($this);
    }
}
```

使用即時 Facade 後，可保持同樣的可測試性，同時不必顯式傳入 `Publisher` 實例。要建立即時 Facade，只需在引入類別的命名空間前加上 `Facades` 前綴。

```php theme={null}
<?php

namespace App\Models;

use Facades\App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;

class Podcast extends Model
{
    /**
     * 發佈 podcast
     */
    public function publish(): void
    {
        $this->update(['publishing' => now()]);

        Publisher::publish($this);
    }
}
```

在使用即時 Facade 時，會用 `Facades` 前綴之後的介面或類別名稱，從服務容器中解析出 publisher 的實作。

```php theme={null}
<?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();
});
```

<Tip>
  即時 Facade 適合用在既想簡化測試 mock、又想擺脫將依賴當作引數傳遞的情境。
</Tip>

## Facade 的測試

要測試 Facade 可用 `shouldReceive` 方法，會回傳 Mockery 的 mock 實例。Facade 實際上由服務容器解析與管理，因此比一般靜態類別容易測試許多。

```php theme={null}
use Illuminate\Support\Facades\Cache;

test('顯示使用者個人資料', function () {
    Cache::shouldReceive('get')
        ->once()
        ->with('user:1')
        ->andReturn(['name' => 'Taylor']);

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

    $response->assertSee('Taylor');
});
```

常用的 mock 方法如下：

| 方法                        | 說明         |
| ------------------------- | ---------- |
| `shouldReceive('method')` | 預期方法會被呼叫   |
| `once()`                  | 預期只被呼叫一次   |
| `times(n)`                | 預期被呼叫 n 次  |
| `with(args)`              | 預期以特定引數被呼叫 |
| `andReturn(value)`        | 回傳指定的值     |
| `andReturnNull()`         | 回傳 null    |

## 主要 Facade 一覽

以下為常用 Facade 及其對應實體類別、服務容器繫結名稱的對照表。

| Facade         | 類別                                        | 繫結           |
| -------------- | ----------------------------------------- | ------------ |
| `App`          | `Illuminate\Foundation\Application`       | `app`        |
| `Auth`         | `Illuminate\Auth\AuthManager`             | `auth`       |
| `Cache`        | `Illuminate\Cache\CacheManager`           | `cache`      |
| `Config`       | `Illuminate\Config\Repository`            | `config`     |
| `Cookie`       | `Illuminate\Cookie\CookieJar`             | `cookie`     |
| `Crypt`        | `Illuminate\Encryption\Encrypter`         | `encrypter`  |
| `DB`           | `Illuminate\Database\DatabaseManager`     | `db`         |
| `Event`        | `Illuminate\Events\Dispatcher`            | `events`     |
| `File`         | `Illuminate\Filesystem\Filesystem`        | `files`      |
| `Gate`         | `Illuminate\Contracts\Auth\Access\Gate`   | —            |
| `Hash`         | `Illuminate\Contracts\Hashing\Hasher`     | `hash`       |
| `Http`         | `Illuminate\Http\Client\Factory`          | —            |
| `Log`          | `Illuminate\Log\LogManager`               | `log`        |
| `Mail`         | `Illuminate\Mail\Mailer`                  | `mailer`     |
| `Notification` | `Illuminate\Notifications\ChannelManager` | —            |
| `Queue`        | `Illuminate\Queue\QueueManager`           | `queue`      |
| `RateLimiter`  | `Illuminate\Cache\RateLimiter`            | —            |
| `Redirect`     | `Illuminate\Routing\Redirector`           | `redirect`   |
| `Request`      | `Illuminate\Http\Request`                 | `request`    |
| `Route`        | `Illuminate\Routing\Router`               | `router`     |
| `Schema`       | `Illuminate\Database\Schema\Builder`      | —            |
| `Session`      | `Illuminate\Session\SessionManager`       | `session`    |
| `Storage`      | `Illuminate\Filesystem\FilesystemManager` | `filesystem` |
| `URL`          | `Illuminate\Routing\UrlGenerator`         | `url`        |
| `Validator`    | `Illuminate\Validation\Factory`           | `validator`  |
| `View`         | `Illuminate\View\Factory`                 | `view`       |

## 下一步

<Card title="Contracts（契約）" icon="file-contract" href="/zh-TW/contracts">
  學習與 Facade 相對應的 Contracts 概念及兩者的抉擇。
</Card>


## Related topics

- [套件的靜態分析（PHPStan / Larastan）](/zh-TW/advanced/package-static-analysis.md)
- [Laravel 套件開發](/zh-TW/advanced/package-development.md)
- [Service Container](/zh-TW/service-container.md)
- [Mock](/zh-TW/mocking.md)
- [Contracts（契約）](/zh-TW/contracts.md)
