I'm working on a set of components (that will hopefully become a full framework) and am currently working on one to provide an abstraction of PHP sessions.
I'm trying to make the code as testable as possible, but a session class, by definition, is going to rely on global state in the form of the $_SESSION superglobal.
I've tried to implement my session class in such a way that $SESSION and session* functions only get called in one place, which I can then override in PHPUnit for testing purposes, but I can't help but wonder if there's a better way of doing this.
If you can suggest a better approach to making a testable session class then I'd appreciate any input you may have.
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
namespace gordian\reefknot\storage\session;
use gordian\reefknot\storage;
/**
* Session management
*
* Each instance of the Session object represents a "namespace" within the PHP
* $_SESSION system. This allows sessions to be easily managed and organized
* within an application
*
*/
class Session implements storage\iface\Crud, iface\Session
{
protected
$name = '',
$storage = NULL;
/**
* Add a new item to the session
*
* @param mixed $data
* @param string $key
* @return Session
* @throws \InvalidArgumentException Thrown if no name is provided
*/
public function createItem ($data, $key)
{
if (!empty ($key))
{
$key = (string) $key;
if (($this -> storage === NULL)
|| (!array_key_exists ($key, $this -> storage)))
{
$this -> storage [$key] = $data;
}
}
else
{
throw new \Exception ('No valid key given');
}
return ($this);
}
/**
* Delete the specified key
*
* @param string $key
* @return Session
*/
public function deleteItem ($key)
{
unset ($this -> storage [$key]);
return ($this);
}
/**
* Retrieve the data stored in the specified key
*
* @param type $key
* @return mixed
*/
public function readItem ($key)
{
return (array_key_exists ($key, $this -> storage)?
$this -> storage ['key']:
NULL);
}
/**
* Update a previously stored data item to a new value
*
* @param mixed $data
* @param string $key
*/
public function updateItem ($data, $key)
{
if ($this -> storage === NULL)
{
throw new \RuntimeException ('Session contains no data');
}
if (array_key_exists ($key, $this -> storage))
{
$this -> storage [$key] = $data;
}
return ($this);
}
/**
* Clear the session of all stored data
*
* @return Session
*/
public function reset ()
{
$this -> storage = NULL;
return ($this);
}
/**
* Retrieve all data stored in the session
*
* @return array
*/
public function getAll ()
{
return ($this -> storage);
}
/**
* Return whether there is data stored in this session
*
* @return bool
*/
public function hasData ()
{
return (!empty ($this -> storage));
}
/**
* Initialize the back-end storage for the session
*
* This method provides access for this class to the underlying PHP session
* mechanism.
*
* @return bool Whether the newly initialized session contains data or not
* @throws \RuntimeException Will be thrown if the session failed to start
*/
protected function initStorage ()
{
// Check that storage hasn't already been initialized
if ($this -> storage === NULL)
{
// Attempt to start the session if it hasn't already been started
if ((session_id () === '')
&& ((headers_sent ())
|| ((!session_start ()))))
{
throw new \RuntimeException ('Unable to start session at this time');
}
// Alias our instance storage to the named $_SESSION variable
$this -> storage =& $_SESSION [$this -> name];
}
return ($this -> hasData ());
}
/**
* Class constructor
*
* @param string $sessName
* @throws \InvalidArgumentException Thrown if no session name is provided
*/
public function __construct ($sessName)
{
if (!empty ($sessName))
{
$this -> name = $sessName;
$this -> initStorage ();
}
else
{
throw new \InvalidArgumentException ('Session must have a name');
}
}
}
For testing, the current plan is to replace initStorage() with a method that just sets up an internal array instead. If you can suggest a better approach I'd be keen to hear it.
if i undestand correctly..
create an abstraction of the native session management so that your session storage helper doesn't need to actually do any of the session_* calls or access $_SESSION directly.
Have two implementations of it, one that actually does the right thing, the other than fakes session_*() and $_SESSION, and in your constructor you just call SESSIONCLASS::start() and SESSIONCLASS::getVar(name). then you can test "Session" completely.