Session Handler and Registry

Coding Critique is the place to post source code for peer review by other members of DevNetwork. Any kind of code can be posted. Code posted does not have to be limited to PHP. All members are invited to contribute constructive criticism with the goal of improving the code. Posted code should include some background information about it and what areas you specifically would like help with.

Popular code excerpts may be moved to "Code Snippets" by the moderators.

Moderator: General Moderators

User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Session Handler and Registry

Post by Luke »

I built this session class in response to a recent thread. This is my first attempt at handling my own session data storage. Much of the session_set_save_handler functionality was borrowed from Jenk's post in that thread. If anything I did was wrong, please correct me. Recommendations, advice, etc. are welcome. Also, none of these classes have been tested... because I have never tested any classes before. I am going to attempt to test them myself, but if I have questions, I intend on asking them in this thread. Thanks for any help given!

Code: Select all

<?php
/**
* Registry class for storing just about anything
*
* This class is basically just a wrapper for an associative array. It
* can be used to store any type of data that an associative array can.
* The most common use for this class is to extend it to allow storage
* in almost any type of class that needs to store data such as
* a session class, an object registry, etc.
*
* @abstract
* @author       Luke Visinoni <luke.visinoni@gmail.com>
* @copyright    Luke Visinoni August 20th 2006
*/
class Registry{

	/**
	* Container for all registered data passed to this class
	* @access protected
	*/
	protected $entries = array();
	
	/**
	* Add a value to registry data
	* @access public
	* @param string $key 	Associative data array key
	* @param mixed	$value	Associative data array value
	*/
	public function register($key, $value){
		$this->entries[$key] = $value;
	}
	
	/**
	* Remove a value from registry data
	* @access public
	* @param string $key 	Associative data array key
	*/
	public function unregister($key){
		unset($this->entries[$key]);
	}
	
	/**
	* Retrieve a value from registry data
	* @access public
	* @param string $key 	Associative data array key
	* @return mixed
	*/
	public function get($key){
		return isset($this->entries[$key]) ? $this->entries[$key] : null;
	}
	
	/**
	* Check that a key exists within registry data
	* @access public
	* @param string $key 	Associative data array key
	* @return bool
	*/
	public function has($key){
		return $name ? isset($this->entries[$key]) : false;
	}
	
	/**
	* Destroy all information stored in this registry
	* @access public
	*/
	public function flush(){
		$this->entries = array();
	}
	
	/**
	* Magic wrapper for get()
	* @access public
	* @param string $key 	Associative data array key
	* @return mixed
	*/
	/*
	public function __get($key){
		return $this->get($key);
	}*/
	
	/**
	* Magic wrapper for register()
	* @access public
	* @param string $key 	Associative data array key
	* @param string $value 	Associative data array value
	*/
	/*
	public function __set($key, $value){
		$this->register($key, $value);
	}*/
}

/**
* Session handler
*
* A class for working with a session. This class is extended
* to provide a particular object or area of an application with its
* own namespace within the session data. This allows for different
* classes to play nicely together and not overwrite or delete session
* data they aren't supposed to.
*
* @author       Luke Visinoni <luke.visinoni@gmail.com>
* @copyright    Luke Visinoni August 20th 2006
*/
class Session extends Registry{
	
	/**
	* Container for session id
	* @access protected
	* @var string
	* @static
	*/
	protected static $id = null; 
	
	/**
	* This particular instance's namespace
	* @access public
	* @var string
	*/
	protected $namespace;			
	
	/**
	* Set to true if you would like for session's id to be regenerated
	* @access protected
	* @var bool
	*/
	protected $regenerate;
	
	/**
	* Constructor
	* @param string $namespace
	* @param bool $regenerate
	*/
	public function __construct($namespace='Session', $regenerate=null) {
		
        $this->namespace = $namespace;
        $this->regenerate = $regenerate;
		$this->start();
		
    }
    
	/**
	* Initialize the session
	* 
	* This method starts the session. Once the session is started, it tries to correct
	* a known IE problem. (Need to find out more about this problem). Now it checks
	* regenerates the session id, if specefied, and associates entries with our session.
	* 
	* @access public
	* @todo research ie problem that the session_cache_limiter is supposed to fix
	*/
    public function start(){
		if(session_id() == ''){
			session_start();
			if (strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE')){
				session_cache_limiter('must-revalidate');
			}
			self::$id = session_id();
		}
		if ($this->regenerate){
			session_regenerate_id();
		}
		$this->entries =& $_SESSION[$this->namespace];
    }
    
	//public function __destruct(){
	//	session_write_close();
	//}
	
	public function __get($key){
		return $this->get($key);
	}
	
	public function __set($key, $value){
		$this->register($key, $value);
	}
}

interface SessionSaveHandler{
	public static function open();
	public static function close();
	public static function read($id);
	public static function write($id, $data);
	public static function destroy($id);
	public static function clean($max);
}

