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

# Eigene Session-Treiber in Paketen

> Analysiert den SessionManager von Laravel 13 und erläutert den Speichervertrag von SessionHandlerInterface, die explizite Treiberauswahl, TTL und Serialisierung, vorgehaltene Stores sowie Tests.

Wenn ein Paket einen eigenen Session-Speicher bereitstellen soll, trennen Sie **die Registrierung der Speicherlogik** von **der Auswahl durch die Anwendung**. Die bloße Installation sollte den Speicherort des Login-Zustands nicht umstellen. Gestalten Sie das Paket so, dass Nutzer die Auswirkungen auf bestehende Sessions prüfen können, bevor sie es aktivieren.

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

## extend erhält einen Handler, keinen Store

Der Callback von `Session::extend()` gibt einen **Speicher-Handler** zurück, der PHPs `SessionHandlerInterface` implementiert. Es handelt sich nicht um eine API, die einen Store mit `Illuminate\Contracts\Session\Session` oder direkt ein Cache-Repository zurückgibt.

`SessionManager::callCustomCreator()` übergibt den Rückgabewert des Callbacks an `buildSession()`, das ihn je nach `session.encrypt` mit einem `Store` oder einem `EncryptedStore` umhüllt. Dabei werden auch der Cookie-Name und `session.serialization` angewendet.

```mermaid theme={null}
flowchart TD
    A["Eigenen Treibernamen in boot() registrieren"] --> B["Nutzer wählt session.driver"]
    B --> C["Erster driver()-Aufruf führt den Erzeugungs-Callback aus"]
    C --> D["SessionHandlerInterface zurückgeben"]
    D --> E["SessionManager umhüllt ihn mit Store oder<br>EncryptedStore"]
    E --> F["StartSession beginnt mit dem Laden"]
    F --> G["Anwendung arbeitet z. B. mit put()"]
    G --> H["Store serialisiert und<br>übergibt an handler.write()"]
```

In der normalen HTTP-Verarbeitung verwaltet die Middleware `StartSession` den Start und das Speichern der Session. Um einen Handler bereitzustellen, muss Ihr Paket weder `session_start()` noch `session_set_save_handler()` aufrufen.

## Den Speichervertrag in Teilen betrachten

Wenn Sie ein eigenes Backend implementieren, erfüllen Sie die folgenden Aufgaben.

| Methode | Verantwortung des Handlers |
| - | - |
| `open($savePath, $sessionName)` | Nötige Initialisierung. Falls nicht nötig, `true` zurückgeben |
| `close()` | Nötige Abschlussarbeiten. Falls nicht nötig, `true` zurückgeben |
| `read($sessionId)` | Gespeicherten String zurückgeben. Bei fehlenden oder abgelaufenen Daten einen leeren String zurückgeben |
| `write($sessionId, $data)` | Übergebenen String speichern und Erfolg oder Fehlschlag zurückgeben |
| `destroy($sessionId)` | Daten der angegebenen ID löschen |
| `gc($lifetime)` | Abgelaufene Daten anhand einer Lebensdauer in Sekunden löschen. Entfällt, wenn der Speicher Daten automatisch ablaufen lässt |

Da der `Store` für Serialisierung und Wiederherstellung zuständig ist, darf der Handler das Session-Array nicht erneut mit `serialize()` oder `json_encode()` verarbeiten. Auch bei aktivierter Verschlüsselung speichern Sie den von `EncryptedStore` verarbeiteten String unverändert.

> [!WARNING]
>
> Unterscheiden Sie fehlende Daten von Backend-Fehlern. Im geprüften Code behandelt `Store::readFromHandler()` ein leeres Leseergebnis oder `false` als leere Session. Außerdem prüft `Store::save()` den Rückgabewert von `write()` des Handlers nicht.
>
> Soll Ihr Paket Fehler stets als Exceptions behandeln, legen Sie die Strategie ausdrücklich fest und testen Sie sie: Unterdrücken Sie keine Exceptions des Speichers, und erkennen Sie bei Backends, die `false` zurückgeben, dies im Handler und wandeln Sie es in eine Exception um. Laravel wiederholt fehlgeschlagene Speichervorgänge nicht zwingend automatisch und meldet sie auch nicht zwingend.

