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

# Illuminate\Support\Manager — Driver 系統解剖

> 自 Laravel 4.0 延續至今的 Driver 系統核心類別。從自訂 Driver 的註冊方式到 MultipleInstanceManager 一併說明。

## 什麼是 Manager

`Illuminate\Support\Manager` 是自 Laravel 4.0 以來就存在的抽象類別。它提供簡易的基礎，用來建構可切換多個「Driver」使用的系統，例如 Cache、Session、Mail。

「Driver」是指擁有相同介面但內部實作不同的後端。舉例來說，Session 有 `file`、`cookie`、`database`、`redis` 等 Driver，可透過設定檔的 `driver` 鍵切換。

```mermaid theme={null}
classDiagram
    class Manager {
        <<abstract>>
        #container
        #config
        #customCreators[]
        #drivers[]
        +driver(name) mixed
        +extend(driver, Closure) self
        +getDrivers() array
        +forgetDrivers() self
        +getDefaultDriver()* string
        #createDriver(driver) mixed
        #callCustomCreator(driver) mixed
    }

    class SessionManager {
        +getDefaultDriver() string
        #createFileDriver() Store
        #createDatabaseDriver() Store
        #createRedisDriver() Store
        #createArrayDriver() Store
    }

    class YourCustomManager {
        +getDefaultDriver() string
        #createFooDriver() mixed
        #createBarDriver() mixed
    }

    Manager <|-- SessionManager
    Manager <|-- YourCustomManager
```

## 框架內的使用狀況

即便名稱含有 `Manager`，仍有並**未**繼承 `Illuminate\Support\Manager` 的類別存在。

| 類別                                   | 是否繼承 `Manager` |
| ------------------------------------ | -------------- |
| `Illuminate\Session\SessionManager`  | ✅ 有繼承          |
| `Illuminate\Cache\CacheManager`      | ❌ 未繼承（自行實作）    |
| `Illuminate\Queue\QueueManager`      | ❌ 未繼承（自行實作）    |
| `Laravel\Socialite\SocialiteManager` | ✅ 有繼承          |

未繼承者，其「以 `extend()` 擴充」的基本觀念是相同的。因此理解 `Manager` 模式即等於理解框架整體運作。

## 基本機制

### driver() — 取得 Driver

呼叫 `driver()` 時，會依下列順序解析 Driver。

```mermaid theme={null}
flowchart TD
    A["呼叫 driver(name)"] --> B{drivers[] 內<br>是否已有快取?}
    B -- 是 --> C["回傳已快取的<br>實例"]
    B -- 否 --> D{customCreators[]<br>是否已註冊?}
    D -- 是 --> E["以 callCustomCreator()<br>產生實例"]
    D -- 否 --> F{"createXxxDriver()<br>方法是否存在?"}
    F -- 是 --> G["呼叫 createXxxDriver()"]
    F -- 否 --> H["拋出<br>InvalidArgumentException"]
    E --> I["於 drivers[] 快取並回傳"]
    G --> I
```

觀察實際原始碼可看到，從 Driver 名稱以 `Str::studly()` 組成方法名稱。

```php theme={null}
// 出自 Illuminate\Support\Manager::createDriver()
protected function createDriver($driver)
{
    if (isset($this->customCreators[$driver])) {
        return $this->callCustomCreator($driver);
    }

    $method = 'create'.Str::studly($driver).'Driver';

    if (method_exists($this, $method)) {
        return $this->$method();
    }

    throw new InvalidArgumentException("Driver [$driver] not supported.");
}
```

也就是說，`file` Driver 會呼叫 `createFileDriver()`，`my-custom` Driver 則會呼叫 `createMyCustomDriver()`。

### extend() — 註冊自訂 Driver

透過 `extend()` 傳入 Driver 名稱與 closure，即可註冊自訂 Driver。closure 的引數為容器實例。

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

Session::extend('redis-cluster', function ($app) {
    return new RedisClusterSessionHandler(
        $app->make('redis'),
        $app['config']['session'],
    );
});
```

`extend()` 會將 closure 綁定至 `$this`，因此可在 closure 中存取 Manager 的屬性與方法。

```php theme={null}
// 出自 Illuminate\Support\Manager::extend()
public function extend($driver, Closure $callback)
{
    try {
        $callback = $callback->bindTo($this, static::class) ?? throw new RuntimeException;
    } catch (Throwable) {
        $callback = $callback->bindTo(null, static::class);
    }

    $this->customCreators[$driver] = $callback;

    return $this;
}
```

### \_\_call — 委派至預設 Driver

Manager 類別實作了 `__call`，Manager 本身不存在的方法呼叫會自動委派給預設 Driver。

```php theme={null}
public function __call($method, $parameters)
{
    return $this->driver()->$method(...$parameters);
}
```

如此可實現「像 `SessionManager::get('key')` 一樣直接使用 Manager，實際處理則交給 Driver」的流程。

## SessionManager 的實作範例

觀察 `Illuminate\Session\SessionManager` 可以具體了解 `Manager` 的使用方式。

```php theme={null}
namespace Illuminate\Session;