/**
* Session Handler for storing data in a mysql database
*
* @author       Luke Visinoni <luke.visinoni@gmail.com>
* @copyright    Luke Visinoni August 20th 2006
* @todo adjust session_set_save_handler methods so that database type and other factors may be dealt with before write/read/etc.
* @todo Allow for use with other than mysql
*/
class MysqlSession extends Session implements SessionSaveHandler{
	
	/**
	* Holds a mysql connection resource link
	* @access private
	* @var mysql connection resource link
	* @todo Set up error handling to use exceptions
	*/
	private static $db;
	
	public function __construct($namespace, $regenerate, $db){
		if(is_null($namespace)) $namespace = 'Session';
		if(is_null($regenerate)) $regenerate = false;
		if(!is_resource($db)){
			// I still need to learn exception handling, but this works for now.
			die('Third parameter must be a valid mysql resource link.');
		}
		self::$db =  $db;
		session_set_save_handler(array(&$this,"open"),
								 array(&$this,"close"),
								 array(&$this,"read"),
								 array(&$this,"write"),
								 array(&$this,"destroy"),
								 array(&$this,"clean"));
		//register_shutdown_function('session_write_close');
		parent::__construct($namespace, $regenerate);
	}
	public static function open(){
		return self::$db;
	}
	public static function close(){
		// No need to close my database connection, since it is shared with the rest of the app
		return true;
	}
	
	/**
	* Reads data from the data source
	* @access public
	* @static
	* @returns string
	*/
	public static function read($id){
		$id = mysql_real_escape_string($id, self::$db); 
	 
		$sql = "SELECT `data` 
				FROM   `session` 
				WHERE  `session_id` = '" . $id . "'"; 
	 
		if ($result = mysql_query($sql, self::$db)) { 
			if (mysql_num_rows($result)) { 
				$record = mysql_fetch_assoc($result); 
				return $record['data']; 
			} 
		} 
		return ''; 
	}
	
	/**
	* Writes serialized data to data source
	* @access public
	* @static
	* @returns bool
	*/
	public static function write($id, $data){
		$access = time(); 
	 
		$id = mysql_real_escape_string($id, self::$db); 
		$access = mysql_real_escape_string($access, self::$db); 
		$data = mysql_real_escape_string($data, self::$db); 

		$sql = "REPLACE 
				INTO `session` (`session_id`, `access`, `data`)
				VALUES('" . $id . "', " . $access . ", '" . $data . "')";
		return mysql_query($sql, self::$db);
	}
	
	/**
	* Delete session data associated with this id
	* @access public
	* @static
	* @returns bool
	*/
	public static function destroy($id){
		$id = mysql_real_escape_string($id, self::$db); 
	 
		$sql = "DELETE 
				FROM   `session` 
				WHERE  `session_id` = '" . $id . "'"; 
	 
		return mysql_query($sql, self::$db);
	}
	
	/**
	* Delete old session data
	* @access public
	* @static
	* @returns bool
	*/
	public static function clean($max){
		$old = time() - $max; 
		$old = mysql_real_escape_string($old, self::$db); 
	 
		$sql = "DELETE 
				FROM   `session` 
				WHERE  `access` < '" . $old . "'"; 
	 
		return mysql_query($sql, self::$db);
	}
}



// Usage
$DB = new Database('mysql://user:xxxx@localhost/my_database');
// This is kind of a fix for the optional params problem... 90% of the time, I would supply the first argument anyway, so I think this will work just fine
$Session = new MysqlSession(null, null, $DB->getLink());
$Session->register('foo', 'bar');
$Session->magic = "This variable was magically registered";
?>
Last edited by Luke on Tue Aug 22, 2006 3:27 pm, edited 1 time in total.
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post by Jenk »

I'm not too sure on the internals of session_set_save_handler, but you may want to change the array params to:

Code: Select all

array(get_class($this) => 'write')
as the session handling stuff happens after all objects have been destroyed, and even though the manual and other blogs/articles say that you can use objects, I just couldn't get it to work for what I presume is the above reason. Hence why you can't use __destruct()

but session_write_close may negate all that and I may need to read the manual before posting.


EDIT: btw, I stole pretty much my entire class from Chris Shiflett's article on using session_set_save_handler, so credit where it is due. :)
Last edited by Jenk on Mon Aug 21, 2006 3:45 am, edited 1 time in total.
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post by Luke »

it seems to be working alright.
User avatar
Oren
DevNet Resident
Posts: 1640
Joined: Fri Apr 07, 2006 5:13 am
Location: Israel

Post by Oren »

Jenk wrote:EDIT: btw, I stole pretty much my entire class from Chris Shiflett's article on using session_set_save_handler, so credit where it is due. :)
I believe we all did that :P
User avatar
Adesso
Forum Newbie
Posts: 9
Joined: Fri Jan 21, 2005 3:49 am
Location: Germany

Post by Adesso »

Just a note:

Code: Select all

