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

# Datenbankverbindungen in Paketen erweitern

> Analysiert DB::extend und Connection::resolverFor in Laravel 13 und erläutert die Priorität von Verbindungs- und Treibernamen, vorgehaltene Verbindungen, die Neuerzeugung sowie Tests für Pakete.

Wenn ein Paket Datenbankverbindungen erweitern soll, trennen Sie **die Registrierung der Erzeugungslogik** von **der Auswahl der genutzten Verbindung**. Die bloße Installation sollte weder die Standardverbindung der Anwendung noch sämtliche MySQL-Verbindungen ersetzen.

Diese Seite basiert auf dem offiziellen Quellcode von Laravel Framework `v13.40.0`. Die Grundlagen der Verbindungskonfiguration finden Sie unter [Datenbankkonfiguration](https://kawax.biz/de/database.md), das Laden von Providern unter [Paket-Discovery](https://kawax.biz/de/advanced/package-discovery.md).

## Den Erweiterungspunkt wählen

Für die Erzeugung von Datenbankverbindungen gibt es Erweiterungspunkte mit unterschiedlicher Reichweite.

| Erweiterungspunkt | Geltungsbereich | Argumente des Callbacks |
| - | - | - |
| `DB::extend('Verbindungsname', $resolver)` | Die Verbindung mit dem angegebenen Namen | Konfigurations-Array, Verbindungsname |
| `DB::extend('Treibername', $resolver)` | Verbindungen, die diesen Treiber nutzen. Eine Erweiterung für den Verbindungsnamen hat jedoch Vorrang | Konfigurations-Array, Verbindungsname |
| `Connection::resolverFor('sqlite', $resolver)` | Connections dieses Treibers, die von der Standard-ConnectionFactory erzeugt werden | PDO oder Closure zur Erzeugung, DB-Name, Tabellenpräfix, Konfigurations-Array |

Der Rückgabewert des an `DB::extend()` übergebenen Callbacks ist weder ein PDO noch ein Query Builder, sondern eine `Illuminate\Database\Connection`. `DatabaseManager::configure()` setzt diese Klasse voraus.

> [!WARNING]
>
> `extend()` der Datenbank folgt nicht derselben Aufrufkonvention wie das gemeinsame `Illuminate\Support\Manager`. Das erste Argument des Callbacks ist nicht der Anwendungscontainer, sondern **das Konfigurations-Array der Verbindung**. Übernehmen Sie Beispiele für andere Treibererweiterungen nicht unverändert.

## Erweiterungen für Verbindungsnamen haben Vorrang vor Treibernamen

`DatabaseManager::makeConnection()` liest die Konfiguration und wählt die Erzeugungslogik in folgender Reihenfolge.

```mermaid theme={null}
flowchart TD
    A["Verbindungsname mit DB::connection angeben"] --> B{"Bereits erzeugte Verbindung vorhanden?"}
    B -->|Ja| C["Vorgehaltene Connection zurückgeben"]
    B -->|Nein| D["Verbindungskonfiguration lesen und URL-Konfiguration parsen"]
    D --> E{"Erweiterung für den Verbindungsnamen vorhanden?"}
    E -->|Ja| F["Resolver des Verbindungsnamens ausführen"]
    E -->|Nein| G{"Erweiterung für den driver vorhanden?"}
    G -->|Ja| H["Resolver des driver ausführen"]
    G -->|Nein| I["Mit der Standard-ConnectionFactory erzeugen"]
    F --> J["Events, Reconnect-Verhalten usw. setzen und vorhalten"]
    H --> J
    I --> J
```

* `DB::extend('reporting', ...)` gilt für `database.connections.reporting`.
* `DB::extend('mysql', ...)` gilt für Verbindungen mit dem `driver` `mysql`, für deren Verbindungsnamen keine Erweiterung existiert.
* Beide nutzen dieselbe Erweiterungsregistrierung. Berücksichtigen Sie auch Kollisionen zwischen Verbindungs- und Treibernamen und verwenden Sie paketspezifische Namen.
* `extend()` erzeugt kein Konfigurations-Array. Wenn Sie nur die Erweiterung registrieren und keine Verbindungskonfiguration unter dem angegebenen Namen existiert, wird eine Ausnahme ausgelöst.

## Einen eigenen Treibernamen explizit auswählen

Das folgende Minimalbeispiel leitet den eigenen Namen `acme-sqlite` an die Erzeugung einer Standard-SQLite-Verbindung weiter. **Es implementiert weder ein neues DB-Protokoll noch einen SQL-Dialekt**. Es zeigt die Grenze zwischen Registrierung und Auswahl, wenn Sie einen eigenen Adapter ausliefern.

Angenommen wird eine Struktur, in der `Acme\DatabaseBridge\` per Composer-PSR-4 auf `src/` abgebildet wird; die Datei liegt unter `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` ist das von Laravel geteilte Binding der `ConnectionFactory`. Da die Standard-Factory direkt aufgerufen wird, kehrt der Ablauf nicht zur Erweiterungsauswahl des DatabaseManager zurück, und die verzögerte PDO-Erzeugung sowie die Connection-Erzeugung für SQLite lassen sich wiederverwenden.

Dieser Callback wird beim ersten Auflösen der Verbindung ausgeführt. `boot()` des Providers registriert nur die Erzeugungslogik und führt weder Verbindungstests noch Migrationen aus. Laden Sie ihn als gewöhnlichen, nicht verzögerten Provider, bevor die Zielverbindung zum ersten Mal aufgelöst wird.

Die nutzende Anwendung fügt die Verbindung dem bestehenden `connections`-Array in `config/database.php` hinzu. Der Standardwert `default` bleibt unverändert.

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

Legen Sie die SQLite-Datei vorab an und prüfen Sie die Schreibrechte, bevor Sie sie nutzen. Gestalten Sie das Paket nicht so, dass allein das Laden eine Datei erzeugt.

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

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

Nutzer übergeben den Verbindungsnamen `acme_reports`, nicht den Treibernamen `acme-sqlite`. Da das Beispiel `driver` vor der Übergabe an die Factory in `sqlite` umwandelt, liefert auch `getDriverName()` nach der Erzeugung `sqlite`.

> [!TIP]
>
> Wenn Sie innerhalb des Resolvers erneut `DB::connection($name)` aufrufen, landen Sie wieder bei der Erzeugung derselben, noch nicht vorgehaltenen Verbindung. Erzeugen Sie die Connection mit der Factory oder einem eigenen Adapter, um Rekursion zu vermeiden.

## Der Austausch durch eine Connection-Unterklasse hat eine andere Reichweite

Um die Connection-Klasse eines bestehenden Treibers selbst zu ersetzen, gibt es `Connection::resolverFor()`. Dem Callback wird nicht unbedingt ein bereits verbundenes PDO übergeben, **sondern unter Umständen eine Closure, die das PDO verzögert erzeugt**.

Diese Registrierung wird jedoch in einer statischen Eigenschaft der Connection-Klasse gehalten. Eine Registrierung für `sqlite` wirkt sich auf alle neu von der Standard-Factory erzeugten SQLite-Verbindungen aus, einschließlich des obigen Beispiels `acme-sqlite`. Sie lässt sich nicht als Austausch für nur einen einzelnen Verbindungsnamen verwenden.

* Geben Sie im Callback eine Connection-Unterklasse für die Ziel-DB zurück. Mit der Basis-Connection allein bleiben DB-spezifische Grammar- oder Processor-Klassen nicht erhalten.
* Die Factory trennt `createConnector()` und `createConnection()`. Eine Registrierung mit `resolverFor()` allein stellt keinen PDO-Connector für einen unbekannten Treiber bereit.
* Wenn ein eigener Resolver von `DB::extend()` die Standard-Factory nicht nutzt, wird auch die Prüfung von `resolverFor()` innerhalb der Factory nicht durchlaufen.
* Eine erneute Registrierung für denselben Treiber überschreibt die vorherige. Prüfen Sie Konflikte mit anderen Paketen und das Übertragen in andere Tests innerhalb desselben PHP-Prozesses.

Pakete, die ihre Auswirkungen begrenzen möchten, sollten zunächst `DB::extend()` mit eigenen Verbindungs- oder Treibernamen in Betracht ziehen.

## reconnect und purge unterscheiden

Der DatabaseManager hält erzeugte Connections pro Verbindungsname vor. Änderungen an Konfiguration oder Resolver werden nicht automatisch auf bereits erzeugte Verbindungen übertragen.

| Operation | Auswirkung auf vorgehaltene Connections |
| - | - |
| `DB::extend()` | Registriert oder überschreibt die nächste Erzeugungslogik. Bestehende Objekte bleiben unverändert |
| `DB::disconnect($name)` | Trennt das PDO, behält die Connection aber vor |
| `DB::reconnect($name)` | Falls vorgehalten, werden PDO und Ähnliches aus einer neu erzeugten Verbindung übertragen. Gibt die ursprüngliche Connection zurück |
| `DB::purge($name)` | Trennt die Verbindung und entfernt sie aus dem Manager. Beim nächsten `connection()` wird sie neu erzeugt |
| `DB::forgetExtension($key)` | Entfernt die Registrierung des Resolvers. Konfiguration und erzeugte Verbindungen bleiben bestehen |

Insbesondere ist `reconnect()` keine API, die Klasse oder Konfigurations-Array einer vorgehaltenen Connection vollständig ersetzt. Wenn Änderungen an der Erweiterungsklasse oder der Verbindungskonfiguration wirksam werden sollen, rufen Sie zu einem sicheren Zeitpunkt ohne laufende Transaktion `purge()` auf und holen danach eine neue Verbindung.

> [!WARNING]
>
> `purge()` ersetzt keine Connection-Referenzen, die andere Services oder Variablen halten, durch die neue Verbindung. Außerdem verwenden die Verbindungsnamen mit `::read`, `::write` und `::direct` eigene Schlüssel. Gehen Sie nicht davon aus, dass ein purge des Basisnamens diese ebenfalls gesammelt entfernt.
>
> Schreiben Sie geteilte Verbindungskonfigurationen nicht leichtfertig zur Laufzeit um, etwa um zwischen Mandanten zu wechseln. Prüfen Sie bei langlebigen Workern nicht nur die Aktualisierung des Konfigurations-Caches, sondern auch den Neustart des Prozesses und die Auswirkungen auf vorgehaltene Services.

## Registrierung und Erzeugung des Pakets testen

Mit [Orchestra Testbench](https://kawax.biz/de/advanced/package-testing.md) laden Sie den Provider und wählen den eigenen Treiber tatsächlich aus. Es folgt ein Beispiel für `tests/DatabaseBridgeTest.php`. Die Ausführungsumgebung benötigt PDO und den SQLite-Treiber.

```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);
    }
}
```

Da der Inhalt von `:memory:` beim Trennen der Verbindung verloren geht, erwartet dieser Test nicht, dass Tabellen oder Daten erhalten bleiben. In einem echten Paket ergänzen Sie außerdem:

* Dass der Resolver des Verbindungsnamens Vorrang vor dem Resolver des Treibernamens hat.
* Dass eine erneute Registrierung unter demselben Namen und `forgetExtension()` erzeugte Connections nicht ersetzen.
* Ausnahmen bei fehlender Pflichtkonfiguration und das Verhalten bei Verbindungsfehlern.
* Zugesagte Funktionen auf den unterstützten Datenbanken, etwa Migrationen, Transaktionen und read/write-Trennung.
* Dass sich Registrierungen und Verbindungszustände bei langlebigen Workern oder im Zusammenspiel mit mehreren Paketen nicht vermischen.

## Prüfpunkte für die Wartung

* Wird über einen eigenen Namen per Opt-in aktiviert, ohne die Standardverbindung oder eingebaute Treiber stillschweigend zu überschreiben?
* Landen keine Closures oder Verbindungsobjekte im Konfigurations-Cache, und liegt die Erzeugungslogik im Provider?
* Werden Verbindungsziele und Zugangsdaten als vertrauenswürdige Anwendungskonfiguration verwaltet?
* Wurden die Argumente von `extend()`, die Erzeugungsreihenfolge der Factory und die von `reconnect()` übertragenen Elemente für die unterstützten Laravel-Versionen erneut geprüft?
* Werden Nutzer über das Vorgehen zur Neuerzeugung nach Konfigurationsänderungen und den Neustart von Workern informiert?

## Verwandte Seiten

* [Paketentwicklung](https://kawax.biz/de/advanced/package-development.md)
* [Lebenszyklus von Paket-Services gestalten](https://kawax.biz/de/advanced/package-service-lifetimes.md)
* [Zusammenführen und Cachen von Paketkonfigurationen](https://kawax.biz/de/advanced/package-config-merging.md)
* [Veröffentlichen und Aktualisieren von Paket-Migrationen](https://kawax.biz/de/advanced/package-migrations.md)

## Herangezogene Primärquellen

* [Laravel offiziell: Datenbank und mehrere Verbindungen](https://github.com/laravel/docs/blob/13.x/database.md#using-multiple-database-connections)
* [Laravel offiziell: Service Provider für Pakete](https://github.com/laravel/docs/blob/13.x/packages.md#service-providers)
* [DatabaseManager: Erzeugungsreihenfolge, Erweiterungsregistrierung, Vorhalten und Neuverbinden von Verbindungen](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Database/DatabaseManager.php)
* [ConnectionFactory: Verzögerte PDO-Erzeugung und Connection-Resolver](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Database/Connectors/ConnectionFactory.php)
* [Connection: resolverFor und getResolver](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Database/Connection.php)
* [DatabaseServiceProvider: Registrierung von db und db.factory](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Database/DatabaseServiceProvider.php)


## Related topics

- [Log-Channels in Paketen erweitern und neu auflösen](https://kawax.biz/de/advanced/package-logging.md)
- [Cache](https://kawax.biz/de/cache.md)
- [Hooks nach der Service-Auflösung in Paketen](https://kawax.biz/de/advanced/package-service-resolution.md)
- [Eigene Benachrichtigungskanäle in Paketen und Prüfung der Zustellung](https://kawax.biz/de/advanced/package-notification-channels.md)
- [Laravel-Paketentwicklung](https://kawax.biz/de/advanced/package-development.md)


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