use Illuminate\Support\Manager;

class SessionManager extends Manager
{
    // 必要：回傳預設 Driver
    public function getDefaultDriver()
    {
        return $this->config->get('session.driver');
    }

    // file Driver 的產生
    protected function createFileDriver()
    {
        return $this->createNativeDriver();
    }

    // database Driver 的產生
    protected function createDatabaseDriver()
    {
        $table = $this->config->get('session.table');
        $lifetime = $this->config->get('session.lifetime');

        return $this->buildSession(new DatabaseSessionHandler(
            $this->getDatabaseConnection(), $table, $lifetime, $this->container
        ));
    }

    // redis Driver 的產生
    protected function createRedisDriver()
    {
        $handler = $this->createCacheHandler('redis');
        // ...
        return $this->buildSession($handler);
    }
}
```

## 於自製套件中使用 Manager

### 基本實作

以通知服務為例，建立繼承 `Manager` 的自訂類別。

<Steps>
  <Step title="建立繼承 Manager 的類別">
    ```php theme={null}
    namespace App\Notifications;

    use Illuminate\Support\Manager;

    class NotificationManager extends Manager
    {
        public function getDefaultDriver(): string
        {
            return $this->config->get('notifications.driver', 'slack');
        }

        protected function createSlackDriver(): SlackNotifier
        {
            return new SlackNotifier(
                $this->config->get('notifications.slack'),
            );
        }

        protected function createEmailDriver(): EmailNotifier
        {
            return new EmailNotifier(
                $this->config->get('notifications.email'),
            );
        }

        protected function createLogDriver(): LogNotifier
        {
            return new LogNotifier(
                $this->container->make('log'),
            );
        }
    }
    ```
  </Step>

  <Step title="於服務提供者註冊">
    ```php theme={null}
    namespace App\Providers;

    use App\Notifications\NotificationManager;
    use Illuminate\Support\ServiceProvider;

    class NotificationServiceProvider extends ServiceProvider
    {
        public function register(): void
        {
            $this->app->singleton(NotificationManager::class, function ($app) {
                return new NotificationManager($app);
            });
        }
    }
    ```
  </Step>

  <Step title="新增自訂 Driver">
    ```php theme={null}
    // 於 AppServiceProvider::boot() 等

    $manager = app(NotificationManager::class);

    $manager->extend('teams', function ($app) {
        return new TeamsNotifier(
            $app['config']['notifications.teams'],
        );
    });
    ```
  </Step>

  <Step title="使用">
    ```php theme={null}
    $manager = app(NotificationManager::class);

    // 使用預設 Driver
    $manager->send('訊息');

    // 指定特定 Driver
    $manager->driver('email')->send('訊息');

    // 已解析過的 Driver 會被快取
    $manager->driver('slack'); // 回傳同一實例
    ```
  </Step>
</Steps>

## MultipleInstanceManager

`Illuminate\Support\MultipleInstanceManager` 是 Laravel 10 新增的類別。相對於 `Manager` 管理 Driver 的「種類」，`MultipleInstanceManager` 可管理多個具名的「實例」。

### 與 Manager 的差異

|        | `Manager`                    | `MultipleInstanceManager`    |
| ------ | ---------------------------- | ---------------------------- |
| 管理單位   | Driver 種類（`file`, `redis` 等） | 具名實例（`mailer1`, `mailer2` 等） |
| 設定持有方式 | 一個預設 Driver                  | 各實例各持設定                      |
| 主要用途   | Session、Cache 等              | Mail、Log 等需要多個連線者            |
| 取得方法   | `driver()`                   | `instance()`                 |

### 必要方法

若要繼承 `MultipleInstanceManager`，需實作 3 個方法。

```php theme={null}
// 出自原始碼
abstract public function getDefaultInstance();
abstract public function setDefaultInstance($name);
abstract public function getInstanceConfig($name);
```

`getInstanceConfig()` 回傳對應名稱的設定陣列。設定中必定要包含 `driver`（或以 `$driverKey` 指定的 key）。

### 解析流程

```mermaid theme={null}
flowchart TD
    A["呼叫 instance(name)"] --> B["若 name 為 null 則<br>使用 getDefaultInstance()"]
    B --> C{instances[] 內<br>是否已有快取?}
    C -- 是 --> D["回傳已快取的<br>實例"]
    C -- 否 --> E["以 getInstanceConfig(name) 取得設定"]
    E --> F{設定內是否<br>有 driver 鍵?}
    F -- 否 --> G["拋出 RuntimeException"]
    F -- 是 --> H{customCreators[]<br>是否已註冊?}
    H -- 是 --> I["以 callCustomCreator(config) 產生"]
    H -- 否 --> J["呼叫 createXxxDriver(config)"]
    I --> K["於 instances[] 快取並回傳"]
    J --> K
