Mysql
 sql >> Database >  >> RDS >> Mysql

Come posso salvare i dati della sessione PHP su un database anziché nel file system?

Ho scoperto nel corso di diverse ore di debug che gli articoli di riferimento sono stati trovati su numerose ricerche su Google, nonché un sottoinsieme significativo di risposte di Stack Overflow come qui , qui e qui tutti forniscono informazioni non valide o obsolete.

Cose che possono causare problemi [critici] con il salvataggio dei dati della sessione in un database:

  • Mentre tutti gli esempi online affermano che puoi "riempire" il session_set_save_handler , nessuno di essi afferma che devi anche impostare la register_shutdown_function('session_write_close') anche (riferimento ).

  • Diverse guide (precedenti) fanno riferimento a una struttura di database SQL obsoleta e non dovrebbero essere usato. La struttura del database necessaria per salvare i dati della sessione nel database è:id /access /data . Questo è tutto. non c'è bisogno di varie colonne di timestamp extra come ho visto su alcune "guide" ed esempi.

    • Molte delle vecchie guide hanno anche una sintassi MySQL obsoleta come DELETE * FROM ...
  • La classe [creata nella mia domanda] deve implementare l'SessionHandlerInterface . Ho visto guide (riferite sopra) che forniscono l'implementazione di sessionHandler che non è un'interfaccia adatta. Forse le versioni precedenti di PHP avevano un metodo leggermente diverso (probabilmente <5.4).

  • I metodi della classe di sessione devono restituire i valori stabiliti dal manuale PHP. Di nuovo, probabilmente ereditato da PHP pre-5.4 ma due guide che ho letto affermavano che class->open restituisce la riga da leggere, mentre il manuale PHP indica che deve restituire true o false solo.

  • Questa è la causa del mio problema originale :Stavo usando nomi di sessione personalizzati (in realtà gli ID come nomi di sessione e gli ID di sessione sono la stessa cosa! ) come da questo ottimo post su StackOverflow e questo stava generando un nome di sessione lungo 128 caratteri. Poiché il nome della sessione è l'unica chiave necessaria per essere decifrata per compromettere una sessione e subentrare con un dirottamento della sessione quindi un nome/ID più lungo è un'ottima cosa.

    • Ma questo ha causato un problema perché MySQL stava tagliando silenziosamente l'id della sessione fino a soli 32 caratteri invece di 128, quindi non è mai stato in grado di trovare i dati della sessione nel database. Questo era un problema completamente silenzioso (forse a causa della mia classe di connessione al database che non lanciava avvisi di tali cose). Ma questo è quello a cui prestare attenzione. In caso di problemi con il recupero delle sessioni da un database, verificare innanzitutto che sia pieno l'id della sessione può essere memorizzato nel campo fornito.

Quindi, con tutto ciò fuori mano, ci sono anche alcuni dettagli extra da aggiungere:

La pagina di manuale di PHP (collegata sopra) mostra una pila di righe inadatta per un oggetto di classe:

Mentre funziona altrettanto bene se lo metti nel costruttore di classe:

class MySessionHandler implements SessionHandlerInterface {

    private $database = null;

public function __construct(){

    $this->database = new Database(whatever);

    // Set handler to overide SESSION
    session_set_save_handler(
        array($this, "open"),
        array($this, "close"),
        array($this, "read"),
        array($this, "write"),
        array($this, "destroy"),
        array($this, "gc")
        );
    register_shutdown_function('session_write_close');
    session_start();
    }
...
}

Questo significa che per poi avviare una sessione sulla tua pagina di output tutto ciò che serve è:

<?php
require "path/to/sessionhandler.class.php"; 
new MySessionHandler();

//Bang session has been setup and started and works

Per riferimento, la classe di comunicazione Session completa è la seguente, funziona con PHP 5.6 (e probabilmente 7 ma non ancora testato su 7)

<?php
/***
 * Created by PhpStorm.
 ***/
class MySessionHandler implements SessionHandlerInterface {
    private $database = null;

    public function __construct($sessionDBconnectionUrl){
        /***
         * Just setting up my own database connection. Use yours as you need.
         ***/ 

            require_once "class.database.include.php";
            $this->database = new DatabaseObject($sessionDBconnectionUrl);

        // Set handler to overide SESSION
        session_set_save_handler(
            array($this, "open"),
            array($this, "close"),
            array($this, "read"),
            array($this, "write"),
            array($this, "destroy"),
            array($this, "gc")
        );
        register_shutdown_function('session_write_close');
        session_start();
    }

    /**
     * Open
     */
    public function open($savepath, $id){
        // If successful
        $this->database->getSelect("SELECT `data` FROM sessions WHERE id = ? LIMIT 1",$id,TRUE);
        if($this->database->selectRowsFoundCounter() == 1){
            // Return True
            return true;
        }
        // Return False
        return false;
    }
    /**
     * Read
     */
    public function read($id)
    {
        // Set query
        $readRow = $this->database->getSelect('SELECT `data` FROM sessions WHERE id = ? LIMIT 1', $id,TRUE);
        if ($this->database->selectRowsFoundCounter() > 0) {
            return $readRow['data'];
        } else {
            return '';
        }
    }

    /**
     * Write
     */
    public function write($id, $data)
    {
        // Create time stamp
        $access = time();

        // Set query
        $dataReplace[0] = $id;
        $dataReplace[1] = $access;
        $dataReplace[2] = $data;
        if ($this->database->noReturnQuery('REPLACE INTO sessions(id,access,`data`) VALUES (?, ?, ?)', $dataReplace)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Destroy
     */
    public function destroy($id)
    {
        // Set query
        if ($this->database->noReturnQuery('DELETE FROM sessions WHERE id = ? LIMIT 1', $id)) {
            return true;
        } else {

            return false;
        }
    }
    /**
     * Close
     */
    public function close(){
        // Close the database connection
        if($this->database->dbiLink->close){
            // Return True
            return true;
        }
        // Return False
        return false;
    }

    /**
     * Garbage Collection
     */
    public function gc($max)
    {
        // Calculate what is to be deemed old
        $old = time() - $max;

        if ($this->database->noReturnQuery('DELETE FROM sessions WHERE access < ?', $old)) {
            return true;
        } else {
            return false;
        }
    }

    public function __destruct()
    {
        $this->close();
    }

}

Utilizzo:come mostrato appena sopra il testo del codice della classe.