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

# Drivers de session personnalisés pour les packages

> Analysez le SessionManager de Laravel 13 pour comprendre le contrat de stockage de SessionHandlerInterface, la sélection explicite du driver, le TTL et la sérialisation, les Store conservés et les tests.

Pour fournir une destination de stockage de session propre depuis un package, séparez **l'enregistrement du stockage** de **la sélection par l'application**. Le simple fait d'installer le package ne doit pas changer l'endroit où l'état de connexion est stocké : concevez-le pour que l'utilisateur puisse l'activer après avoir évalué l'impact sur les sessions existantes.

Cette page a été vérifiée sur les sources officielles de Laravel Framework `v13.39.0`. Pour les opérations de base, consultez [Session](https://kawax.biz/fr/session.md), et pour le chargement automatique des providers, consultez [Découverte automatique des packages](https://kawax.biz/fr/advanced/package-discovery.md).

## extend reçoit un handler, pas un Store

Le callback de `Session::extend()` retourne un **handler de stockage** qui implémente le `SessionHandlerInterface` de PHP. Ce n'est pas une API qui retourne directement un Store implémentant `Illuminate\Contracts\Session\Session` ou un Repository de cache.

`SessionManager::callCustomCreator()` transmet la valeur de retour du callback à `buildSession()`, qui l'enveloppe dans un `Store` ou un `EncryptedStore` selon `session.encrypt`. Le nom du cookie et `session.serialization` sont appliqués à ce moment-là.

```mermaid theme={null}
flowchart TD
    A["Enregistrer le nom du driver dans boot()"] --> B["L'utilisateur choisit session.driver"]
    B --> C["Le premier driver() exécute le callback de création"]
    C --> D["Retourner un SessionHandlerInterface"]
    D --> E["SessionManager l'enveloppe dans<br>un Store ou un EncryptedStore"]
    E --> F["StartSession lance la lecture"]
    F --> G["L'application manipule la session avec put(), etc."]
    G --> H["Le Store sérialise et<br>transmet à handler.write()"]
```

Dans le traitement HTTP normal, le middleware `StartSession` gère le démarrage et l'enregistrement de la session. Pour fournir un handler, le package n'a pas besoin d'appeler `session_start()` ni `session_set_save_handler()`.

## Distinguer les éléments du contrat de stockage

Pour implémenter un backend propre, remplissez les rôles suivants.

| Méthode | Responsabilité du handler |
| - | - |
| `open($savePath, $sessionName)` | Initialisation nécessaire. Retourner `true` si aucune n'est requise |
| `close()` | Finalisation nécessaire. Retourner `true` si aucune n'est requise |
| `read($sessionId)` | Retourner la chaîne stockée. Retourner une chaîne vide si rien n'est stocké ou si les données ont expiré |
| `write($sessionId, $data)` | Stocker la chaîne reçue et retourner le succès ou l'échec |
| `destroy($sessionId)` | Supprimer les données de l'ID indiqué |
| `gc($lifetime)` | Supprimer les données expirées selon une durée de vie en secondes. Inutile si le stockage expire automatiquement |

Comme le `Store` se charge de la sérialisation et de la restauration, le handler ne doit pas appliquer à nouveau `serialize()` ou `json_encode()` au tableau de session. Lorsque le chiffrement est activé, stockez tel quel la chaîne traitée par `EncryptedStore`.

> [!WARNING]
>
> Distinguez l'absence de données d'une panne du backend. Le `Store::readFromHandler()` examiné traite un résultat de lecture vide ou `false` comme une session vide. De plus, `Store::save()` ne vérifie pas la valeur de retour du `write()` du handler.
>
> Si votre package doit toujours traiter les pannes comme des exceptions, explicitez cette politique et testez-la : ne masquez pas les exceptions du stockage, et pour les backends qui retournent `false`, détectez-le dans le handler et convertissez-le en exception. Laravel ne réessaie pas forcément automatiquement et ne signale pas forcément l'échec de l'enregistrement.

## N'enregistrer que la création dans boot

L'exemple minimal suivant réutilise le `CacheBasedSessionHandler` intégré. Il n'implémente pas de nouveau protocole de stockage : il montre comment enregistrer un driver propre qui utilise un store de cache nommé. `Acme\SessionBridge\` correspond à `src/` via le PSR-4 de Composer.

Préparez le provider dans `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()` récupère le SessionManager et enregistre la création, mais le store de cache du callback n'est pas encore résolu à ce stade. Le stockage n'est créé que lorsque ce driver est sélectionné pour la première fois. N'exécutez ni vérification de connexion ni écriture de données dans le `boot()` du provider.

Si vous utilisez votre propre implémentation de `SessionHandlerInterface`, remplacez le handler retourné par le callback. Aller jusqu'à `new Store(...)` ne correspond plus à la valeur de retour attendue par le SessionManager.

* Chargez le provider comme un provider normal, non différé.
* Terminez l'enregistrement avant la première utilisation de la session. Si un autre provider résout votre driver plus tôt, vérifiez aussi l'ordre de chargement.
* Utilisez un nom de driver propre au package. Un `extend()` portant le même nom écrase la création, et l'enregistrement d'un nom de driver intégré prend le pas sur l'implémentation intégrée.

## L'application utilisatrice fait un choix explicite

L'utilisateur modifie les clés correspondantes de son `config/session.php` existant. Les autres réglages restent en place.

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

Pour le stockage, ajoutez un store nommé au tableau `stores` existant de `config/cache.php`. L'exemple suivant utilise la connexion Redis `default`. [La configuration de la connexion Redis et le client nécessaire](https://kawax.biz/fr/redis.md) sont préparés par l'application.

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

La connexion et le préfixe de cet exemple sont déterminés par **la configuration du store de cache**. Comme le chemin ne passe pas par le `createRedisDriver()` du driver de session Redis intégré, ajouter `session.connection` ou `session.prefix` ne sera pas automatiquement pris en compte par ce callback.

Ce callback ne modifie ni le Repository ni la configuration du stockage : il utilise le store nommé tel quel. Le store `array` de test partage lui aussi le même espace de stockage au sein d'une même application.

Le package ne doit pas réécrire inconditionnellement `session.driver`, le nom du cookie ou le préfixe de cache global de l'application. Vérifiez aussi avec l'utilisateur que l'espace de stockage n'est pas partagé avec d'autres usages et que plusieurs serveurs peuvent utiliser le même stockage.

> [!TIP]
>
> Si votre seul objectif est un stockage Redis ou base de données classique, commencez par les drivers de session intégrés. Un driver propre est un point d'extension pour les cas où un nouveau stockage ou des règles de stockage supplémentaires sont nécessaires.

## Préserver l'unité du TTL et le format de stockage

Le constructeur de `CacheBasedSessionHandler` reçoit la durée de conservation en **minutes**. Comme `write()` transmet à chaque fois `$minutes * 60` au `put()` du Repository de cache, l'exemple ci-dessus renouvelle le TTL à chaque écriture. `gc()` retourne `0` et délègue l'expiration au cache.

En revanche, la durée que `StartSession` transmet au `gc($lifetime)` du handler est en **secondes**. N'implémentez pas un stockage qui utilise directement la valeur de `session.lifetime` comme des secondes.

Par ailleurs, les changements suivants ne se limitent pas à un renommage de configuration.

| Changement | Impact sur les sessions existantes |
| - | - |
| Stockage, connexion, préfixe des clés | Les anciennes données ne sont plus visibles depuis le nouveau stockage |
| `session.serialization` | Les données à l'ancien format ne peuvent plus être restaurées avec le nouveau format |
| `session.encrypt` | La lecture et l'écriture en clair et chiffrées ne correspondent plus |
| `session.cookie` | La correspondance avec le cookie d'ID de session existant envoyé par le navigateur change |

Si une mise à jour du package modifie ces éléments, prévoyez des instructions de réauthentification, décidez s'il faut une migration, et envisagez les déploiements où l'ancienne et la nouvelle version coexistent. Comme dans [Gestion de la compatibilité de versions de package](https://kawax.biz/fr/advanced/package-versioning.md), incluez le format de stockage et les clés de configuration dans la spécification publique que vous maintenez.

## Un Store déjà créé n'est pas remplacé par un simple changement de configuration

`Manager`, la classe parente du SessionManager, conserve les drivers créés par nom. De plus, `SessionServiceProvider` enregistre `session.store` comme singleton.

| Opération | Ce qui se passe | Ce qui ne se passe pas |
| - | - | - |
| Modifier seulement la configuration | Les créations futures peuvent lire les nouvelles valeurs | La modification du TTL ou de la connexion du handler déjà créé |
| Réenregistrer `extend()` sous le même nom | Remplacement du callback de création | La destruction du Store conservé sous ce nom |
| `forgetDrivers()` | Suppression de tous les drivers conservés dans le Manager | Le remplacement du `session.store` résolu ou des références au Store conservées ailleurs |

`forgetDrivers()` n'est pas non plus une API de suppression des données de session. Recréer le Store pendant une requête en cours provoque des incohérences avec les données déjà lues ou avec d'autres références.

Le CacheManager de cet exemple conserve lui aussi les stores nommés. Si vous modifiez leur configuration de connexion, re-résoudre seulement le SessionManager ne suffit pas.

Dans les déploiements qui utilisent le cache de configuration, reconstruisez le cache, et redémarrez les workers longue durée pour qu'ils chargent le nouveau code et la nouvelle configuration. Le redémarrage de l'application et la migration des données stockées sont deux tâches distinctes.

## Le stockage de session et le verrouillage des requêtes sont distincts

Implémenter un handler personnalisé ne suffit pas à sérialiser les requêtes parallèles sur une même session. Le blocage de `StartSession` utilise les verrous du store de cache choisi avec `session.block_store`, qui n'est pas forcément le même que le stockage de la session elle-même.

Si vous contrôlez cela avec `Route::post(...)->block()` ou similaire, vérifiez séparément un store de cache prenant en charge les verrous, le partage entre plusieurs serveurs, ainsi que les durées d'attente et de détention. Le package ne doit pas activer de lui-même le blocage pour toute l'application.

## Tester jusqu'à l'enregistrement et la relecture

Pour un package qui utilise [Orchestra Testbench](https://kawax.biz/fr/advanced/package-testing.md), chargez le provider et préparez des tests qui sélectionnent réellement votre driver. Voici un exemple de `tests/SessionDriverTest.php`. Le cache `array` de test n'utilise aucune connexion externe.

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

Ce test est un **test isolé** qui recrée le Store au sein d'une même application. Il ne vérifie ni le middleware HTTP, ni l'aller-retour des cookies, ni la persistance vers un autre processus, ni l'expiration ou les verrous d'un vrai Redis. N'utilisez pas `array` comme stockage partagé en production.

Avant de publier un handler propre, ajoutez aussi les points suivants.

* Un ID non stocké ou expiré retourne une chaîne vide
* La chaîne stockée est restaurée sans modification et ne peut plus être récupérée après suppression
* L'unité et le renouvellement du TTL, et l'expiration sur le vrai backend
* Les échecs de lecture, d'écriture et de suppression produisent les valeurs de retour ou exceptions définies
* L'aller-retour avec `session.encrypt` activé et les formats de sérialisation pris en charge
* Le simple chargement du provider n'accède pas au stockage et ne détourne pas la sélection des drivers intégrés
* Si nécessaire, des tests HTTP passant par le vrai middleware `web` et les cookies, ainsi que le verrouillage des requêtes parallèles

## Pages connexes

> [card] [Session](https://kawax.biz/fr/session.md)
>
> Lecture et écriture de session, données flash et régénération de l'ID.

> [card] [Implémentation interne de Manager](https://kawax.biz/fr/advanced/manager.md)
>
> Création et conservation des drivers personnalisés, et délégation au driver par défaut.

> [card] [Stores de cache personnalisés pour les packages](https://kawax.biz/fr/advanced/package-cache-drivers.md)
>
> Répartition des rôles entre le Store de cache servant de stockage et le Repository, et contrat du TTL.

> [card] [Cycle de vie des services d'un package](https://kawax.biz/fr/advanced/package-service-lifetimes.md)
>
> Dépendances conservées par les workers longue durée et isolation de l'état.

## Sources primaires

* [Documentation officielle HTTP Session : drivers personnalisés et blocage](https://github.com/laravel/docs/blob/625d1dfb61018e6bc50cc7ab67fca89ae018dfa2/session.md)
* [Documentation officielle du développement de packages](https://github.com/laravel/docs/blob/625d1dfb61018e6bc50cc7ab67fca89ae018dfa2/packages.md)
* [SessionManager : création personnalisée et construction du Store](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Session/SessionManager.php)
* [Store : lecture, sérialisation, enregistrement et régénération de l'ID](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Session/Store.php)
* [CacheBasedSessionHandler : conversion du TTL et contrat de stockage](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Session/CacheBasedSessionHandler.php)
* [CacheManager : stores nommés et configuration Redis](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 : enregistrement singleton du Manager et de session.store](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Session/SessionServiceProvider.php)
* [StartSession : enregistrement, GC et verrouillage par le cache](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Session/Middleware/StartSession.php)
* [CacheBasedSessionHandlerTest officiel : vérification des données absentes, du TTL et de la suppression](https://github.com/laravel/framework/blob/v13.39.0/tests/Session/CacheBasedSessionHandlerTest.php)


## Related topics

- [Stores de cache personnalisés et contrats pour les packages](https://kawax.biz/fr/advanced/package-cache-drivers.md)
- [Extension des connexions de base de données pour les packages](https://kawax.biz/fr/advanced/package-database-connections.md)
- [Agents personnalisés](https://kawax.biz/fr/packages/laravel-copilot-sdk/custom-agents.md)
- [Enregistrer des drivers Storage dans un package et les résoudre à nouveau](https://kawax.biz/fr/advanced/package-filesystem-drivers.md)
- [Hooks de session](https://kawax.biz/fr/packages/laravel-copilot-sdk/hooks.md)


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