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();