```

### 自製套件的實作範例

以同時對應多個 SMS Gateway 的套件為例。

```php theme={null}
namespace App\Sms;

use Illuminate\Support\MultipleInstanceManager;

class SmsManager extends MultipleInstanceManager
{
    // 預設實例名稱
    public function getDefaultInstance(): string
    {
        return $this->config->get('sms.default', 'primary');
    }

    public function setDefaultInstance($name): void
    {
        $this->config->set('sms.default', $name);
    }

    // 回傳對應實例名稱的設定
    public function getInstanceConfig($name): array
    {
        return $this->config->get("sms.gateways.{$name}");
    }

    // twilio Driver 的產生（傳入設定陣列）
    protected function createTwilioDriver(array $config): TwilioSmsGateway
    {
        return new TwilioSmsGateway(
            $config['account_sid'],
            $config['auth_token'],
        );
    }

    // vonage Driver 的產生
    protected function createVonageDriver(array $config): VonageSmsGateway
    {
        return new VonageSmsGateway(
            $config['api_key'],
            $config['api_secret'],
        );
    }
}
```

對應的設定檔：

```php theme={null}
// config/sms.php
return [
    'default' => 'primary',

    'gateways' => [
        'primary' => [
            'driver' => 'twilio',
            'account_sid' => env('TWILIO_ACCOUNT_SID'),
            'auth_token' => env('TWILIO_AUTH_TOKEN'),
        ],
        'backup' => [
            'driver' => 'vonage',
            'api_key' => env('VONAGE_API_KEY'),
            'api_secret' => env('VONAGE_API_SECRET'),
        ],
        'marketing' => [
            'driver' => 'twilio',
            'account_sid' => env('TWILIO_MARKETING_SID'),
            'auth_token' => env('TWILIO_MARKETING_TOKEN'),
        ],
    ],
];
```

使用時以 `instance()` 指定名稱。

```php theme={null}
$sms = app(SmsManager::class);

// 使用預設實例（primary = twilio）
$sms->send('+886901234xxx', '驗證碼：123456');

// 以名稱指定實例
$sms->instance('backup')->send('+886901234xxx', '驗證碼：123456');

// 使用行銷用實例
$sms->instance('marketing')->send('+886901234xxx', '活動通知');
```

<Tip>
  `MultipleInstanceManager` 與 `Manager` 相同，可以 `extend()` 加入自訂 Driver。亦實作了 `__call`，實例本身不存在的方法會委派給預設實例。
</Tip>

## Driver 快取的管理

### Manager 的快取操作

```php theme={null}
// 取得所有 Driver
$drivers = $manager->getDrivers();

// 清除所有 Driver 的快取
$manager->forgetDrivers();
```

### MultipleInstanceManager 的快取操作

```php theme={null}
// 刪除特定實例
$manager->forgetInstance('backup');

// 刪除預設實例
$manager->forgetInstance();

// 一次刪除多個實例
$manager->forgetInstance(['primary', 'backup']);

// 刪除實例並清除快取
$manager->purge('backup');
```

## 總結

* `Manager` 管理 Driver 的「種類」。實作 `createXxxDriver()` 方法，並以 `extend()` 擴充
* `MultipleInstanceManager` 依名稱管理 Driver 的「實例」。適合需要多個連線設定的套件
* 名為 `Manager` 的類別並非全部繼承 `Illuminate\Support\Manager`。`CacheManager` 與 `QueueManager` 為自行實作

<Card title="Macroable trait" icon="puzzle-piece" href="/zh-TW/advanced/macroable">
  學習為既有類別新增自訂方法的 Macroable trait 的用法。
</Card>


## Related topics

- [Laravel Socialite（社群認證）](/zh-TW/socialite.md)
- [Laravel Sentinel — 路由保護 Middleware 調查](/zh-TW/blog/sentinel-introduction.md)
- [圖片加工](/zh-TW/images.md)
- [自訂驗證 Guard 的實作](/zh-TW/advanced/custom-auth-guard.md)
- [從 Laravel 11 升級到 12 指南](/zh-TW/blog/upgrade-11-to-12.md)
