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

# パッケージのデータベース接続拡張

> Laravel 13のDB::extendとConnection::resolverForを読み解き、接続名とドライバー名の優先順位、接続の保持、再生成とパッケージのテストを解説します。

パッケージからデータベース接続を拡張するには、**生成方法の登録**と**利用する接続の選択**を分けます。インストールしただけでアプリケーションの既定接続や、すべてのMySQL接続を置き換えない設計にします。

このページはLaravel Framework `v13.40.0` の公式実装を確認しています。接続設定の基本は[データベース設定](https://kawax.biz/jp/database.md)、プロバイダーの読み込みは[パッケージの自動検出](https://kawax.biz/jp/advanced/package-discovery.md)を参照してください。

## 拡張点を選ぶ

データベースの生成処理には、異なる範囲の拡張点があります。

| 拡張点 | 適用範囲 | コールバックの引数 |
| - | - | - |
| `DB::extend('接続名', $resolver)` | 指定した名前の接続 | 設定配列、接続名 |
| `DB::extend('ドライバー名', $resolver)` | そのドライバーを使う接続。ただし接続名の拡張が優先 | 設定配列、接続名 |
| `Connection::resolverFor('sqlite', $resolver)` | 標準ConnectionFactoryが生成する、そのドライバーのConnection | PDOまたは生成用Closure、DB名、テーブルプレフィックス、設定配列 |

`DB::extend()` に渡すコールバックの戻り値はPDOやQuery Builderではなく、`Illuminate\Database\Connection` です。`DatabaseManager::configure()` がこのクラスを要求します。

> [!WARNING]
>
> データベースの `extend()` は、共通の `Illuminate\Support\Manager` と同じ呼び出し規約ではありません。コールバックの第1引数はアプリケーションコンテナではなく、**接続設定の配列**です。ほかのドライバー拡張の例をそのまま流用しないでください。

## 接続名の拡張がドライバー名より優先される

`DatabaseManager::makeConnection()` は、設定を取得してから次の順で生成方法を選びます。

```mermaid theme={null}
flowchart TD
    A["DB::connectionで接続名を指定"] --> B{"生成済みの接続がある？"}
    B -->|ある| C["保持済みConnectionを返す"]
    B -->|ない| D["接続設定を取得・URL設定を解析"]
    D --> E{"接続名の拡張がある？"}
    E -->|ある| F["接続名のresolverを実行"]
    E -->|ない| G{"driverの拡張がある？"}
    G -->|ある| H["driverのresolverを実行"]
    G -->|ない| I["標準ConnectionFactoryで生成"]
    F --> J["イベント・再接続処理などを設定して保持"]
    H --> J
    I --> J
```

* `DB::extend('reporting', ...)` は、`database.connections.reporting` に対して適用されます。
* `DB::extend('mysql', ...)` は、接続名側に拡張がない、`driver` が `mysql` の接続へ適用されます。
* どちらも同じ拡張レジストリを使います。接続名とドライバー名の衝突も考慮して、パッケージ固有の名前を使います。
* `extend()` は設定配列を作りません。拡張だけ登録しても、指定した名前の接続設定がなければ例外になります。

## 独自ドライバー名を明示的に選ぶ

次は独自名 `acme-sqlite` を、標準SQLite接続の生成へ橋渡しする最小例です。**新しいDBプロトコルやSQL方言を実装する例ではありません**。独自アダプターを配布する際の、登録と選択の境界を示します。

`Acme\DatabaseBridge\` がComposerのPSR-4で `src/` に対応する構成を想定し、`src/DatabaseBridgeServiceProvider.php` に配置します。

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

namespace Acme\DatabaseBridge;

use Illuminate\Database\Connection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\ServiceProvider;

final class DatabaseBridgeServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $app = $this->app;

        DB::extend('acme-sqlite', function (array $config, string $name) use ($app): Connection {
            $config['driver'] = 'sqlite';

            return $app->make('db.factory')->make($config, $name);
        });
    }
}
```

`db.factory` はLaravelが共有する `ConnectionFactory` のバインディングです。標準Factoryを直接呼ぶため、DatabaseManagerの拡張選択に戻らず、SQLiteの遅延PDO生成とConnection生成を再利用できます。

このコールバックは初回の接続解決時に実行されます。プロバイダーの `boot()` は生成方法の登録だけを行い、接続テストやマイグレーションを実行しません。通常の非遅延プロバイダーとして、対象接続が初めて解決される前に読み込ませます。

利用アプリケーションは、`config/database.php` の既存の `connections` 配列へ接続を追加します。既定の `default` は変更しません。

```php theme={null}
'acme_reports' => [
    'driver' => 'acme-sqlite',
    'database' => database_path('reports.sqlite'),
    'prefix' => '',
    'foreign_key_constraints' => true,
],
```

SQLiteファイルを事前に用意し、書き込み権限を確認してから利用します。パッケージを読み込んだだけでファイルを作成する設計にはしません。

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

$connection = DB::connection('acme_reports');
$rows = $connection->select('select 1 as value');
```

利用者が渡すのは接続名 `acme_reports` であり、ドライバー名 `acme-sqlite` ではありません。例ではFactoryへ渡す前に `driver` を `sqlite` へ変換するため、生成後の `getDriverName()` も `sqlite` です。

> [!TIP]
>
> resolverの中から `DB::connection($name)` を呼び直すと、まだ保持されていない同じ接続の生成に戻ります。再帰を避けるため、Factoryや独自アダプターでConnectionを生成します。

## Connectionサブクラスへの置き換えは別の範囲

既存ドライバーのConnectionクラス自体を置き換える場合は、`Connection::resolverFor()` があります。コールバックには、即座に接続済みのPDOではなく、**PDOを遅延生成するClosureが渡る場合もあります**。

ただし、この登録はConnectionクラスのstaticプロパティに保持されます。`sqlite` に登録すれば、標準Factoryで新しく作るSQLite接続全体へ影響し、上の `acme-sqlite` の例も対象になります。接続名1つだけの置き換えとしては扱えません。

* コールバックでは対象DB用のConnectionサブクラスを返します。基底Connectionだけでは、DB固有のGrammarやProcessorなどを保てません。
* Factoryは `createConnector()` と `createConnection()` を分けています。`resolverFor()` の登録だけで、未知のドライバーのPDOコネクターまで提供されるわけではありません。
* `DB::extend()` の独自resolverが標準Factoryを使わなければ、Factory内の `resolverFor()` の確認も通りません。
* 同じドライバーへの再登録は上書きです。ほかのパッケージとの競合と、同一PHPプロセス内のテストへの持ち越しを確認します。

影響を限定したいパッケージは、まず固有の接続名・ドライバー名による `DB::extend()` を検討します。

## reconnectとpurgeを区別する

DatabaseManagerは、生成済みConnectionを接続名ごとに保持します。設定やresolverを変更しても、すでに生成された接続には自動反映されません。

| 操作 | 保持済みConnectionへの影響 |
| - | - |
| `DB::extend()` | 次回の生成処理を登録・上書きする。既存オブジェクトはそのまま |
| `DB::disconnect($name)` | PDOを切断するが、Connectionは保持する |
| `DB::reconnect($name)` | 保持済みなら、新しく生成した接続からPDO類を移す。元のConnectionを返す |
| `DB::purge($name)` | 切断してManagerの保持対象から除外する。次の `connection()` で再生成 |
| `DB::forgetExtension($key)` | resolverの登録を削除する。設定や生成済み接続は削除しない |

特に `reconnect()` は、保持済みConnectionのクラスや設定配列を丸ごと置き換えるAPIではありません。拡張クラスや接続設定の変更を反映したい場合は、処理中のトランザクションがない安全なタイミングで `purge()` した後、新しい接続を取得します。

> [!WARNING]
>
> `purge()` は、別のサービスや変数が保持しているConnection参照を新しいものに差し替えません。また、接続名の `::read`・`::write`・`::direct` は別の保持キーになるため、基底名をpurgeするだけで、それらも一括削除されるとは考えないでください。
>
> テナント切り替えなどを目的に、実行中の共有接続設定を安易に書き換えないでください。長寿命ワーカーでは、設定キャッシュの更新だけでなくプロセスの再起動と、保持済みサービスへの影響も確認します。

## パッケージの登録と生成をテストする

[Orchestra Testbench](https://kawax.biz/jp/advanced/package-testing.md)では、プロバイダーを読み込み、独自ドライバーを実際に選択します。次は `tests/DatabaseBridgeTest.php` の例です。実行環境にはPDOとSQLiteドライバーが必要です。

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

namespace Acme\DatabaseBridge\Tests;

use Acme\DatabaseBridge\DatabaseBridgeServiceProvider;
use Illuminate\Database\SQLiteConnection;
use Orchestra\Testbench\TestCase;

final class DatabaseBridgeTest extends TestCase
{
    protected function getPackageProviders($app): array
    {
        return [DatabaseBridgeServiceProvider::class];
    }

    protected function defineEnvironment($app): void
    {
        $app['config']->set('database.default', 'testing');
        $app['config']->set('database.connections.acme_reports', [
            'driver' => 'acme-sqlite',
            'database' => ':memory:',
            'prefix' => '',
        ]);
    }

    public function test_registration_does_not_create_connections(): void
    {
        $manager = $this->app->make('db');

        $this->assertSame('testing', $manager->getDefaultConnection());
        $this->assertArrayNotHasKey('acme_reports', $manager->getConnections());
    }

    public function test_connection_is_reused_and_purge_creates_a_new_one(): void
    {
        $manager = $this->app->make('db');
        $first = $manager->connection('acme_reports');

        $this->assertInstanceOf(SQLiteConnection::class, $first);
        $this->assertSame('acme_reports', $first->getName());
        $this->assertSame('sqlite', $first->getDriverName());
        $this->assertSame(1, (int) $first->selectOne('select 1 as value')->value);
        $this->assertSame($first, $manager->connection('acme_reports'));
        $this->assertSame($first, $manager->reconnect('acme_reports'));

        $manager->purge('acme_reports');

        $second = $manager->connection('acme_reports');

        $this->assertNotSame($first, $second);
        $this->assertSame(1, (int) $second->selectOne('select 1 as value')->value);
    }
}
```

`:memory:` は接続の切断で内容が失われるため、このテストはテーブルやデータの保持を期待しません。実際のパッケージでは、次も追加します。

* 接続名のresolverがドライバー名のresolverより優先されること。
* 同名の再登録と `forgetExtension()` が、生成済みConnectionを置き換えないこと。
* 必須設定が欠けた場合の例外と、接続失敗時の挙動。
* 対応DBでのマイグレーション、トランザクション、read/write分離など、提供を約束する機能。
* 長寿命ワーカーや複数パッケージとの併用で、登録や接続状態が混ざらないこと。

## 保守時の確認項目

* 独自名でオプトインし、既定接続や組み込みドライバーを黙って上書きしていないか。
* configキャッシュにClosureや接続オブジェクトを入れず、生成処理はプロバイダーに置いているか。
* 接続先や認証情報を信頼できるアプリケーション設定として管理しているか。
* `extend()` の引数、Factoryの生成順、`reconnect()` の転送対象をサポート対象のLaravelで再確認したか。
* 設定変更後の再生成手順とワーカー再起動を利用者へ案内しているか。

## 関連ページ

* [パッケージ開発](https://kawax.biz/jp/advanced/package-development.md)
* [パッケージサービスのライフサイクル設計](https://kawax.biz/jp/advanced/package-service-lifetimes.md)
* [パッケージ設定のマージとキャッシュ](https://kawax.biz/jp/advanced/package-config-merging.md)
* [パッケージのマイグレーション公開と更新](https://kawax.biz/jp/advanced/package-migrations.md)

## 参照した一次情報

* [Laravel公式:データベースと複数接続](https://github.com/laravel/docs/blob/13.x/database.md#using-multiple-database-connections)
* [Laravel公式:パッケージのサービスプロバイダー](https://github.com/laravel/docs/blob/13.x/packages.md#service-providers)
* [DatabaseManager:生成順、拡張登録、接続の保持と再接続](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Database/DatabaseManager.php)
* [ConnectionFactory:PDOの遅延生成とConnection resolver](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Database/Connectors/ConnectionFactory.php)
* [Connection:resolverForとgetResolver](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Database/Connection.php)
* [DatabaseServiceProvider:dbとdb.factoryの登録](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Database/DatabaseServiceProvider.php)


## Related topics

- [データベース設定](https://kawax.biz/jp/database.md)
- [応用トピック](https://kawax.biz/jp/advanced/index.md)
- [並行処理](https://kawax.biz/jp/concurrency.md)
- [MongoDB](https://kawax.biz/jp/mongodb.md)
- [Laravel Boost](https://kawax.biz/jp/boost.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.