## In boot nur die Erzeugungslogik registrieren

Das folgende Minimalbeispiel verwendet den eingebauten `CacheBasedSessionHandler` wieder. Es implementiert kein neues Speicherprotokoll, sondern zeigt, wie Sie einen eigenen Treiber registrieren, der einen benannten Cache-Store nutzt. `Acme\SessionBridge\` ist per Composer-PSR-4 auf `src/` abgebildet.

Legen Sie den Provider in `src/SessionBridgeServiceProvider.php` an.

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

namespace Acme\SessionBridge;

use Illuminate\Contracts\Container\Container;
use Illuminate\Session\CacheBasedSessionHandler;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\ServiceProvider;
use InvalidArgumentException;
use SessionHandlerInterface;

final class SessionBridgeServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Session::extend('acme-cache', function (Container $app): SessionHandlerInterface {
            $config = $app->make('config');
            $store = $config->get('session.store');
            $minutes = (int) $config->get('session.lifetime', 120);

            if (! is_string($store) || $store === '' || $minutes <= 0) {
                throw new InvalidArgumentException(
                    'acme-cache requires a named cache store and a positive session lifetime.'
                );
            }

            return new CacheBasedSessionHandler(
                $app->make('cache')->store($store),
                $minutes,
            );
        });
    }
}
```

`Session::extend()` holt den SessionManager und registriert die Erzeugungslogik, der Cache-Store im Callback wird zu diesem Zeitpunkt aber noch nicht aufgelöst. Der Speicher wird erst erzeugt, wenn der Treiber zum ersten Mal ausgewählt wird. Führen Sie im `boot()` des Providers keine Verbindungsprüfungen und keine Speichervorgänge aus.

Wenn Sie eine eigene Implementierung von `SessionHandlerInterface` verwenden, tauschen Sie den Handler aus, den der Callback zurückgibt. Gehen Sie bis zu `new Store(...)`, passt der Rückgabewert nicht mehr zu dem, was der SessionManager erwartet.

* Laden Sie den Provider als normalen, nicht verzögerten Provider.
* Schließen Sie die Registrierung ab, bevor die Session zum ersten Mal verwendet wird. Wenn ein anderer Provider Ihren Treiber früher auflöst, prüfen Sie auch die Ladereihenfolge.
* Verwenden Sie einen paketspezifischen Treibernamen. Ein `extend()` mit demselben Namen überschreibt die Erzeugungslogik, und die Registrierung eines eingebauten Treibernamens hat Vorrang vor der eingebauten Implementierung.

## Die nutzende Anwendung wählt explizit aus

Nutzer ändern die betreffenden Schlüssel in ihrer bestehenden `config/session.php` und lassen die übrigen Einstellungen unverändert.

```php theme={null}
'driver' => 'acme-cache',
'store' => 'acme-sessions',
```

