Using Advanced Functions to Maintain the State of Applications with PHP Sessions

Here we are again. This is part two of the series “Maintaining the state of applications with PHP sessions.” In three parts, this series ranges from the basics of session management in PHP, such as creating, registering session data, and destroying sessions, to exploring advanced concepts, like working with different session storage modules and creating custom session handling objects.

Introduction

Before I dive into the subject of this second tutorial, I’ll go quickly over the topics that I explained in the first article, so it will be easier for you to understand some of the advanced PHP session features that I plan to discuss in the next few lines. As you’ll certainly recall, I started this series explaining the basic features of the PHP session management module, and showed some illustrative examples of how to create PHP sessions and store session data. In addition, I explained how to manipulate session identifiers and retrieve and manage some of the most common session-related settings included within the corresponding php.ini configuration file.

Based on the features that you saw previously, getting started using the basics of PHP sessions is really a straightforward process, which before long can help you in the development of simple session-based scripts with minor hassles. Moreover, if you’re a PHP developer with an intermediate grounding in PHP sessions, then probably you’ve already used some of the advanced features included in the corresponding session management module.

On the other hand, if you’re still not that familiar with the set of advanced session functions exposed by PHP, in this article I’ll take a look at them, in order to demonstrate with several code samples how to use them and how to take advantage of their many capabilities. Hopefully, when you finish reading this article, you should have a decent understanding of how to include advanced session handling routines within your own PHP-driven applications.

After defining the guidelines for this tutorial, it’s time to go ahead and explore some of most interesting PHP session-related functions. Let’s get started.

{mospagebreak title=Tweaking the PHP session storage module: using the “session_set_save_handler ()” function}

As you learned before, the PHP built-in session management mechanism uses as default the “/tmp” directory, in order to store session data, which is first serialized and then saved in the dynamically-generated session file (remember that session IDs also are saved in cookies in the client).

However, due to the versatility of the session module, it’s possible to modify this behavior and change the location –- and eventually the way –- in which session data is stored. Regarding this useful feature, PHP provides developers with the ability to define six callback functions, which can be used in conjunction with the “session_set_save_handler()” function, in order to construct a user-defined session storage module.

In this case, a custom callback function must be defined for each of the corresponding operations performed to manage a particular session, that is when reading, writing and destroying session data, and when the PHP built-in garbage collection mechanism is triggered. This process can be translated into the definition of six generic functions, which are listed below:

function open(){
    // called when a session is opened
}
function close(){
    // called when a session is closed
}
function read(){
    // called when session is read
}
function write(){
    // called when session is written
}
function destroy(){
    // called when session is destroyed
}
function gc(){
    // called when the garbage collection mechanism is triggered
}

As you can see, each of the above defined functions can be used in conjunction with the “session_set_save_handler()” function. This is handy for constructing a custom session mechanism, which eventually can be more efficient and secure that the one provided as the default by PHP.

In order to illustrate this useful concept, first I’ll show you a basic and admittedly inefficient implementation, which utilizes user-defined callback functions to store session data on a different directory. Here’s the corresponding code example:

// define new file path for storing session data
define(“SESSION_PATH”,”c:session_path”); // double
backslashes is used on Windows systems
// not required to implement ‘open()’ method
function open(){
    return true;
}
// not required to implement ‘close()’ method
function close(){
    return true;
}
// define ‘read()’ method
function read($id){
    $sessionFile=SESSION_PATH.’sess_’.$id;
    if(!file_exists($sessionFile)||!
$sessionData=@file_get_contents($sessionFile)){
        return “”;
    }
    return $sessionData;
}
// define ‘write()’ method
function write($id,$sessionData){
    if(!$fp=fopen(SESSION_PATH.’sess_’.$id,’w’)){
        return “”;
    }
    return fwrite($fp,$sessionData);
}
// define ‘destroy()’ method
function destroy($id){
    // delete session file
    return unlink(SESSION_PATH.’sess_’.$id);
}
function gc($maxlifetime){
    $sessionFile=SESSION_PATH.’sess_’.$id;
    if(filemtime($sessionFile)>(time()-$maxlifetime)){
        unlink($sessionFile);
    }
    return true;
}
// define custom session handling functions
session_set_save_handler
(‘open’,’close’,’read’,’write’,’destroy’,’gc’);
// use sessions as one would expect
session_start();
// register some session variables
$_SESSION['firstname']=’Alejandro';
$_SESSION['lastname']=’Gervasio'; 

In short, what I’ve done with the above example is define the corresponding six callback functions that will be called up during the regular occurrence of a session. Notice that the first two functions, “open()” and close(),” don’t need to be concretely defined, thus they’re simply specified as empty.

