Articoli

logo articolo

La Gestione Interna Della Sessione in Laravel

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:

 diagramma classi sessione Laravel

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 metodo terminate del middleware

Spero che questo articolo vi abbia aiutato a capire più approfonditamente come sia gestita la sessione in Laravel