marek_mar
12-29-2005, 10:38 PM
This is a session class that has nothing to do with PHP sessions. It uses PDO so it should be usable with any DB PDO supports. The PHP requirements are quite high. You need PHP5.1.1 for it to run.
Ok but what can this thing do?
It does everything what the PHP sessions would do (Ionclusing the usage of the $_SESSION superglobal)
It checks wether the user wa inactive for too long.
Obviously it checks if the session didn't expire.
It checks if the users IP/User Agent are the same.
It regenerates the sid every n seconds (which makes it harder to steal the session)
Everything listed above can be configured.
<?php
/**
* Session Class
*
* @author Marek_mar
*
*/
class session
{
/*
This is the table you need.
CREATE TABLE `TABLE_SESSIONS`
(
`sid` varchar(32) NOT NULL default '0',
`last_page` varchar(200) NOT NULL,
`session_start` int(11) NOT NULL,
`last_active` int(11) NOT NULL,
`last_sid_change` int(11) NOT NULL,
`session_ip` varchar(40) NOT NULL,
`session_agent` varchar(150) NOT NULL,
`data` text NOT NULL,
PRIMARY KEY (`sid`)
);
*/
public $sid = false,
$session_data = array();
private $db = false,
$use_cookies = false,
$cookie_data,
$new = false,
$time = 0,
$db_sid = '';
private $fields = array('sid', 'last_page', 'session_start', 'last_active', 'last_sid_change', 'session_ip', 'session_agent');
// These fileds are in the DB and should not be stored with the other data.
private $config = array(
'session_max_time' => 3600, // 1 hour
'session_max_inactive' => 600, // 10 minutes
'sid_regeneration_interval' => 60, // Regenerate the sid every n seconds
'cookie_name' => 'my_session', // The cookie name
'check_ip' => true, // Should the IP be checked
'check_agent' => true, // Should the user agent be checked
);
public function __construct(&$db)
{
$this->db = $db;
$this->time = time();
$_SESSION = array();
$this->session_data =& $_SESSION;
// Ok one way or another we are ready to check for the sid.
if($this->getCookieData())
{ // Cookies enabled.
return true;
}
else if(isset($_REQUEST['sid']) && (strlen($_REQUEST['sid']) > 0))
{ // We do not have cookies enabled.
$this->sid = $_REQUEST['sid'];
}
else
{ // No session.
$this->create();
return 0;
}
return true;
// Ok we we can start the session any time now.
}
public function start()
{
if($this->new)
{
return true;
}
// Let's check if a session exists.
$sql = 'SELECT * FROM `' . TABLE_SESSIONS . '`
WHERE `sid` = ' . $this->db->quote($this->sid) . ' LIMIT 1;';
try
{
$result = $this->db->query($sql);
}
catch (PDOException $e)
{
print "Error!: " . $e->getMessage() . "<br/>";
die();
}
$data = $result->fetchAll(PDO::FETCH_ASSOC);
if(isset($data[0]))
{ // Session exists.
$data = $data[0];
if($data['last_active'] + $this->cfg('session_max_inactive', 3600) < $this->time)
{ // Either the user was inactive for too long.
$this->create();
return 1;
}
if($data['session_start'] + $this->cfg('session_max_time', 3600) < $this->time)
{ // The session expired.
$this->create();
return 2;
}
if($this->cfg('check_ip', false) && $data['session_ip'] != $_SERVER['REMOTE_ADDR'])
{ // INvalid IP.
$this->create();
return 3;
}
if($this->cfg('check_agent', true) && $data['session_agent'] != $_SERVER['HTTP_USER_AGENT'])
{ // INvalid useragent.
$this->create();
return 4;
}
// Ok the session should be valid by this point.
// Updating the needed variables.
$data['last_active'] = $this->time;
$data['last_page'] = substr($_SERVER['PHP_SELF'] . '?' . $_SERVER['QUERY_STRING'], 0, 200);
// Merging with the other values.
$other_data = unserialize($data['data']);
unset($data['data']); // We need to get rid of this becouse someone might want to add a $_SESSION['data'] index.
$data = array_merge($data, $other_data);
$this->db_sid = $data['sid'];
// Does the sid need regenerating?
if($data['last_sid_change'] + $this->cfg('sid_regeneration_interval', 60) < $this->time)
{ // Yes it does.
$data['last_sid_change'] = $this->time;
$data['sid'] = $this->generate_sid();
// The sid will be simply updated so no unused sessions will be there.
}
$this->session_data = $data;
if($this->use_cookies)
{
$this->setSessionCookie('_sid', array('sid' => $this->session_data['sid']));
}
// For the users use.
$GLOBALS['SID'] = '?sid=' . (($this->use_cookies) ? '' : $this->session_data['sid']);
define('SID', $GLOBALS['SID']);
return true;
}
// Session does not exist.
$this->create();
return 5;
}
private function create()
{ // Default settings.
$this->sid = $this->generate_sid();
$this->session_data = array(
'sid' => $this->sid,
'session_start' => $this->time,
'last_active' => $this->time,
'last_sid_change' => $this->time,
'last_page' => substr($_SERVER['PHP_SELF'] . '?' . $_SERVER['QUERY_STRING'], 0, 200),
'session_ip' => $_SERVER['REMOTE_ADDR'],
'session_agent' => substr($_SERVER['HTTP_USER_AGENT'], 0, 150)
);
$this->new = true;
// All data set.
// We assume cookies are enabled.
$this->setSessionCookie('_sid', array('sid' => $this->session_data['sid']));
// For the users use.
if(!defined('SID'))
{
$GLOBALS['SID'] = '?sid=' . (($this->use_cookies) ? '' : $this->session_data['sid']);
define('SID', $GLOBALS['SID']);
}
return true;
}
private function generate_sid()
{
return md5($this->time . uniqid() . $_SERVER['REMOTE_ADDR'] . 'salt'); // Overkill?
}
function __destruct()
{
foreach($this->fields as $key)
{
if(!in_array($key, $this->fields))
{
continue;
}
$sql_array[$key] = $this->session_data[$key];
unset($this->session_data[$key]);
}
$sql_array['data'] = serialize($this->session_data);
if($this->new)
{
$sql = $this->build_query('insert', TABLE_SESSIONS, $sql_array);
}
else
{
$sql = $this->build_query('update', TABLE_SESSIONS, $sql_array, 'sid', $this->db_sid);
}
try
{
$this->db->query($sql);
}
catch (PDOException $e)
{
$data = "Error!: " . $e->getMessage() . "<br/>";
die();
}
if(!rand(0, 9))
{ // 10% chance of gc (Garbage Collection - Deletion of old sessions)
$this->gc();
}
return true;
}
function gc()
{
$sql = 'DELETE FROM `' . TABLE_SESSIONS . '`
WHERE `session_start` < ' . ($this->time - $this->cfg('session_max_time', 3600)) . '
OR `last_active` < ' . ($this->time - $this->cfg('session_max_inactive', 600)) . ';';
try
{
$this->db->query($sql);
}
catch (PDOException $e)
{
print "Error!: " . $e->getMessage() . "<br/>";
die();
}
return true;
}
// This is something that proves that this class was written to work with other classes / functions.
function cfg($cfg, $default = false)
{
if(!isset($this->config[$cfg]))
{
if($default)
{
return $default;
}
else
{
return -1;
}
}
return $this->config[$cfg];
}
private function setSessionCookie($suffix, $data, $expires = false)
{
if(!is_array($data))
{
$data = array($data);
}
return setcookie($this->cfg('cookie_name') . $suffix, serialize($data), $this->time + (($expires) ? $expires : $this->cfg('session_max_time', 3600)));
}
private function getCookieData()
{
if(isset($_COOKIE[$this->cfg('cookie_name') . '_sid']))
{
$this->use_cookies = true;
$this->cookie_data = unserialize($_COOKIE[$this->cfg('cookie_name') . '_sid']);
$this->sid = $this->cookie_data['sid'];
return true;
}
return false;
}
private function build_query($type, $table, $array, $where = false, $value = false)
{ // Remember this?
$type = strtoupper($type);
switch($type)
{
case 'UPDATE':
$ret = array();
foreach($array as $k => $v)
{
if(is_array($v))
{
$ret[] = '`' . $k . '` = `' . $k . '`' . $v[0];
}
else
{
$ret[] = '`' . $k . '` = ' . $this->db->quote($v);
}
}
$ret = 'SET ' . implode(', ', $ret);
if($where && $value)
{
$ret .= ' WHERE `' . $where . '` = ' . $this->db->quote($value);
}
break;
case 'INSERT':
$type = 'INSERT INTO';
foreach($array as $k => $v)
{
$array[$k] = $this->db->quote($v);
}
$ret = '(`' . implode('`, `', array_keys($array)) . '`) VALUES (' . implode(', ', $array) . ')';
break;
}
return $type . ' `' . $table . '` ' . $ret;
}
}
?>
The constructor expects a PDO class.
$db = new PDO('mysql:host=localhost;dbname=session', 'root', ''); // These are the default mysql DB settings. The table name is "session"
// You should define the session table name:
define('TABLE_SESSIONS', 'sessions');
$session = new session($db);
After that all you need to do is staret the session
$session->start();
session::start() doesn't expect any arguments.
It returns an error code (integer) or bool(true); See example.
Another method you can use it the session::cfg() method. It returns sessions configuration values.
print $session->cfg('session_max_time');
After the session was started you will have acces to the $_SESSION superglobal. It will have a vew variables predefined* (they may come in handy). If you change/delete them the class might stop working. You can set any other variables just like with normal PHP sessions.
Example:
<?php
$db = new PDO('mysql:host=localhost;dbname=session', 'root', ''); // These are the default mysql DB settings. The table name is "session"
define('TABLE_SESSIONS', 'sessions');
$error = array(
'New session',
'You were inactive for too long.',
'Your session expired.',
'You IP doesn\'t match the IP with which the session was started.',
'Your User Agent doesn\'t match the User Agent with which the session was started.',
'Session does not exist.'
);
$session = new session($db);
$error_code = $session->start();
if($error_code !== true)
{
print '<span style="color:red">' . $error[$error_code] . '</span>' . "<br />\n";
}
print '<a href="' . $_SERVER['PHP_SELF'] . SID . '">Test </a>' . "<br />\n";
if(!isset($_SESSION['clicks']))
{
$_SESSION['clicks'] = 0;
}
else
{
$_SESSION['clicks']++;
}
if(!isset($_SESSION['testing']))
{
$_SESSION['testing'] = time();
}
print 'You were inactive for ' . (time() - $_SESSION['testing']) . ' seconds!' . ((time() - $_SESSION['testing'] == 0) ? ' You are so imptient!' : '' ) . "<br />\n";
print 'This session will last only for another ' . (($session->cfg('session_max_time') + $_SESSION['session_start']) - time()) . ' seconds!' . "<br />\n";
$_SESSION['testing'] = time();
print 'This is the ' . $_SESSION['clicks'] . ' page you visited this session.' . "<br />\n";
print 'Your sid should have regenerated ' . floor((time() - $_SESSION['session_start']) / $session->cfg('sid_regeneration_interval')) . ' times by now.' . "<br />\n";
?>
Have fun!
* These are those predefined values: 'sid', 'last_page', 'session_start', 'last_active', 'last_sid_change', 'session_ip' and 'session_agent'.
I've wanted to post this for a while now... I just waited for a suitable date...
Ok but what can this thing do?
It does everything what the PHP sessions would do (Ionclusing the usage of the $_SESSION superglobal)
It checks wether the user wa inactive for too long.
Obviously it checks if the session didn't expire.
It checks if the users IP/User Agent are the same.
It regenerates the sid every n seconds (which makes it harder to steal the session)
Everything listed above can be configured.
<?php
/**
* Session Class
*
* @author Marek_mar
*
*/
class session
{
/*
This is the table you need.
CREATE TABLE `TABLE_SESSIONS`
(
`sid` varchar(32) NOT NULL default '0',
`last_page` varchar(200) NOT NULL,
`session_start` int(11) NOT NULL,
`last_active` int(11) NOT NULL,
`last_sid_change` int(11) NOT NULL,
`session_ip` varchar(40) NOT NULL,
`session_agent` varchar(150) NOT NULL,
`data` text NOT NULL,
PRIMARY KEY (`sid`)
);
*/
public $sid = false,
$session_data = array();
private $db = false,
$use_cookies = false,
$cookie_data,
$new = false,
$time = 0,
$db_sid = '';
private $fields = array('sid', 'last_page', 'session_start', 'last_active', 'last_sid_change', 'session_ip', 'session_agent');
// These fileds are in the DB and should not be stored with the other data.
private $config = array(
'session_max_time' => 3600, // 1 hour
'session_max_inactive' => 600, // 10 minutes
'sid_regeneration_interval' => 60, // Regenerate the sid every n seconds
'cookie_name' => 'my_session', // The cookie name
'check_ip' => true, // Should the IP be checked
'check_agent' => true, // Should the user agent be checked
);
public function __construct(&$db)
{
$this->db = $db;
$this->time = time();
$_SESSION = array();
$this->session_data =& $_SESSION;
// Ok one way or another we are ready to check for the sid.
if($this->getCookieData())
{ // Cookies enabled.
return true;
}
else if(isset($_REQUEST['sid']) && (strlen($_REQUEST['sid']) > 0))
{ // We do not have cookies enabled.
$this->sid = $_REQUEST['sid'];
}
else
{ // No session.
$this->create();
return 0;
}
return true;
// Ok we we can start the session any time now.
}
public function start()
{
if($this->new)
{
return true;
}
// Let's check if a session exists.
$sql = 'SELECT * FROM `' . TABLE_SESSIONS . '`
WHERE `sid` = ' . $this->db->quote($this->sid) . ' LIMIT 1;';
try
{
$result = $this->db->query($sql);
}
catch (PDOException $e)
{
print "Error!: " . $e->getMessage() . "<br/>";
die();
}
$data = $result->fetchAll(PDO::FETCH_ASSOC);
if(isset($data[0]))
{ // Session exists.
$data = $data[0];
if($data['last_active'] + $this->cfg('session_max_inactive', 3600) < $this->time)
{ // Either the user was inactive for too long.
$this->create();
return 1;
}
if($data['session_start'] + $this->cfg('session_max_time', 3600) < $this->time)
{ // The session expired.
$this->create();
return 2;
}
if($this->cfg('check_ip', false) && $data['session_ip'] != $_SERVER['REMOTE_ADDR'])
{ // INvalid IP.
$this->create();
return 3;
}
if($this->cfg('check_agent', true) && $data['session_agent'] != $_SERVER['HTTP_USER_AGENT'])
{ // INvalid useragent.
$this->create();
return 4;
}
// Ok the session should be valid by this point.
// Updating the needed variables.
$data['last_active'] = $this->time;
$data['last_page'] = substr($_SERVER['PHP_SELF'] . '?' . $_SERVER['QUERY_STRING'], 0, 200);
// Merging with the other values.
$other_data = unserialize($data['data']);
unset($data['data']); // We need to get rid of this becouse someone might want to add a $_SESSION['data'] index.
$data = array_merge($data, $other_data);
$this->db_sid = $data['sid'];
// Does the sid need regenerating?
if($data['last_sid_change'] + $this->cfg('sid_regeneration_interval', 60) < $this->time)
{ // Yes it does.
$data['last_sid_change'] = $this->time;
$data['sid'] = $this->generate_sid();
// The sid will be simply updated so no unused sessions will be there.
}
$this->session_data = $data;
if($this->use_cookies)
{
$this->setSessionCookie('_sid', array('sid' => $this->session_data['sid']));
}
// For the users use.
$GLOBALS['SID'] = '?sid=' . (($this->use_cookies) ? '' : $this->session_data['sid']);
define('SID', $GLOBALS['SID']);
return true;
}
// Session does not exist.
$this->create();
return 5;
}
private function create()
{ // Default settings.
$this->sid = $this->generate_sid();
$this->session_data = array(
'sid' => $this->sid,
'session_start' => $this->time,
'last_active' => $this->time,
'last_sid_change' => $this->time,
'last_page' => substr($_SERVER['PHP_SELF'] . '?' . $_SERVER['QUERY_STRING'], 0, 200),
'session_ip' => $_SERVER['REMOTE_ADDR'],
'session_agent' => substr($_SERVER['HTTP_USER_AGENT'], 0, 150)
);
$this->new = true;
// All data set.
// We assume cookies are enabled.
$this->setSessionCookie('_sid', array('sid' => $this->session_data['sid']));
// For the users use.
if(!defined('SID'))
{
$GLOBALS['SID'] = '?sid=' . (($this->use_cookies) ? '' : $this->session_data['sid']);
define('SID', $GLOBALS['SID']);
}
return true;
}
private function generate_sid()
{
return md5($this->time . uniqid() . $_SERVER['REMOTE_ADDR'] . 'salt'); // Overkill?
}
function __destruct()
{
foreach($this->fields as $key)
{
if(!in_array($key, $this->fields))
{
continue;
}
$sql_array[$key] = $this->session_data[$key];
unset($this->session_data[$key]);
}
$sql_array['data'] = serialize($this->session_data);
if($this->new)
{
$sql = $this->build_query('insert', TABLE_SESSIONS, $sql_array);
}
else
{
$sql = $this->build_query('update', TABLE_SESSIONS, $sql_array, 'sid', $this->db_sid);
}
try
{
$this->db->query($sql);
}
catch (PDOException $e)
{
$data = "Error!: " . $e->getMessage() . "<br/>";
die();
}
if(!rand(0, 9))
{ // 10% chance of gc (Garbage Collection - Deletion of old sessions)
$this->gc();
}
return true;
}
function gc()
{
$sql = 'DELETE FROM `' . TABLE_SESSIONS . '`
WHERE `session_start` < ' . ($this->time - $this->cfg('session_max_time', 3600)) . '
OR `last_active` < ' . ($this->time - $this->cfg('session_max_inactive', 600)) . ';';
try
{
$this->db->query($sql);
}
catch (PDOException $e)
{
print "Error!: " . $e->getMessage() . "<br/>";
die();
}
return true;
}
// This is something that proves that this class was written to work with other classes / functions.
function cfg($cfg, $default = false)
{
if(!isset($this->config[$cfg]))
{
if($default)
{
return $default;
}
else
{
return -1;
}
}
return $this->config[$cfg];
}
private function setSessionCookie($suffix, $data, $expires = false)
{
if(!is_array($data))
{
$data = array($data);
}
return setcookie($this->cfg('cookie_name') . $suffix, serialize($data), $this->time + (($expires) ? $expires : $this->cfg('session_max_time', 3600)));
}
private function getCookieData()
{
if(isset($_COOKIE[$this->cfg('cookie_name') . '_sid']))
{
$this->use_cookies = true;
$this->cookie_data = unserialize($_COOKIE[$this->cfg('cookie_name') . '_sid']);
$this->sid = $this->cookie_data['sid'];
return true;
}
return false;
}
private function build_query($type, $table, $array, $where = false, $value = false)
{ // Remember this?
$type = strtoupper($type);
switch($type)
{
case 'UPDATE':
$ret = array();
foreach($array as $k => $v)
{
if(is_array($v))
{
$ret[] = '`' . $k . '` = `' . $k . '`' . $v[0];
}
else
{
$ret[] = '`' . $k . '` = ' . $this->db->quote($v);
}
}
$ret = 'SET ' . implode(', ', $ret);
if($where && $value)
{
$ret .= ' WHERE `' . $where . '` = ' . $this->db->quote($value);
}
break;
case 'INSERT':
$type = 'INSERT INTO';
foreach($array as $k => $v)
{
$array[$k] = $this->db->quote($v);
}
$ret = '(`' . implode('`, `', array_keys($array)) . '`) VALUES (' . implode(', ', $array) . ')';
break;
}
return $type . ' `' . $table . '` ' . $ret;
}
}
?>
The constructor expects a PDO class.
$db = new PDO('mysql:host=localhost;dbname=session', 'root', ''); // These are the default mysql DB settings. The table name is "session"
// You should define the session table name:
define('TABLE_SESSIONS', 'sessions');
$session = new session($db);
After that all you need to do is staret the session
$session->start();
session::start() doesn't expect any arguments.
It returns an error code (integer) or bool(true); See example.
Another method you can use it the session::cfg() method. It returns sessions configuration values.
print $session->cfg('session_max_time');
After the session was started you will have acces to the $_SESSION superglobal. It will have a vew variables predefined* (they may come in handy). If you change/delete them the class might stop working. You can set any other variables just like with normal PHP sessions.
Example:
<?php
$db = new PDO('mysql:host=localhost;dbname=session', 'root', ''); // These are the default mysql DB settings. The table name is "session"
define('TABLE_SESSIONS', 'sessions');
$error = array(
'New session',
'You were inactive for too long.',
'Your session expired.',
'You IP doesn\'t match the IP with which the session was started.',
'Your User Agent doesn\'t match the User Agent with which the session was started.',
'Session does not exist.'
);
$session = new session($db);
$error_code = $session->start();
if($error_code !== true)
{
print '<span style="color:red">' . $error[$error_code] . '</span>' . "<br />\n";
}
print '<a href="' . $_SERVER['PHP_SELF'] . SID . '">Test </a>' . "<br />\n";
if(!isset($_SESSION['clicks']))
{
$_SESSION['clicks'] = 0;
}
else
{
$_SESSION['clicks']++;
}
if(!isset($_SESSION['testing']))
{
$_SESSION['testing'] = time();
}
print 'You were inactive for ' . (time() - $_SESSION['testing']) . ' seconds!' . ((time() - $_SESSION['testing'] == 0) ? ' You are so imptient!' : '' ) . "<br />\n";
print 'This session will last only for another ' . (($session->cfg('session_max_time') + $_SESSION['session_start']) - time()) . ' seconds!' . "<br />\n";
$_SESSION['testing'] = time();
print 'This is the ' . $_SESSION['clicks'] . ' page you visited this session.' . "<br />\n";
print 'Your sid should have regenerated ' . floor((time() - $_SESSION['session_start']) / $session->cfg('sid_regeneration_interval')) . ' times by now.' . "<br />\n";
?>
Have fun!
* These are those predefined values: 'sid', 'last_page', 'session_start', 'last_active', 'last_sid_change', 'session_ip' and 'session_agent'.
I've wanted to post this for a while now... I just waited for a suitable date...