Sessions

HTTP est un protocole sans état ; presque toutes les applications ont pourtant besoin de conserver un état entre les requêtes, par exemple le contenu d'un panier d'achat. C'est exactement à cela que servent les sessions. Nous allons montrer :

  • comment utiliser les sessions
  • comment éviter les conflits de noms
  • comment définir l'expiration

Lors de l'utilisation des sessions, chaque utilisateur reçoit un identifiant unique appelé ID de session, transmis dans un cookie. Il sert de clé vers les données de la session. Contrairement aux cookies, qui sont stockés du côté du navigateur, les données de session sont stockées du côté du serveur.

Nous configurons les sessions dans la configuration ; le choix de la durée d'expiration est particulièrement important.

La gestion des sessions est assurée par l'objet Nette\Http\Session, auquel vous accédez en vous le faisant passer par injection de dépendances. Dans les presenters, il suffit d'appeler $session = $this->getSession().

Installation et prérequis

Démarrer la session

Par défaut, Nette démarre automatiquement une session dès que nous commençons à y lire ou à y écrire des données. Pour démarrer une session manuellement, utilisez $session->start().

PHP envoie au démarrage de la session des en-têtes HTTP influençant la mise en cache (voir session_cache_limiter), et éventuellement un cookie contenant l'ID de session. Il est donc toujours nécessaire de démarrer la session avant d'envoyer la moindre sortie au navigateur ; sinon, une exception sera levée. Si vous savez qu'une session sera utilisée pendant le rendu de la page, démarrez-la donc manuellement au préalable, par exemple dans le presenter.

En mode développement, Tracy démarre la session, car elle s'en sert pour afficher dans la Tracy Bar les barres relatives aux redirections et aux requêtes AJAX.

Sections

En PHP pur, le stockage des données de session est implémenté comme un tableau accessible via la variable globale $_SESSION. Le problème est que les applications se composent généralement de nombreuses parties indépendantes et que, si toutes ne disposent que d'un seul tableau, une collision de noms finira tôt ou tard par se produire.

Nette Framework résout ce problème en divisant tout l'espace en sections (objets Nette\Http\SessionSection). Chaque unité utilise alors sa propre section portant un nom unique, et aucune collision ne peut se produire.

Nous obtenons une section depuis la session :

$section = $session->getSection('nom unique');

Dans le presenter, il suffit d'utiliser getSession() avec un paramètre :

// $this est un Presenter
$section = $this->getSession('nom unique');

L'existence d'une section peut être vérifiée à l'aide de la méthode $session->hasSection('nom unique'). La liste des noms de toutes les sections existantes est renvoyée par $session->getSectionNames().

Le travail avec la section elle-même est ensuite très simple grâce aux méthodes set(), get() et remove() :

// écriture d'une variable
$section->set('userName', 'john');

// lecture d'une variable, renvoie null si elle n'existe pas
echo $section->get('userName');

// suppression d'une variable
$section->remove('userName');

Pour obtenir toutes les variables d'une section, vous pouvez utiliser une boucle foreach :

foreach ($section as $key => $val) {
	echo "$key = $val";
}

Comment définir l'expiration

L'expiration peut être définie pour chaque section, voire pour chaque variable. Nous pouvons faire expirer la connexion d'un utilisateur au bout de 20 minutes tout en continuant de mémoriser le contenu du panier.

// la section expire au bout de 20 minutes
$section->setExpiration('20 minutes');

Pour définir l'expiration de variables individuelles, utilisez le troisième paramètre de la méthode set() :

// la variable 'flash' expire au bout de 30 secondes
$section->set('flash', $message, '30 seconds');

Rappelez-vous que la durée d'expiration de la session entière (voir la configuration des sessions) doit être égale ou supérieure à la durée définie pour les sections ou variables individuelles.

Pour annuler une expiration définie précédemment, utilisez la méthode removeExpiration() ; pour effacer l'expiration d'une variable précise, passez son nom : removeExpiration('flash'). Pour supprimer immédiatement toute la section, utilisez la méthode remove().

Événements $onStart, $onBeforeWrite

L'objet Nette\Http\Session possède les événements $onStart et $onBeforeWrite, vous pouvez donc ajouter des callbacks invoqués après le démarrage de la session ou avant qu'elle ne soit écrite sur le disque puis terminée.

$session->onBeforeWrite[] = function () {
	// écriture de données dans la session
	$this->section->set('basket', $this->basket);
};

Gestion de la session

Aperçu des méthodes de la classe Nette\Http\Session pour la gestion des sessions :

start(): void

Démarre la session.

isStarted(): bool

La session est-elle démarrée ?

close(): void

Termine la session. La session se termine automatiquement à la fin de l'exécution du script.

destroy(): void

Termine et supprime la session.

exists(): bool

La requête HTTP contient-elle un cookie avec un ID de session ?

regenerateId(): void

Génère un nouvel ID de session aléatoire. Les données sont conservées.

getId(): string

Renvoie l'ID de session.

Configuration

Nous configurons la session dans la configuration. Si vous écrivez une application qui n'utilise pas de conteneur DI, utilisez ces méthodes pour la configurer. Elles doivent être appelées avant le démarrage de la session.

setName (string $name): static

Définit le nom du cookie dans lequel l'ID de session est transmis. Le nom standard est PHPSESSID. C'est utile si vous faites tourner plusieurs applications différentes sur le même site.

getName(): string

Renvoie le nom du cookie dans lequel l'ID de session est transmis.

setOptions (array $options)static

Configure la session. Il est possible de définir toutes les directives de session de PHP (au format camelCase, par exemple écrire savePath au lieu de session.save_path) ainsi que readAndClose.

setExpiration (?string $expire)static

Définit la durée d'inactivité au bout de laquelle la session expire.

setCookieParameters (string $path, ?string $domain=null, ?bool $secure=null, SameSite|string|null $samesite=null)static

Définit les paramètres des cookies. Vous pouvez changer les valeurs par défaut des paramètres dans la configuration.

setSavePath (string $path)static

Définit le répertoire où sont stockés les fichiers de session.

setHandler (\SessionHandlerInterface $handler)static

Définit un handler personnalisé, voir la documentation PHP.

La sécurité avant tout

Le serveur part du principe qu'il communique avec le même utilisateur tant que les requêtes sont accompagnées du même ID de session. Le rôle des mécanismes de sécurité est de garantir qu'il en va bien ainsi et que l'identifiant ne peut être ni volé ni substitué.

Nette Framework configure donc correctement les directives de PHP pour ne transmettre l'ID de session que dans les cookies, le rendre inaccessible au JavaScript et ignorer tout identifiant présent dans l'URL. De plus, aux moments critiques, comme la connexion de l'utilisateur, il génère un nouvel ID de session.

La fonction ini_set est utilisée pour configurer PHP, mais malheureusement certains hébergeurs en interdisent l'usage. Si c'est le cas du vôtre, essayez de convenir avec lui qu'il vous autorise cette fonction ou qu'il configure au moins correctement le serveur.

version: 4.x