44. Gestione avanzata delle Sessioni

Con questo capitolo concludiamo il discorso sulle Sessioni, costruendo una libreria esterna che ci consentirà un maggiore controllo sulle operazioni che ne coinvolgono la gestione.

Di default PHP salva i dati di sessione nella directory /tmp/ che è solitamente accessibile anche dall'esterno, questo potrebbe comportare seri problemi di sicurezza a seconda dei dati che intendete tenere in memoria nelle sessioni.

Definirò di seguito dei gestori che saranno automaticamente chiamati da PHP al verificarsi di un determinato evento di sessione.

Per definire quale gestore deve essere chiamato per quel determinato evento abbiamo a disposizione la funzione nativa session_set_save_handler().


Prototipo della funzione session_set_save_handler()

void session_set_save_handler ( string apertura, string chiusura, string lettura, string scrittura, string distruzione, string gc )

I parametri di questa funzione sono delle stringhe il cui nome rappresenta una funzione definita dall'utente da richiamare al verificarsi di quel determinato evento di sessione :

  • apertura - Chiamata ad inizio sessione da session_start()
    Prende due parametri : "path" e "nome", rispettivamente il percorso per i file di sessione e il nome della sessione corrente appena inizializzata.
    Deve restituire true se l'apertura della sessione è avvenuta con successo.
  • chiusura - Chiamata a fine sessione
    Non prende paramentri e deve restituire true se la chiusura della sessione è avvenuta con successo.
  • lettura - Chiamata dopo "apertura" per leggere i dati di sessione e renderli disponibili allo script tramite l'array globale $_SESSION[]
    Prende solo il parametro "id", ossia l'id della sessione corrente che sarà utilizzato per accedere ai dati di sessione.
    Dovrà restituire una stringa che rappresenta la serializzazione dell'array $_SESSION[] oppure una stringa vuota.
  • scrittura - Chiamata quando viene scritto un dato di sessione (es. $_SESSION["dato"] = 3)
    Prende due parametri : "id" e "dati", rispettivamente l'id della sessione e tutti i dati da memorizzare (l'intero contenuto di $_SESSION[]).
    Deve restituire true se la scrittura è avvenuta con successo.
  • distruzione - Chiamata da session_destroy()
    Prende solo il parametro "id" ossia l'id della sessione corrente e deve restituire se la distruzione è avvenuta con successo.
  • gc - Garbage Collector, viene chiamata casualmente per controllare che la sessione non sia scaduta
    Prende solo il parametro "max_lifetime" che rappresenta la durata della sessione in secondi (default 1440 secondi, quindi 24 minuti).
    Deve restituire true per proseguire.

Prima di iniziare a scrivere le funzioni di gestione è necessario configurare alcuni settaggi per far funzionare correttamente lo script.

Innanzitutto dobbiamo fare in modo che la chiamata a session_set_save_handler() vada a buon fine, impostando il parametro "session.save_handler" al valore "user". Trovate il parametro nel file di configurazione php.ini.

Se non potete accedere al file php.ini o volete settare questa impostazione solo per lo script corrente allora ci affidiamo come sempre alla funzione ini_set().

Vediamo ora il file sorgente della piccola libreria di esempio (sessioni.php) che ho costruito, ovviamente incompleta essendo il suo scopo puramente didattico.

sessioni.php

<?php

	// Attiva i cookie di sessione
	ini_set("session.use_cookies", 1); 

	// Accetta ID solo da cookie, rifiuta se vengono propagati da URL
	ini_set("session.use_only_cookies", 1);

	// Informa PHP che dovrà usare dei gestori definiti da noi
	ini_set("session.save_handler", "user");

	// Impostiamo la cartella del server che dovrà contenere i file di sessione
	ini_set("session.save_path", "C:/Appserv/www/test/sessioni");

	define("FILE_PREFISSO", "sess_"); // Inizio nome dei file di sessione
	define("FILE_ESTENSIONE", ".txt"); // Estensione dei file di sessione

	$session_file = NULL;

	function ss_apertura($path, $nome)
	{
		session_cache_expire(60 * 24); // I file di sessione sono validi per 24 ore
		session_set_cookie_params(60 * 60 * 24); // Il cookie di sessione dura 24 ore
		return true;
	}

	function ss_chiusura()
	{
		global $session_file;
		return @fclose($session_file); // Chiude il puntatore al file di sessione aperto
	}

	function ss_lettura($id)
	{
		$id = session_id();
		$filename = session_save_path() . "/" . FILE_PREFISSO . $id . FILE_ESTENSIONE;

		// Tenta di leggere il contenuto del file. Se il file non esiste lo crea
		$session_data = @file_get_contents($filename);

		if (strlen($session_data))
		{ return $session_data; }
		else
		{ return ""; }
	}

	function ss_scrittura($id, $dati)
	{
		global $session_file;

		$id = session_id();
		$filename = session_save_path() . "/" . FILE_PREFISSO . $id . FILE_ESTENSIONE;

		try
		{
			$session_file = @fopen($filename, "w+");
			@fwrite($session_file, $dati); // Scriviamo nel file i nuovi dati di sessione

			return true;
		}
		catch (Exception $e)
		{ return false; }
	}

	function ss_distruzione($id)
	{
		$filename = session_save_path() . "/" . FILE_PREFISSO . $id . FILE_ESTENSIONE;
		return @unlink($filename); // Cancella il file
	}

	function ss_spazzino($max_lifetime) { return true; }

	session_set_save_handler("ss_apertura", "ss_chiusura", "ss_lettura", "ss_scrittura", "ss_distruzione", "ss_spazzino");
	session_start();

