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

# Eigen sessiedrivers voor packages

> Ontleed de SessionManager van Laravel 13 en leer het opslagcontract van SessionHandlerInterface, expliciete driverselectie, TTL en serialisatie, vastgehouden Stores en testen kennen.

Wanneer je vanuit een package een eigen opslaglocatie voor sessies wilt aanbieden, scheid je **het registreren van de opslag** van **de selectie door de applicatie**. Alleen het installeren van de package mag niet veranderen waar de inlogstatus wordt opgeslagen: ontwerp het zo dat gebruikers het pas inschakelen nadat ze de impact op bestaande sessies hebben beoordeeld.

Deze pagina is gecontroleerd aan de hand van de officiële broncode van Laravel Framework `v13.39.0`. Zie [Sessies](https://kawax.biz/nl/session.md) voor de basisbediening en [Package discovery](https://kawax.biz/nl/advanced/package-discovery.md) voor het automatisch laden van providers.

## extend ontvangt een handler, geen Store

De callback van `Session::extend()` retourneert een **opslaghandler** die de PHP-`SessionHandlerInterface` implementeert. Het is geen API die rechtstreeks een Store retourneert die `Illuminate\Contracts\Session\Session` implementeert, of een cache-Repository.

`SessionManager::callCustomCreator()` geeft de retourwaarde van de callback door aan `buildSession()`, dat deze afhankelijk van `session.encrypt` in een `Store` of `EncryptedStore` verpakt. Daarbij worden ook de cookienaam en `session.serialization` toegepast.

```mermaid theme={null}
flowchart TD
    A["Eigen drivernaam registreren in boot()"] --> B["Gebruiker kiest session.driver"]
    B --> C["Eerste driver() voert de aanmaakcallback uit"]
    C --> D["SessionHandlerInterface retourneren"]
    D --> E["SessionManager verpakt in<br>Store of EncryptedStore"]
    E --> F["StartSession start het inlezen"]
    F --> G["Applicatie werkt met put() enz."]
    G --> H["Store serialiseert en<br>geeft door aan handler.write()"]
```

Bij normale HTTP-verwerking beheert de middleware `StartSession` het starten en opslaan van de sessie. Om een handler aan te bieden hoeft de package `session_start()` of `session_set_save_handler()` niet aan te roepen.

## Bekijk het opslagcontract per onderdeel

Wanneer je een eigen backend implementeert, vervul je de volgende rollen.

| Methode | Verantwoordelijkheid van de handler |
| - | - |
| `open($savePath, $sessionName)` | Noodzakelijke initialisatie. Retourneer `true` als die niet nodig is |
| `close()` | Noodzakelijke afronding. Retourneer `true` als die niet nodig is |
| `read($sessionId)` | Retourneer de opgeslagen string. Retourneer een lege string als er niets is opgeslagen of de data verlopen is |
| `write($sessionId, $data)` | Sla de ontvangen string op en retourneer succes of mislukking |
| `destroy($sessionId)` | Verwijder de data van het opgegeven ID |
| `gc($lifetime)` | Verwijder verlopen data op basis van een levensduur in seconden. Niet nodig als de opslag zelf automatisch laat verlopen |

Omdat de `Store` verantwoordelijk is voor serialisatie en herstel, pas je in de handler niet nogmaals `serialize()` of `json_encode()` toe op de sessie-array. Ook als versleuteling is ingeschakeld, sla je de door `EncryptedStore` verwerkte string ongewijzigd op.

> [!WARNING]
>
> Maak onderscheid tussen ontbrekende data en een storing in de backend. De onderzochte `Store::readFromHandler()` behandelt een leeg leesresultaat of `false` als een lege sessie. Bovendien controleert `Store::save()` de retourwaarde van `write()` van de handler niet.
>
> Moet je package storingen altijd als exceptions behandelen, leg dat beleid dan expliciet vast en test het: slik exceptions van de opslag niet in, en detecteer bij backends die `false` retourneren dit in de handler en zet het om in een exception. Laravel probeert het niet per se automatisch opnieuw en meldt een mislukte opslag niet per se.

## Registreer in boot alleen het aanmaken

Het volgende minimale voorbeeld hergebruikt de ingebouwde `CacheBasedSessionHandler`. Het implementeert geen nieuw opslagprotocol, maar laat zien hoe je een eigen driver registreert die een benoemde cachestore gebruikt. `Acme\SessionBridge\` is via Composer-PSR-4 gekoppeld aan `src/`.

Maak de provider aan in `src/SessionBridgeServiceProvider.php`.

```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()` haalt de SessionManager op en registreert het aanmaken, maar de cachestore in de callback wordt op dat moment nog niet geresolved. De opslag wordt pas aangemaakt wanneer de driver voor het eerst wordt gekozen. Voer in de `boot()` van de provider geen verbindingscontroles of dataopslag uit.

Gebruik je een eigen implementatie van `SessionHandlerInterface`, vervang dan de handler die de callback retourneert. Ga je zo ver dat je `new Store(...)` aanmaakt, dan past dat niet meer bij de retourwaarde die de SessionManager verwacht.

* Laad de provider als een gewone, niet-uitgestelde provider.
* Rond de registratie af voordat de sessie voor het eerst wordt gebruikt. Als een andere provider je driver eerder resolvet, controleer dan ook de laadvolgorde.
* Gebruik een packagespecifieke drivernaam. Een `extend()` met dezelfde naam overschrijft het aanmaken, en wie een ingebouwde drivernaam registreert, krijgt voorrang op de ingebouwde implementatie.

## De gebruikende applicatie kiest expliciet

De gebruiker past de betreffende keys in de bestaande `config/session.php` aan. De overige instellingen blijven staan.

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

Voeg voor de opslag een benoemde store toe aan de bestaande `stores`-array in `config/cache.php`. Het volgende voorbeeld gebruikt de Redis-verbinding `default`. [De Redis-verbindingsinstellingen en de benodigde client](https://kawax.biz/nl/redis.md) regelt de applicatie zelf.

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

De verbinding en prefix in dit voorbeeld worden bepaald door **de configuratie van de cachestore**. Omdat `createRedisDriver()` van de ingebouwde Redis-sessiedriver niet wordt doorlopen, neemt deze callback `session.connection` of `session.prefix` niet automatisch over als je die toevoegt.

Deze callback wijzigt de Repository en de opslagconfiguratie niet, maar gebruikt de benoemde store zoals die is. Ook de `array`-store voor tests deelt binnen dezelfde applicatie dezelfde opslagruimte.

Laat de package `session.driver`, de cookienaam of de applicatiebrede cacheprefix niet onvoorwaardelijk overschrijven. Stem ook met de gebruiker af dat de opslagruimte niet met andere toepassingen wordt gedeeld en dat meerdere servers dezelfde opslag kunnen gebruiken.

> [!TIP]
>
> Is je enige doel gewone opslag in Redis of een database, begin dan met de ingebouwde sessiedrivers. Een eigen driver is een uitbreidingspunt voor situaties waarin een nieuwe opslaglocatie of aanvullende opslagregels nodig zijn.

## Behoud de TTL-eenheid en het opslagformaat

De constructor van `CacheBasedSessionHandler` ontvangt de bewaarduur in **minuten**. Omdat `write()` telkens `$minutes * 60` doorgeeft aan `put()` van de cache-Repository, wordt de TTL in het voorbeeld hierboven bij elke schrijfactie vernieuwd. `gc()` retourneert `0` en laat het verlopen over aan de cache.

De duur die `StartSession` aan `gc($lifetime)` van de handler doorgeeft, is daarentegen in **seconden**. Implementeer geen opslag die de waarde van `session.lifetime` rechtstreeks als seconden gebruikt.

Daarnaast zijn de volgende wijzigingen niet af te doen met alleen een andere instellingsnaam.

| Wijziging | Impact op bestaande sessies |
| - | - |
| Opslaglocatie, verbinding, keyprefix | Oude data is niet meer zichtbaar vanuit de nieuwe opslag |
| `session.serialization` | Data in het oude formaat kan niet meer met het nieuwe formaat worden hersteld |
| `session.encrypt` | Lezen en schrijven van platte tekst en versleutelde data sluiten niet meer op elkaar aan |
| `session.cookie` | De koppeling met de bestaande sessie-ID-cookie die de browser meestuurt, verandert |

Verandert een package-update een van deze zaken, overweeg dan instructies voor opnieuw inloggen, of er een migratie nodig is, en deployments waarin oude en nieuwe versies naast elkaar draaien. Net als bij [Versiecompatibiliteit van packages beheren](https://kawax.biz/nl/advanced/package-versioning.md) horen het opslagformaat en de configuratiekeys bij de publieke specificatie die je onderhoudt.

## Een aangemaakte Store wordt niet vervangen door alleen de configuratie te wijzigen

`Manager`, de bovenliggende klasse van de SessionManager, houdt aangemaakte drivers per naam vast. Daarnaast registreert `SessionServiceProvider` `session.store` als singleton.

| Actie | Wat er gebeurt | Wat er niet gebeurt |
| - | - | - |
| Alleen configuratiewaarden wijzigen | Toekomstige aanmaak kan de nieuwe waarden lezen | Wijziging van de TTL of verbinding van een al aangemaakte handler |
| `extend()` opnieuw registreren onder dezelfde naam | Vervanging van de aanmaakcallback | Verwijdering van de onder die naam vastgehouden Store |
| `forgetDrivers()` | Verwijdering van alle drivers die de Manager intern vasthoudt | Vervanging van de al geresolvede `session.store` of van elders vastgehouden Store-referenties |

`forgetDrivers()` is ook geen API om sessiedata te verwijderen. Een Store opnieuw aanmaken tijdens een lopende request leidt tot inconsistenties met al ingelezen data of andere referenties.

De CacheManager in dit voorbeeld houdt ook benoemde stores vast. Wijzig je de verbindingsinstellingen daarvan, dan is alleen de SessionManager opnieuw resolven niet genoeg.

Bouw bij deployments met configuratiecache de cache opnieuw op, en herstart ook langlopende workers zodat ze de nieuwe code en configuratie inlezen. Het herstarten van de applicatie en het migreren van opgeslagen data zijn afzonderlijke taken.

## Sessieopslag en request-locking staan los van elkaar

Alleen een eigen handler implementeren zorgt er niet voor dat parallelle requests op dezelfde sessie na elkaar worden verwerkt. De blokkering van `StartSession` gebruikt locks van de cachestore die je met `session.block_store` kiest, en dat is niet per se dezelfde als de opslag van de sessie zelf.

Regel je dit met bijvoorbeeld `Route::post(...)->block()`, verifieer dan afzonderlijk een cachestore met lockondersteuning, het delen over meerdere servers en de wacht- en vasthoudtijden. Laat de package niet op eigen houtje de blokkering voor de hele applicatie inschakelen.

## Test tot en met opslaan en opnieuw inlezen

Laad bij packages die [Orchestra Testbench](https://kawax.biz/nl/advanced/package-testing.md) gebruiken de provider en schrijf tests die je eigen driver daadwerkelijk selecteren. Hieronder volgt een voorbeeld van `tests/SessionDriverTest.php`. De `array`-cache voor tests gebruikt geen externe verbindingen.

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

Deze test is een **geïsoleerde test** die de Store binnen dezelfde applicatie opnieuw aanmaakt. Hij verifieert niet de HTTP-middleware, de heen-en-weerreis van cookies, persistentie naar een ander proces of het verlopen en locken in een echte Redis. Gebruik `array` niet als gedeelde opslag in productie.

Voeg vóór het publiceren van een eigen handler ook het volgende toe.

* Een niet-opgeslagen of verlopen ID retourneert een lege string
* De opgeslagen string wordt ongewijzigd hersteld en is na verwijdering niet meer op te halen
* De eenheid en vernieuwing van de TTL, en het verlopen op de echte backend
* Mislukt lezen, schrijven of verwijderen levert de vastgelegde retourwaarden of exceptions op
* De heen-en-weerreis met `session.encrypt` ingeschakeld en de ondersteunde serialisatieformaten
* Alleen het laden van de provider benadert de opslag niet en neemt de selectie van ingebouwde drivers niet over
* Indien nodig HTTP-tests via de echte `web`-middleware en cookies, en locking van parallelle requests

## Gerelateerde pagina's

> [card] [Sessies](https://kawax.biz/nl/session.md)
>
> Sessies lezen en schrijven, flashdata en het opnieuw genereren van het ID.

> [card] [Interne implementatie van Manager](https://kawax.biz/nl/advanced/manager.md)
>
> Het aanmaken en vasthouden van eigen drivers en het delegeren naar de standaarddriver.

> [card] [Eigen cachestores voor packages](https://kawax.biz/nl/advanced/package-cache-drivers.md)
>
> De rolverdeling tussen de cache-Store als opslag en de Repository, en het TTL-contract.

> [card] [Levensduur van packageservices](https://kawax.biz/nl/advanced/package-service-lifetimes.md)
>
> Afhankelijkheden die langlopende workers vasthouden en het isoleren van state.

## Primaire bronnen

* [Officiële HTTP Session-documentatie: eigen drivers en blokkering](https://github.com/laravel/docs/blob/625d1dfb61018e6bc50cc7ab67fca89ae018dfa2/session.md)
* [Officiële documentatie over packageontwikkeling](https://github.com/laravel/docs/blob/625d1dfb61018e6bc50cc7ab67fca89ae018dfa2/packages.md)
* [SessionManager: eigen aanmaak en opbouw van de Store](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Session/SessionManager.php)
* [Store: inlezen, serialisatie, opslaan en opnieuw genereren van het ID](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Session/Store.php)
* [CacheBasedSessionHandler: TTL-conversie en opslagcontract](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Session/CacheBasedSessionHandler.php)
* [CacheManager: benoemde stores en Redis-configuratie](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Cache/CacheManager.php)
* [Manager: extend, driver en forgetDrivers](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Support/Manager.php)
* [SessionServiceProvider: singletonregistratie van de Manager en session.store](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Session/SessionServiceProvider.php)
* [StartSession: opslaan, GC en locking via de cache](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Session/Middleware/StartSession.php)
* [Officiële CacheBasedSessionHandlerTest: verificatie van ontbrekende data, TTL en verwijdering](https://github.com/laravel/framework/blob/v13.39.0/tests/Session/CacheBasedSessionHandlerTest.php)


## Related topics

- [Eigen cachestores en contracten voor packages](https://kawax.biz/nl/advanced/package-cache-drivers.md)
- [Core-package en eigen drivers - Feedable](https://kawax.biz/nl/packages/feedable/core.md)
- [Mailtransports voor packages registreren en testen](https://kawax.biz/nl/advanced/package-mail-transports.md)
- [GeneratorCommand — eigen make: commando's implementeren](https://kawax.biz/nl/advanced/generator-command.md)
- [Laravel Package Skeleton — officiële startertemplate voor packages](https://kawax.biz/nl/blog/package-skeleton-introduction.md)


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