The remaining functions take care of reading, writing and eventually deleting session data, by using the value of the constant “SESSION_PATH,” which I defined right at the beginning of the script. Of course, as I said before, this isn’t the best way of building a user-defined session storage module, but my intention is that you learn how these callback functions are used as parameters by the “session_set_save_handler()” function.

Once the “session_set_save_handler()” function is invoked with the proper callback functions, all the session data will be stored and read from the “C:session_path” directory (you can change this, in case you’re using a UNIX-based operating system), which means that all the session files will be created within the mentioned directory. In case you want to see how this session mechanism works, first run the above script and use the $_SESSION superglobal array to store some session data. Then open the custom directory, and you should see that PHP has created the corresponding session files on that directory.

Nevertheless, I said the previous example was pretty useless. Why? Well, in fact there’s no need to reinvent the wheel if you just want to change the physical directory where session data is saved. Fortunately, PHP offers the “session_save_path()” function, which takes as an argument the new path where you want session data to be stored. Being aware of the existence of this function, the entire prior example might be rewritten as follows:

// use ‘session_save_path()’ function
session_save_path(“c:session_path”);// double backslashes is
used on Windows systems
session_start();
// register some session variables
$_SESSION['firstname']=’Alejandro';
$_SESSION['lastname']=’Gervasio';

Definitely, you’ll agree with me that the above script is much simpler to code and read. And the best thing is that you’ll get the same result, just using the “session_save_path()” function. However, the example that you learned before for using callback functions is quite good for introducing the “session_set_save_handler()” function, and serves as a nice preface for showing a more useful example of how to use this function. To see the development of a more complex example, please go ahead and keep on reading.

{mospagebreak title=Going deeper into PHP session management: creating a MySQL-based session storage module}

After demonstrating how the “session_set_save_handler()” can be used to create a personalized session handling mechanism, I’ll go one step further by creating a new set of callback functions, which uses a MySQL database table for managing sessions. First, the definition for the corresponding callback functions is listed below:

