Questo articolo vuole essere un approfondimento su come avviene internamente la gestione della sessione in Laravel. Piuttosto che analizzare le API del Framework, che sono già ben documentate nel sito ufficiale, vedremo invece quali sono le principali classi coinvolte, la loro interazione e il loro ruolo nella gestione della sessione. L'articolo si basa su Laravel 5.1, che è l'attuale versione LTS.
Come premessa mi sembra importante precisare che Laravel non si avvale dei costrutti nativi di sessione di PHP (quindi, tanto per capirci, non utilizza session_start()
e le altre funzioni simili). La motivazione che sta alla base di questa scelta è indicata dallo stesso Taylor Otwell in un commento su GitHub.
Le Classi Coinvolte
Il seguente diagramma UML indica le classi coinvolte nella gestione della sessione e ne evidenzia le principali proprietà e metodi, utili a farne comprendere il funzionamento:
Descriviamo brevemente le classi presenti nel diagramma:
SessionServiceProvider : come ogni altro servizio, anche quello che gestisce la sessione ha un proprio Service Provider. Lo scopo principale di questa classe è creare un'istanza di SessionManager
e registrarla come singleton nel Service Container:
protected function registerSessionManager() { $this->app->singleton('session', function ($app) { return new SessionManager($app); }); }
Come vedremo successivamente, questo binding è importante perchè ogni volta che usiamo la Facade Session
, l'oggetto che viene risolto dal Service Contaier è proprio quello registrato tramite questo binding
SessionManager : in Laravel ci sono diverse classi che si occupano di coordinare la creazione e le operazioni svolte dai vari moduli, dei 'Manager' appunto. Alcuni tra i più conosciuti sono: AuthManager
, CacheManager
, FileSystemManager
e il nostro SessionManager
. Tutte queste classi estendono la classe astratta Manager
, che tramite la proprietà $drivers
mette a disposizione alle sottoclassi concrete un array per memorizzare diversi driver da usare per svolgere lo specifico compito. Nel caso del SessionManager
i principali drivers utilizzabili sono:
- File
- Cookie
- Database
Nell'esempio da cui è tratto il diagramma si suppone di aver scelto il driver File
( quello di default ) per la gestione della sessione. Possiamo impostare il driver da utilizzare dal file di configurazione della sessione: config\session.php
Session (Facade) : Per capire meglio quale sia il ruolo delle altre classi, possiamo seguire l'application flow da quando accediamo ad una chiave in sessione tramite la Facade Session
. Consideriamo un esempio in cui usiamo la Facade per memorizzare un valore in sessione :
Session::put( ‘my_value’, 100 );
Quando si usa la Facade Session
, la chiave risolta dal Service Container è 'session'
e, come abbiamo visto precedentemente, in SessionServiceProvider
viene fatto un binding per questa chiave che ritorna un oggetto SessionManager
, quindi la chiamata precedente si traduce in:
SessionManager::put( ‘my_value’, 100 );
Se analizziamo i metodi della classe SessionManager
però, non troviamo il metodo put
. Come sappiamo, quando si verifica la chiamata di un metodo non esistente, viene automaticamente chiamato il magic method __call
cioè SessionManager::__call
:
public function __call($method, $parameters) { return call_user_func_array( [ $this->driver(), $method ], $parameters ); }
La chiamata $this->driver()
sul SessionManager
crea e ritorna un oggetto Store
che, come vedremo in seguito, viene creato e configurato in base al driver scelto. Quindi l'istruzione si traduce in:
Store::put( ‘my_value’, 100 );
Store : Possiamo pensare che l'oggetto Store
sia usato come un contenitore 'temporaneo' per i dati di sessione: all'inizio della request cycle i dati di sessione vengono prelevati dallo storage di sessione e memorizzati nella proprietà Store::$attributtes
. Durante la request cycle, se i dati di sessione vengono letti (get
) o scritti (put
), gli accessi sono fatti sulla proprietà $attributtes
. Solo alla fine della request cycle questi dati verranno memorizzati nell'effettivo storage della sessione ( che dipenderà dal driver scelto ), per essere poi ricaricati nelle richieste successive.
FileSessionHandler : Quando l'oggetto Store
viene costruito in SessionManager::createNativeDriver
, gli viene passato un oggetto $handler
di tipo SessionHandlerInterface
, creato in base al tipo di driver scelto. Visto che nel nostro esempio abbiamo scelto File
come driver per memorizzare i dati di sessione, allo Store
viene passata un'istanza di FileSessionHandler
/** * Create an instance of the file session driver. * * @return \Illuminate\Session\Store */ protected function createNativeDriver() { $path = $this->app['config']['session.files']; return $this->buildSession(new FileSessionHandler($this->app['files'], $path)); }
Come accennato prima, questo handler verrà utilizzato per memorizzare effettivamenti i dati nello storage di sessione. La memorizzazione avviene nel metodo StartSession::terminate
del middleware StartSession
, in cui viene chiamato Store::save()
che a sua volta invoca FileSessionHandler::write()
per salvare il contenuto dello Store
nello storage.
Il Middleware StartSession
Nel middleware StartSession
non vengono soltanto scritti i dati nello storage, ma questa classe serve anche ad inizializzare l'interazione con la sessione. Più precisamente:
nel metodo handle
del middleware, che viene eseguito all'inizio della request cycle, viene inizializzato l'oggetto Store
, popolandone i valori partendo da quelli memorizzati dallo storage
nel metodo terminate
, che viene eseguito alla fine della request cycle, avviene l'effettiva scrittura dei dati nello storage.
Capita che si presentino alcuni casi che sono spesso interpretati dai programmatori come un malfunzionamento di sessione, ma che possiamo comprendere meglio tenendo presente quello che abbiamo appena detto:
- Non è possibile accedere direttamente alla sessione nella fase di boot di un Service Provider ( a meno di non utilizzare una callback che verrà eseguita in seguito alla fase di boot), perchè la sessione viene inizializzata in un middleware, e i middleware eseguono sempre dopo la fase di boot dei Service Provider
- Se si interrompe in qualche modo il ciclo di una richiesta ( ad esempio con un
dd()
in un controller ) dopo aver scritto un valore in sessione, quel valore non sarà effettivamente scritto nello storage della sessione, perchè il ciclo della richiesta non arriva ad eseguire il metodoterminate
del middleware
Spero che questo articolo vi abbia aiutato a capire più approfonditamente come sia gestita la sessione in Laravel