Skip to content
 

Implementing a Database-based Session-handler

While I’ve been using the database session data handler in the CodeIgniter framework for some time, upon reading this thread at PHPBuilder forums I decided it was time to write my own. In part I just wanted to gain a more thorough understanding of the process, and I also figured I could use it some day for a small project that does not require the power of CodeIgniter or something similar.

I decided to use an OOP approach for this, creating two classes. One class, SessionHandler, would be the interface the client code will use to establish the session-handler functions. A second class, SessionData, would handle the actual interface with the database. For now it uses the MySQLi database
interface; but if I wanted to use PDO or something else, I would just need to either edit the SessionData class appropriately, or even go to some sort of abstract factory pattern, perhaps, to instantiate a DBMS-specific class. For now, though, I’ll just keep it “simple” with only the MySQLi class.

So, without further ado, here is the code I cobbled together. Feel free to use it and modify it as desired, but I accept no responsibility if it does not work as you would like.

SessionData class:


<?php
/**
 * @author Charles Reace (www.charles-reace.com)
 * @package session
 */
 
/**
 * Session data model
 */
class SessionData
{
   //------------ Modify these values for your installation/application:
   /**
    * @var string Database host
    */
   private $dbHost  = 'localhost';
   /**
    * @var string Database user name
    */
   private $dbUser  = 'root';
   /**
    * @var string Database user password
    */
   private $dbPass  = '********';
   /**
    * @var string Database name
    */
   private $dbName  = 'test';
   /**
    * @var string Database table name
    */
   private $dbTable = 'session_data';
   //------------ End user-modified settings -------------//
 
   /**
    * MySQLi object
    */
   private $db;
 
   /**
    * Constructor
    *
    * Creates MySQLi connection if none provided.
    * Throws exception if cannot create connection
    *
    * @return void
    * @param object $db MySQLi
    */
   public function __construct(mysqli $db = null)
   {
      if(isset($db))
      {
         $this->db = $db;
      }
      else
      {
         $this->db = new mysqli(
            $this->dbHost,
            $this->dbUser,
            $this->dbPass
         );
         if($this->db->connect_errno)
         {
            throw new Exception($this->db->connect_error);
         }
      }
   }
 
   /**
    * Read session data
    * @return string
    * @param string $id Session ID
    */
   public function get($id)
   {
      $sql = "SELECT `data` FROM `{$this->dbName}`.`{$this->dbTable}` " .
             "WHERE `id` = ?";
      if($stmt = $this->db->prepare($sql))
      {
         if($stmt->bind_param('s', $id))
         {
            if($stmt->execute())
            {
               $stmt->bind_result($data);
               if($stmt->fetch())
               {
                  return $data;
               }
               return '';
            }
            error_log($this->stmt->error);
         }
         error_log($this->stmt->error);
      }
      error_log($this->db->error);
      return '';
   }
 
   /**
    * Insert/replace session data
    * @return bool
    * @param string $id  Session ID
    * @param string $data  Session data
    */
   public function set($id, $data)
   {
      $sql = "REPLACE INTO `{$this->dbName}`.`{$this->dbTable}` " .
             "(`id`, `data`, `time`) VALUES (?, ?, NOW())";
      if($stmt = $this->db->prepare($sql))
      {
         if($stmt->bind_param('ss', $id, $data))
         {
            if($stmt->execute() and $stmt->affected_rows)
            {
               return true;
            }
            else
            {
               error_log($stmt->error);
            }
         }
         else
         {
            error_log($stmt->error);
         }
      }
      else
      {
         error_log($this->db->error);
      }
      return false;
   }
 
   /**
    * Delete data for given session ID
    * @return bool
    * @param string $id
    */
   public function delete($id)
   {
      $sql = "DELETE FROM `{$this->dbName}`.`{$this->dbTable}` " .
             "WHERE `id` = ?";
      if($stmt = $this->db->prepare($sql))
      {
         if($stmt->bind_param('s', $id))
         {
            if($stmt->execute())
            {
               return true;
            }
            else
            {
               error_log($stmt->error);
            }
         }
         else
         {
            error_log($stmt->error);
         }
      }
      else
      {
         error_log($this->db->error);
      }
      return false;
   }
 
   /**
    * Delete data older than specified number of seconds
    * @return bool
    * @param int $seconds
    */
   public function clean($seconds)
   {
      $sec = (int) $seconds;
      $sql = "DELETE FROM `{$this->dbName}`.`{$this->dbTable}` " .
             "WHERE `time` < (NOW() - INTERVAL $sec SECOND)";
      if($this->db->query($sql))
      {
         return true;
      }
      error_log($this->db->error);
      return false;
   }
 
   /**
    * Create the session data table
    *
    * Use this method with a DB user with create privilege, or set the
    * $print option to true to just get the SQL string for copy-and-paste use.
    *
    * @return mixed string if print, else bool
    * @param bool $print
    */
   public function createTable($print = false)
   {
      $sql = <<<EOD
CREATE TABLE IF NOT EXISTS `{$this->dbName}`.`{$this->dbTable}` (
  `id` varchar(255) NOT NULL COMMENT 'session ID',
  `data` text CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'serialized session array',
  `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `time` (`time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
EOD;
      if($print)
      {
         return $sql;
      }
      else
      {
         $this->db->query($sql);
         if($this->db->errno)
         {
            user_error($this->db->error);
            return false;
         }
      }
      return true;
   }
}

SessionHandler class:


<?php
/**
 * @author Charles Reace (www.charles-reace.com)
 * @package Session
 */
 
/**
 *@uses SessionData
 */
require_once dirname(__FILE__).'/SessionData.php';
 
/**
 * Session Handler
 */
class SessionHandler
{
   /**
    * @var object SessionData
    */
   private $sd;
 
   /**
    * Constructor
    * @return void
    * @param object $sd SessionData [optional]
    */
   public function __construct(SessionData $sd=null)
   {
      if(isset($sd))
      {
         $this->sd = $sd;
      }
      else
      {
         try
         {
            $this->sd = new SessionData();
         }
         catch(Exception $e)
         {
            throw new Exception($e);
         }
      }
   }
 
   public function open($path, $name)
   {
      return true;
   }
 
   public function close()
   {
      return true;
   }
 
   public function read($id)
   {
      return $this->sd->get($id);
   }
 
   public function write($id, $data)
   {
      return $this->sd->set($id, $data);
   }
 
   public function gc($seconds)
   {
      return $this->sd->clean($seconds);
   }
 
   public function destroy($id)
   {
      return $this->sd->delete($id);
   }
 
   public function __destruct()
   {
      session_write_close();
   }
}

Sample include file implementing this session-handler:


<?php
/**
 * Start up the database session handler
 * @author Charles Reace
 * @package Session
 */
 
require_once dirname(__FILE__) . '/SessionHandler.php';
try
{
   $sh = new SessionHandler();
}
catch(Exception $e)
{
   error_log((string)$e);
   die('Session initialization failed');
}
session_set_save_handler(
   array($sh, 'open'),
   array($sh, 'close'),
   array($sh, 'read'),
   array($sh, 'write'),
   array($sh, 'destroy'),
   array($sh, 'gc')
) or die("Session save handler failed");
session_start();

Leave a Reply

You must be logged in to post a comment.