As you should be aware the HTTP protocol, as
used for serving web pages, is completely stateless. This means that
after the server has received a request, processed it and sent a
response, the process which dealt with that request dies.
Saving PHP Session Data to a Database
Introduction
As you should be aware the HTTP protocol, as used for serving web
pages, is completely stateless. This means that after the server has
received a request, processed it and sent a response, the process which
dealt with that request dies. Anything that the process had in its
memory therefore dies with it, so when a subsequent request is received
from the same client it is unable to refer to its memory about anything
that happened previously.
Fortunately PHP provides a standard method of maintaining memory (state) between requests in the form of Session Handling functions. This allows the programmer to maintain a set of variables in the $_SESSION
array which is automatically saved to persistent storage at the end of
each script, and then automatically loaded back into memory when a
subsequent request is received from a client which supplies the same session_id.
By
default the medium used as persistent storage by the session handler
will be a series of disk files, one per session, where the file name is
the session_id.
A file is created khan a new session starts, and is deleted when the
session terminates (or has expired). This is perfectly adequate for
most circumstances, but it has the following drawbacks:
- If
you are using a shared server then other users of that server may be
able to access your session files, thus compromising the security of
your site.
- Each server will have its own directory where
these session files are maintained, so if you are employing load
balancing across multiple servers there is no guarantee that a request
for an existing session will be given to the server which is
maintaining the state for that session.
- It would be difficult
for a site administrator to perform such queries as "how many sessions
are currently active?" or "which users are currently logged in?"
The authors of PHP have provided the ability to store session data using a method other than disk files by means of the session_set_save_handler function. This document will show how I have used this function to store all my session data in my application database.
Define database table
This is how I have defined the database table which will hold all my session data:
CREATE TABLE `php_session` (
`session_id` varchar(32) NOT NULL default '',
`user_id` varchar(16) default NULL,
`date_created` datetime NOT NULL default '0000-00-00 00:00:00',
`last_updated` datetime NOT NULL default '0000-00-00 00:00:00',
`session_data` longtext,
PRIMARY KEY (`session_id`),
KEY `last_updated` (`last_updated`)
) ENGINE=MyISAM
Please note the following:
| session_id |
This is the identity of the session, so it must be the primary key. |
| session_data |
This must be big enough to hold the largest $_SESSION array that your application is liable to produce. |
| date_created |
This is used to identify when the session was started. |
| last_updated |
This
is used to identify when the last request was processed for the
session. This is also used in garbage collection to remove those
sessions which have been inactive for a period of time. |
| user_id |
This is used to identify the person to whom this session belongs. The value is provided by the application logon screen. |
Define database class
Within my development infrastructure it is my practice to use a separate class to access each database table. Each table class is actually a subclass to a generic table class
which contains all the functionality which is standard across all
database tables. This section identifies the contents of the subclass.
A copy of the superclass is contained within the source code for my sample application.
The table class exists in a file called <tablename>.class.inc which is described in A Data Dictionary for PHP Applications.
<?php
require_once 'std.table.class.inc';
class php_Session extends Default_Table
{
function php_Session ()
{
$this->dirname = dirname(__file__);
$this->tablename = 'php_session';
$this->dbname = 'audit';
$this->fieldspec = $this->getFieldSpec_original();
$this->audit_logging = false;
}
The member variable $session_open is for use in the close() method (see below).
The constructor forces the member variable $audit_logging to be false as updates to this database table are not to appear in the audit log.
function open ($save_path, $session_name)
{
return TRUE;
}
The open() function does not have to do anything as the database is not actually opened until control is passed to my DML class.
function close ()
{
if (!empty($this->fieldarray)) {
$result = $this->gc(0);
return $result;
}
return FALSE;
}
The close() function is responsible for calling the gc() function to perform garbage collection.
function read ($session_id)
{
$fieldarray = $this->_dml_getData("session_id='$session_id'");
if (isset($fieldarray[0]['session_data'])) {
$this->fieldarray = $fieldarray[0];
$this->fieldarray['session_data'] = '';
return $fieldarray[0]['session_data'];
} else {
return '';
}
}
The read() function is responsible for
retrieving the data for the specified session. Note that if there is no
data it must return an empty string, not the value NULL.
function write ($session_id, $session_data)
{
if (!empty($this->fieldarray)) {
if ($this->fieldarray['session_id'] != $session_id) {
$this->fieldarray = array();
}
}
if (empty($this->fieldarray)) {
$array['session_id'] = $session_id;
$array['date_created'] = getTimeStamp();
$array['last_updated'] = getTimeStamp();
$array['session_data'] = addslashes($session_data);
$this->_dml_insertRecord($array);
} else {
if (isset($_SESSION['logon_user_id'])) {
$array['user_id'] = $_SESSION['logon_user_id'];
}
$array['last_updated'] = getTimeStamp();
$array['session_data'] = addslashes($session_data);
$this->_dml_updateRecord($array, $this->fieldarray);
}
return TRUE;
}
The write() function is responsible for creating or updating the database with the session data which is passed to it.
function destroy ($session_id)
{
$fieldarray['session_id'] = $session_id;
$this->_dml_deleteRecord($fieldarray);
return TRUE;
}
If the session_destroy() function is issued in the code then this will be responsible for deleting the session data from the database.
function gc ($max_lifetime)
{
$real_now = date('Y-m-d H:i:s');
$dt1 = strtotime("$real_now -2 hours");
$dt2 = date('YmdHis', $dt1);
$count = $this->_dml_deleteSelection("last_updated < $dt2");
return TRUE;
}
}
?>
This is the garbage collection or "clean-up" function.
Notice that the time limit of 2 hours has been hard-coded. This means
that any session record which has not been modified within this time
limit Will be deleted.
Define session handler
This is as described in the manual for the session_set_save_handler() function. These lines must come before the session_start() function.
require_once 'classes/php_session.class.inc';
$session_class = new php_Session;
session_set_save_handler(array(&$session_class, 'open'),
array(&$session_class, 'close'),
array(&$session_class, 'read'),
array(&$session_class, 'write'),
array(&$session_class, 'destroy'),
array(&$session_class, 'gc'));
Notice that the arguments describe methods in a class and not stand-alone functions.
Conclusion
As you should be able to see it is a relatively straightforward
process to switch the recording of session data from ordinary disk
files to a database table. This overcomes the drawbacks inherent with
ordinary disk files:
- The session data is more secure as a potential hacker must be able to log into the database before he can access anything.
- The
use of multiple servers would not create a problem as all session data
now resides in a single central place and is accessible by all servers.
- It is much easier to query the database should the site
administrator require information about current sessions or current
users.
|