Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Como faço para salvar os dados da sessão PHP em um banco de dados em vez de no sistema de arquivos?


Descobri ao longo de várias horas de depuração que os artigos referenciados encontrados em várias pesquisas do Google, bem como um subconjunto significativo de respostas do Stack Overflow, como aqui , aqui e aqui todos fornecem informações inválidas ou desatualizadas.

Coisas que podem causar problemas [críticos] ao salvar dados de sessão em um banco de dados:

  • Enquanto todos os exemplos online afirmam que você pode "preencher" o session_set_save_handler , nenhum deles afirma que você também deve definir a register_shutdown_function('session_write_close') também (referência ).

  • Vários guias (mais antigos) referem-se a uma estrutura de banco de dados SQL desatualizada e não ser usado. A estrutura do banco de dados necessária para salvar os dados da sessão no banco de dados é:id /access /data . É isso. não há necessidade de várias colunas extras de timestamp, como vi em alguns "guias" e exemplos.
    • Vários dos guias mais antigos também têm sintaxe desatualizada do MySQL, como DELETE * FROM ...

  • A aula [feita na minha pergunta] deve implementar a SessionHandlerInterface . Eu vi guias (referenciados acima) que fornecem a implementação de sessionHandler que não é uma interface adequada. Talvez as versões anteriores do PHP tivessem um método ligeiramente diferente (provavelmente <5.4).

  • Os métodos de classe de sessão devem retorne os valores definidos pelo manual do PHP. Novamente, provavelmente herdado do PHP pré-5.4, mas dois guias que li afirmaram que class->open retorna a linha a ser lida, enquanto o informações do manual do PHP que ele precisa retornar true ou false só.

  • Esta é a causa do meu problema original :eu estava usando nomes de sessão personalizados (na verdade, id's como nomes de sessão e ids de sessão são a mesma coisa! ) conforme esta muito boa postagem do StackOverflow e isso estava gerando um nome de sessão com 128 caracteres. Como o nome da sessão é a única chave que precisa ser quebrada para comprometer uma sessão e assumir o controle com um seqüestro de sessão então um nome/id mais longo é uma coisa muito boa.
    • Mas isso causou um problema porque o MySQL estava fatiando silenciosamente o ID da sessão reduzido para apenas 32 caracteres em vez de 128, por isso nunca foi capaz de encontrar os dados da sessão no banco de dados. Este foi um problema completamente silencioso (talvez devido à minha classe de conexão de banco de dados não lançar avisos de tais coisas). Mas este é o único a observar. Se você tiver algum problema com a recuperação de sessões de um banco de dados, verifique primeiro se o arquivo completo O ID da sessão pode ser armazenado no campo fornecido.

Então, com tudo isso fora do caminho, há alguns detalhes extras para adicionar também:

A página de manual do PHP (link acima) mostra uma pilha inadequada de linhas para um objeto de classe:

Considerando que funciona tão bem se você colocar isso no construtor da 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();
    }
...
}

Isso significa que para iniciar uma sessão em sua página de saída, tudo o que você precisa é:
<?php
require "path/to/sessionhandler.class.php"; 
new MySessionHandler();

//Bang session has been setup and started and works

Para referência, a classe completa de comunicação de sessão é a seguinte, isso funciona com PHP 5.6 (e provavelmente 7, mas ainda não testado em 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();
    }

}

Uso:Como mostrado logo acima do texto do código da classe.