Für den Speicher ergänzen Sie einen benannten Store im bestehenden Array `stores` von `config/cache.php`. Das folgende Beispiel nutzt die Redis-Verbindung `default`. [Die Redis-Verbindungseinstellungen und den benötigten Client](https://kawax.biz/de/redis.md) stellt die Anwendung bereit.

```php theme={null}
'acme-sessions' => [
    'driver' => 'redis',
    'connection' => 'default',
    'prefix' => 'acme-sessions:',
],
```

Verbindung und Präfix ergeben sich in diesem Beispiel aus der **Konfiguration des Cache-Stores**. Da `createRedisDriver()` des eingebauten Redis-Session-Treibers nicht durchlaufen wird, übernimmt dieser Callback `session.connection` oder `session.prefix` nicht automatisch, auch wenn Sie sie ergänzen.

Dieser Callback verändert weder das Repository noch die Speicherkonfiguration, sondern nutzt den benannten Store unverändert. Auch ein `array`-Store für Tests teilt innerhalb derselben Anwendung denselben Speicherbereich.

Überschreiben Sie `session.driver`, den Cookie-Namen oder das anwendungsweite Cache-Präfix nicht bedingungslos aus Ihrem Paket heraus. Klären Sie mit den Nutzern auch, dass der Speicherbereich nicht mit anderen Zwecken geteilt wird und dass mehrere Server denselben Speicher nutzen können.

> [!TIP]
>
> Wenn Sie nur gewöhnliche Speicherung in Redis oder einer Datenbank benötigen, verwenden Sie zunächst die eingebauten Session-Treiber. Ein eigener Treiber ist ein Erweiterungspunkt für Fälle, in denen Sie einen neuen Speicher oder zusätzliche Speicherkonventionen brauchen.

## TTL-Einheit und Speicherformat beibehalten

Der Konstruktor von `CacheBasedSessionHandler` erhält die Lebensdauer in **Minuten**. `write()` übergibt bei jedem Aufruf `$minutes * 60` an `put()` des Cache-Repositorys, sodass die TTL im obigen Beispiel bei jedem Schreibvorgang erneuert wird. `gc()` gibt `0` zurück und überlässt den Ablauf dem Cache.

Die Lebensdauer, die `StartSession` an `gc($lifetime)` des Handlers übergibt, ist dagegen in **Sekunden** angegeben. Implementieren Sie nichts, das den Wert von `session.lifetime` unverändert als Sekunden speichert.

Die folgenden Änderungen sind außerdem mit mehr als einer Umbenennung der Einstellung verbunden.

| Änderung | Auswirkung auf bestehende Sessions |
| - | - |
| Speicher, Verbindung oder Schlüsselpräfix | Alte Daten sind im neuen Speicher nicht mehr sichtbar |
| `session.serialization` | Daten im alten Format lassen sich im neuen Format nicht mehr wiederherstellen |
| `session.encrypt` | Lesen und Schreiben von Klartext und Chiffretext passen nicht mehr zusammen |
| `session.cookie` | Die Zuordnung zum vorhandenen Session-ID-Cookie, das der Browser sendet, ändert sich |

Wenn ein Paket-Update eines dieser Elemente ändert, berücksichtigen Sie Hinweise zur erneuten Anmeldung, eine mögliche Migration und Deployments, in denen alte und neue Versionen parallel laufen. Wie bei der [Versionskompatibilität von Paketen](https://kawax.biz/de/advanced/package-versioning.md) zählen auch Speicherformat und Konfigurationsschlüssel zur öffentlichen Spezifikation, die Sie pflegen.

## Eine Konfigurationsänderung allein ersetzt einen erzeugten Store nicht

`Manager`, die Elternklasse von SessionManager, hält erzeugte Treiber nach Namen vor. Zusätzlich registriert `SessionServiceProvider` `session.store` als Singleton.

| Vorgang | Was passiert | Was nicht passiert |
| - | - | - |
| Nur Konfigurationswerte ändern | Künftige Erzeugungen können die neuen Werte lesen | TTL oder Verbindung eines bereits erzeugten Handlers ändern sich |
| `extend()` unter demselben Namen erneut registrieren | Der Erzeugungs-Callback wird ersetzt | Der unter diesem Namen vorgehaltene Store wird verworfen |
| `forgetDrivers()` | Alle im Manager vorgehaltenen Treiber werden entfernt | Der aufgelöste `session.store` oder extern gehaltene Store-Referenzen werden ersetzt |

`forgetDrivers()` ist auch keine API zum Löschen von Session-Daten. Wird der Store während einer laufenden Anfrage neu erzeugt, entstehen Inkonsistenzen mit bereits geladenen Daten und anderen Referenzen.

Auch der CacheManager in diesem Beispiel hält benannte Stores vor. Ändern Sie deren Verbindungskonfiguration, reicht es nicht, nur den SessionManager neu aufzulösen.

Bei Deployments mit Konfigurations-Cache bauen Sie den Cache neu auf und starten langlebige Worker neu, damit sie den neuen Code und die neue Konfiguration laden. Der Neustart der Anwendung und die Migration gespeicherter Daten sind getrennte Aufgaben.

## Session-Speicherung und Request-Locking sind getrennt

Allein durch die Implementierung eines eigenen Handlers werden parallele Anfragen an dieselbe Session nicht serialisiert. Das Blocking von `StartSession` nutzt Locks des Cache-Stores, der über `session.block_store` gewählt wird. Dieser ist nicht zwingend derselbe wie der Speicher der Session selbst.

Wenn Sie dies etwa mit `Route::post(...)->block()` steuern, prüfen Sie separat einen Lock-fähigen Cache-Store, die gemeinsame Nutzung über mehrere Server sowie Warte- und Haltezeiten. Ihr Paket sollte anwendungsweite Blocking-Einstellungen nicht eigenmächtig aktivieren.

## Bis zum Speichern und erneuten Laden testen

In Paketen mit [Orchestra Testbench](https://kawax.biz/de/advanced/package-testing.md) laden Sie den Provider und schreiben Tests, die Ihren eigenen Treiber tatsächlich auswählen. Das folgende Beispiel zeigt `tests/SessionDriverTest.php`. Der `array`-Cache für Tests nutzt keine externe Verbindung.

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

namespace Acme\SessionBridge\Tests;

use Acme\SessionBridge\SessionBridgeServiceProvider;
use Illuminate\Session\CacheBasedSessionHandler;
use Illuminate\Session\Store;
use Orchestra\Testbench\TestCase;

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

    protected function defineEnvironment($app): void
    {
        $app['config']->set('session.driver', 'acme-cache');
        $app['config']->set('session.store', 'session-test');
        $app['config']->set('session.lifetime', 2);
        $app['config']->set('session.cookie', 'acme_session');
        $app['config']->set('session.serialization', 'json');
        $app['config']->set('session.encrypt', false);
        $app['config']->set('cache.stores.session-test', ['driver' => 'array']);
    }

    public function test_it_wraps_the_registered_handler(): void
    {
        $store = $this->app->make('session')->driver();

        $this->assertInstanceOf(Store::class, $store);
        $this->assertInstanceOf(CacheBasedSessionHandler::class, $store->getHandler());
        $this->assertSame($store, $this->app->make('session.store'));
    }

    public function test_it_saves_and_reloads_the_payload(): void
    {
        $manager = $this->app->make('session');
        $first = $manager->driver();
        $first->start();
        $first->put('cart.quantity', 3);
        $first->save();

        $id = $first->getId();
        $payload = $first->getHandler()->read($id);
        $this->assertSame(3, json_decode($payload, true)['cart']['quantity']);

        $manager->forgetDrivers();
        $second = $manager->driver();
        $second->setId($id);
        $second->start();

        $this->assertNotSame($first, $second);
        $this->assertSame(3, $second->get('cart.quantity'));

        $second->invalidate();
        $this->assertSame('', $second->getHandler()->read($id));
    }

    public function test_forgetting_drivers_does_not_replace_the_store_binding(): void
    {
        $old = $this->app->make('session.store');
        $manager = $this->app->make('session');

        $this->app['config']->set('session.lifetime', 5);
        $manager->forgetDrivers();

        $this->assertNotSame($old, $manager->driver());
        $this->assertSame($old, $this->app->make('session.store'));
    }
}
```

Dieser Test ist ein **isolierter Test**, der den Store innerhalb derselben Anwendung neu erzeugt. Er prüft weder HTTP-Middleware noch den Cookie-Hin- und Rückweg, die Persistenz über Prozessgrenzen hinweg oder den tatsächlichen Ablauf und das Locking in Redis. Verwenden Sie `array` nicht als gemeinsamen Speicher in Produktion.

Bevor Sie einen eigenen Handler veröffentlichen, ergänzen Sie außerdem Folgendes.

* Nicht gespeicherte oder abgelaufene IDs liefern einen leeren String
* Ein gespeicherter String wird unverändert wiederhergestellt und ist nach dem Löschen nicht mehr abrufbar
* TTL-Einheit und Erneuerung sowie der Ablauf im echten Backend
* Fehler beim Lesen, Schreiben und Löschen führen zu den festgelegten Rückgabewerten oder Exceptions
* Hin- und Rückweg mit aktiviertem `session.encrypt` sowie die unterstützten Serialisierungsformate
* Das bloße Laden des Providers greift nicht auf den Speicher zu und übernimmt nicht die Auswahl eingebauter Treiber
* Bei Bedarf HTTP-Tests über die echte `web`-Middleware und Cookies sowie Locking bei parallelen Anfragen

## Verwandte Seiten

> [card] [Session](https://kawax.biz/de/session.md)
>
> Lesen und Schreiben von Sessions, Flash-Daten und das Neugenerieren von IDs.

> [card] [Interne Implementierung von Manager](https://kawax.biz/de/advanced/manager.md)
>
> Erzeugung und Vorhalten eigener Treiber sowie die Delegation an den Standardtreiber.

> [card] [Eigene Cache-Stores in Paketen](https://kawax.biz/de/advanced/package-cache-drivers.md)
>
> Aufgabenteilung zwischen Cache-Store und Repository als Speicher sowie der TTL-Vertrag.

> [card] [Lebenszyklus von Paketdiensten](https://kawax.biz/de/advanced/package-service-lifetimes.md)
>
> Von langlebigen Workern vorgehaltene Abhängigkeiten und die Isolation von Zustand.

## Herangezogene Primärquellen

* [Offizielle HTTP-Session-Dokumentation: eigene Treiber und Blocking](https://github.com/laravel/docs/blob/625d1dfb61018e6bc50cc7ab67fca89ae018dfa2/session.md)
* [Offizielle Dokumentation zur Paketentwicklung](https://github.com/laravel/docs/blob/625d1dfb61018e6bc50cc7ab67fca89ae018dfa2/packages.md)
* [SessionManager: eigene Erzeugung und Aufbau des Stores](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Session/SessionManager.php)
* [Store: Laden, Serialisierung, Speichern und Neugenerieren von IDs](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Session/Store.php)
* [CacheBasedSessionHandler: TTL-Umrechnung und Speichervertrag](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Session/CacheBasedSessionHandler.php)
* [CacheManager: benannte Stores und Redis-Konfiguration](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Cache/CacheManager.php)
* [Manager: extend, driver, forgetDrivers](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Support/Manager.php)
* [SessionServiceProvider: Singleton-Registrierung von Manager und session.store](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Session/SessionServiceProvider.php)
* [StartSession: Speichern, GC und Locking über den Cache](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Session/Middleware/StartSession.php)
* [Offizieller CacheBasedSessionHandlerTest: Prüfung fehlender Daten, TTL und Löschen](https://github.com/laravel/framework/blob/v13.39.0/tests/Session/CacheBasedSessionHandlerTest.php)


## Related topics

- [Session](https://kawax.biz/de/session.md)
- [Eigene Cache-Stores und Verträge in Paketen](https://kawax.biz/de/advanced/package-cache-drivers.md)
- [Storage-Treiber in Paketen registrieren und neu auflösen](https://kawax.biz/de/advanced/package-filesystem-drivers.md)
- [Log-Channels in Paketen erweitern und neu auflösen](https://kawax.biz/de/advanced/package-logging.md)
- [Core-Paket und eigene Treiber - Feedable](https://kawax.biz/de/packages/feedable/core.md)


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