?>

Il codice è piuttosto semplice e salva tutti i file di sessione all'interno di file di testo in una cartella "sessioni".

La libreria essendo solo dimostrativa non gestisce alcuna eccezione, ho infatti messo la chiocciola @ prima di quelle funzioni che potrebbero sollevare errori o avvertenze, che farebbero fallire l'invio degli header non avendo bufferizzato l'output con ob_start().

Come potete vedere dalle funzioni ss_lettura() e ss_scrittura, la serializzazione dei dati viene gestita automaticamente da PHP, non vi è quindi alcuna necessità di chiamare altre funzioni, lo scopo di questi due gestori è solo di scrivere e leggere i dati di sessione già pronti.

Di seguito due pagine di esempio : "index.php" e "test.php", la prima scrive i dati di sessione e la seconda li legge.

index.php

<?php

	require_once("sessioni.php");

	$_SESSION["nome"] = "Mario";
	$_SESSION["cognome"] = "Rossi";

?>

<a href="test.php">Test</a>

test.php

<?php

	require_once("sessioni.php");

	echo "Nome = " . $_SESSION["nome"] . "<br />";
	echo "Cognome = " . $_SESSION["cognome"] . "<br />";

?>

Eseguendo index.php viene inizializzata la sessione a partire dal cookie di sessione che come potete vedere dalla schermata non scadrà alla chiusura del browser ma 24 ore dopo. Il cookie come sempre conterrà l'id di sessione che sarà utilizzato per dare un nome univoco al file di sessione (schermata) che conterrà i dati di sessione già serializzati (schermata).


Osservazioni

Ho volutamente lasciato incompleta la funzione ss_spazzino() che impersona il Garbage Collector perchè non l'ho ritenuta utile per questo esempio con i file.

A mio avviso questo sistema di gestione delle sessioni può tornare molto utile e versatile qualora si possedesse un database MySQL, in modo da memorizzare le sessioni nella base di dati, non solo rendendo l'applicazione più sicura, ma consentendovi di condividere i dati delle sessioni anche con applicazioni esterne al server, utilizzando quindi gli stessi dati da più siti contemporaneamente.

Ho creato un esempio semplice con i file invece di usare MySQL, per darvi modo di prendere dimestichezza con queste funzioni.

Vi consiglio pertanto di tornare a dare una spolverata a questo capitolo non appena avrete imparato ad utilizzare un database MySQL, e allora avrete modo di costruirvi una libreria vostra molto più potente e utile di questa.


Il Garbage Collector

Quando definirete una funzione per il Garbage Collector, vi sarà utile sapere per la fase di debug che questa funzione verrà chiamata casualmente in base a dei parametri contenuti nel file di configurazione php.ini :

  • session.gc_maxlifetime - Specifica i secondi dopo i quali i dati non saranno più validi
    Di default è impostato a 1440 secondi. Vi consiglio di tenere in memoria da qualche parte (es. variabile globale) il timestamp di quando è nata la sessione, in modo da verificare rapidamente con un semplice confronto se la sessione è scaduta, sommando al timestamp di nascita il valore maxlifetime passato come parametro al Garbage Collector. Se sarà minore del timestamp corrente allora la sessione è scaduta.
  • session.gc_divisor - Usato per il calcolo della probabilità che venga chiamato il Garbage Collector
    Legato a "session.gc_probability", vedi sotto.
  • session.gc_probability - Usato per il calcolo della probabilità che venga chiamato il Garbage Collector
    I parametri "gc_divisor" e "gc_probability" sono di default settati rispettivamente a 100 e 1.
    Questo significa che c'è una probabilità dell'1% che venga chiamato il garbage collector, o meglio ogni 100 esecuzioni dello script.

In fase di debug vi consiglio quindi di impostare i parametri come specificato di seguito :

  • session.gc_maxlifetime - 60 secondi invece di 1440, così non dovrete aspettare 24 minuti ogni volta che controllate se "pulisce"
  • session.gc_divisor - Lasciate il valore inalterato a 100
  • session.gc_probability - Impostate il valore a 100 come gc_divisor, in modo che il Garbage Collector venga chiamato ad ogni esecuzione dello script e vi dia il modo di verificarne immediatamente il funzionamento

Anche queste impostazioni possono essere modificate tramite ini_set().

Nota : ricordatevi che potete usare $_SESSION[] come un normale array, memorizzandovi quindi anche dati complessi :

<?php

	$_SESSION["utente"] = new Utente("Mario", "Rossi");

?>