// define ‘openSession()’ function
function openSession($sessionPath,$sessionName){
    return true;
}
// define ‘closeSession()’ function
function closeSession(){
    return true;
}
// define ‘readSession()’ method
function readSession($sessionId){
    global $db;
    // escape session ID
    if(!get_magic_quotes_gpc()){
        $sessionId=mysql_real_escape_string($sessionId);
    }
    $result=$db->query(“SELECT sessiondata FROM sessions WHERE
sessionid=’$sessionId’ AND expiry > NOW()”);
    if($result->countRows()>0){
        $row=$result->fetchRow();
        return $row['sessiondata'];
    }
    // return empty string
    return “”;
}
// define ‘writeSession()’ function
function writeSession($sessionId,$sessionData){
    global $db;
    $expiry=time()+get_cfg_var(‘session.gc_maxlifetime’)-1;
    // escape session ID
    if(!get_magic_quotes_gpc()){
        $sessionId=mysql_real_escape_string($sessionId);
    }
    $result=$db->query(“SELECT sessionid FROM sessions WHERE
sessionid=’$sessionId'”);
    // check if a new session must be stored or an existing one
must be updated 
    ($result->countRows()>0)?$db->query(“UPDATE sessions SET
sessionid=’$sessionId’,expiry=’$expiry’,
sessiondata=’$sessionData’ WHERE sessionid=’$sessionId'”):$db-
>query(“INSERT INTO sessions (sessionid,expiry,sessiondata)
VALUES (‘$sessionId’,’$expiry’,’$sessionData’)”);
    return true;
}
// define ‘destroySession()’ function
function destroySession($sessionId){
    global $db;
    // escape session ID
    if(!get_magic_quotes_gpc()){
        $sessionId=mysql_real_escape_string($sessionId);
    }
    $db->query(“DELETE FROM sessions WHERE
sessionid=’$sessionId'”);
    return true;
}
// define ‘gcSession()’ function
function gcSession($maxlifetime){
    global $db;
    $db->query(“DELETE FROM sessions WHERE expiry < NOW()”);
    return true;
}

As you can see, in the above example I defined the six callback functions that will be passed as arguments to the “session_set_save_handler(),” in such a way that they use a simple MySQL database table in order to read and write session data. This database table stores session IDs, the serialized session data, and finally a timestamp, which can be useful for assigning a time expiration to a particular session, so it can be deleted from the mentioned database table when the PHP garbage collection mechanism is appropriately triggered. (Notice that this situation may vary from system to system, in accordance with some settings of the session module within the php.ini file, such as the “session.gc_maxlifetime” and “session.gc_probability” directives).

Also, notice that most of the callback functions use a global $db variable, in order to have access to an instance of a MySQL wrapping class, which is utilized to connect to MySQL, together with performing all the SQL queries against the respective database table. Also, a MySQL result set processing class is used internally, in this case represented by the $result variable. Regarding these classes, in the next section I’ll show you their corresponding definition, thus you can have at hand all the source code required for implementing the previous MySQL-based session mechanism. Keep on reading to learn more.

{mospagebreak title=Getting the MySQL-based session module complete: listing MySQL processing classes}

As I promised, here’s the source code for the couple of MySQL processing classes used within the definition of the prior callback functions:

class MySQL {
    var $conId; // connection identifier
    var $host; // MySQL host
    var $user; // MySQL username
    var $password; // MySQL password
    var $database; // MySQL database
    // constructor
    function MySQL($options=array()){
        // validate incoming parameters
        if(count($options)>0){
            foreach($options as $parameter=>$value){
                if(empty($value)){
                    trigger_error(‘Invalid parameter
‘.$parameter,E_USER_ERROR);
                }
                $this->{$parameter}=$value;
            }
            // connect to MySQL
            $this->connectDB();
        }
        else {
            trigger_error(‘No connection parameters were
provided’,E_USER_ERROR);
        }
    }
    // connect to MYSQL server and select database
    function connectDB(){
        if(!$this->conId=mysql_connect($this->host,$this-
>user,$this->password)){
            trigger_error(‘Error connecting to the
server’,E_USER_ERROR);
        }
        if(!mysql_select_db($this->database,$this->conId)){
            trigger_error(‘Error selecting
database’,E_USER_ERROR);
        }
    }
    // perform query
    function query($query){
        if(!$this->result=mysql_query($query,$this->conId)){
            trigger_error(‘Error performing query
‘.$query,E_USER_ERROR);
        }
        // return new Result object
        return new Result($this,$this->result); 
    }
}

class Result {
    var $mysql; // instance of MySQL object
    var $result; // result set
    function Result(&$mysql,$result){
        $this->mysql=&$mysql;
        $this->result=$result;
    }
    // fetch row
    function fetchRow(){
        return mysql_fetch_array($this->result,MYSQL_ASSOC);
    }
    // count rows
    function countRows(){
        if(!$rows=mysql_num_rows($this->result)){
            return false;
        }
        return $rows;
    }
    // count affected rows
    function countAffectedRows(){
        if(!$rows=mysql_affected_rows($this->mysql->conId)){
            trigger_error(‘Error counting affected
rows’,E_USER_ERROR);
        }
        return $rows;
    }
    // get ID from last inserted row
    function getInsertID(){
        if(!$id=mysql_insert_id($this->mysql->conId)){
            trigger_error(‘Error getting ID’,E_USER_ERROR);
        }
        return $id;
    }
    // seek row
    function seekRow($row=0){
        if(!mysql_data_seek($this->result,$row)){
            trigger_error(‘Error seeking data’,E_USER_ERROR);
        }
    }
    function getQueryResource(){
        return $this->result;
    }
}

Right. Now that I showed you the pair of MySQL processing classes used by the previous callback functions, let me set up an example which implements this user-defined session storage system, by the respective “session_set_save_handler()” function. Have a look at the code listed below:

// include classes
require_once ‘mysqlclass.php';
require_once ‘resultclass.php';
// connect to MySQL
$db=&new MySQL(array
(‘host’=>’localhost’,’user’=>’user’,’password’=>’password’,
‘database’=>
‘database’));
// use ‘session_set_save_handler function’
session_set_save_handler
(‘openSession’,’closeSession’,’readSession’,’writeSession’,
‘destroySession’,
‘gcSession’);
session_start();
// register some session variables
$_SESSION['firstname']=’Alejandro';
$_SESSION['lastname']=’Gervasio';

That’s it. After connecting to MySQL, the above script uses the “session_set_save_handler()” in order to register all the callback functions that you saw before, and as a result, all the session data will be stored in a sample “sessions” database table. By tweaking the correct session settings within the php.ini file, in conjunction with implementing this MySQL-driven session storage module, it’s possible to construct a more efficient and secure session management mechanism than the one provided as default by PHP. As you’ve seen, the experience can be instructive and educational, so why don’t you try it for yourself?

Wrapping up

That’s all for the moment. Over this second part of the series, I explored the powerful “session_save_path()” and “session_set_save_handler()” functions. Particularly, this last function can be extremely helpful for developing a custom session management system that uses a MySQL database table for storing session-related data, instead of conventional flat files.

Since this approach is used in many situations where a personalized session storage mechanism is preferred over the default offered by PHP, in the last article, I’ll encapsulate all the pertinent callback functions defined before within a class. In this way, the entire session handling process can be centralized at only one handler object. Therefore, don’t miss the next part!

[gp-comments width="770" linklove="off" ]

chat