if(!is_a($db, 'ADOConnection')){
This operater of your extended class is depricated, and should be replaced with instanceof if you want this to be true PHP5. I alos find that the Exception handling can be improved. I am currently working at writing a similar class...
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post by Jenk »

FYI: you can scratch your @todo for function clean() because $max is the time in miliseconds that a session expires, the PHP engine provides this time.

this is set in the php.ini session.max_lifetime directive :)

Also, for the above post you can now type hint objects (and arrays, but not primitives as yet :():

So this:

Code: Select all

public function __construct($namespace='Session', $regenerate=null, $db){ 
                if(!is_a($db, 'ADOConnection')){ 
                        // I still need to learn how to handle exceptions properly, so forgive me if this is not right 
                        throw new Exception('Third parameter must be of type: "ADOConnection"'); 
                }
can become simply this:

Code: Select all

public function __construct($namespace='Session', $regenerate=null, ADOConnection $db){
:)

And furthermore.. when specifying optional arguments like you have done, you should always declare mandatory arguments first:

Code: Select all

public function __construct(ADOConnection $db, $namespace='Session', $regenerate=null){
otherwise your optional args are no longer optional :)
User avatar
Adesso
Forum Newbie
Posts: 9
Joined: Fri Jan 21, 2005 3:49 am
Location: Germany

Post by Adesso »

I like the suggestion of using hints, but am not sure how exceptions can be handled in such cases...
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post by Jenk »

They don't, you receive a fatal error if you pass an argument for $db that isn't of type ADOConnection in that case.

thrown exceptions go with try {} catch () {} blocks, or set_exception_handler()

try/catch blocks being my preference - and it really is down to preference whether you chose to try/catch every exception, or set a handler.

It would also be better to create sub classed exceptions instead of using Exception class everytime so you can indentify what type of exception is thrown.

PHP5 now has a number of predefined exception types available for use, see this page for more info (php manual on exceptions) and execute:

Code: Select all

<?php

$classes = get_declared_classes();
$exceptions = "<b>PHP predefined exceptions list:</b><br />\n";

foreach ($classes as $class) {
    if (substr($class, -9) == 'Exception') {
        $exceptions .= $class . "<br />\n";
    }
}

echo $exceptions;
?>
to see a list of predefined exceptions.
User avatar
Adesso
Forum Newbie
Posts: 9
Joined: Fri Jan 21, 2005 3:49 am
Location: Germany

Post by Adesso »

Point made.. I am with you on the try catch, as it just makes more sense fro me..

I must say that the more I dismantel this class, the more I dislike it.. There is some nice ideas, but some really bad mistakes too. I think that maybe in future a copy and paste and claiming it as your own is not a good way to start a class...

I think I am starting again from scratch.. :roll:
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post by Jenk »

Just to nitpick, Ninja did give credit that he took ideas from elsewhere.. and this is the coding critique forum - the place where you post code with the intent of receiving feedback both good and bad :)
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post by Luke »

Adesso wrote:copy and paste and claiming it as your own
Are you saying that's whay I did? If so, I think you are confused. The only copy and pasted part of this is the session handler, and there really isn't a more 'inventive' way to do it. I suppose I could completely remove $_SESSION from the class and handle the entire session inside my class... but I have found that PHP handles sessions pretty well, so why reinvent the wheel.
User avatar
julian_lp
Forum Contributor
Posts: 121
Joined: Sun Jul 09, 2006 1:00 am
Location: la plata - argentina

Post by julian_lp »

Just to know where to put attention...

Are you updating the original class (first comment in this thread) or should we look for new posts below?
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post by Luke »

julian_lp wrote:Just to know where to put attention...

Are you updating the original class (first comment in this thread) or should we look for new posts below?
Good question... I think I will update the original post so as not to clutter the thread with a hundred updates. Thanks for asking!
User avatar
julian_lp
Forum Contributor
Posts: 121
Joined: Sun Jul 09, 2006 1:00 am
Location: la plata - argentina

Post by julian_lp »

The Ninja Space Goat wrote:
julian_lp wrote:Just to know where to put attention...

Are you updating the original class (first comment in this thread) or should we look for new posts below?
Good question... I think I will update the original post so as not to clutter the thread with a hundred updates. Thanks for asking!
Great. You could write a new comment just saying "hey, I've edited the class" so us all will be mailed ;)

BTW: Does ADOdb handle functions like mysql_real_escape internally?
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post by Luke »

julian_lp wrote:
The Ninja Space Goat wrote:
julian_lp wrote:Just to know where to put attention...

Are you updating the original class (first comment in this thread) or should we look for new posts below?
Good question... I think I will update the original post so as not to clutter the thread with a hundred updates. Thanks for asking!
Great. You could write a new comment just saying "hey, I've edited the class" so us all will be mailed ;)

BTW: Does ADOdb handle functions like mysql_real_escape internally?
No, I don't think so. I think you have to use ADOConnection::qstr() I can't quite recall. For now I am going to remove the adodb dependancy and just rely on mysql since it's using REPLACE anyway which is mysql-only. I will bring ADOdb back into the class once I find the time. I will be updating it probably late tonight to my new version.
Post Reply