Practical PHP Patterns: Server Session State
Join the DZone community and get the full member experience.
Join For FreeThe Server Session State pattern is an umbrella term used to describe the various techniques used to keep the user session state on the server, with the only requisite of a session id on the clients to distinguish between different users.
A common implementation of this pattern in languages like Java and PHP is a centralized data source with recursive associative maps where the session ids are the keys and associative arrays, containing the user session data, are values.
Implementation
The initial assumption of the Server State pattern was that there is only one server, where a simple data structure should be stored and maintained throughout the different HTTP requests. However all the assumptions can be eliminated to complicate the solution at will and make it work in rather complex environments, like a cluster of servers.
PHP has out of the box support for this pattern, with the session_*() functions and the $_SESSION superglobal variable (see the example below). Essentially you can mostly use the $_SESSION array like an ordinary variable, with the slight difference that it wil be automatically saved and recreate in every PHP script called by the same user.
Since it is not efficient to occupy a segment of the central memory for all the clients at the same time, what you put in $_SESSION is serialized and unserialized between requests. This means that scalar values and arrays are quite simple to put in the session, while object structures may be tricky. PHP however has also automatic serialization of all the types, so the difficulty isn't in creating a string representation of them.
In basic environments, this pattern has a greater simplicity with respect to Client Session State, since we do not transmit data over a loosely typed protocol like HTTP, but simply we keep it right next to the PHP scripts.
Issues
There are many implementation issues that can arise while using this pattern when the original assumptions fail. The most important are:
Problem: server resources (like a database connection) are in the object graph to store.
A simple solution is storing only graphs which do not have references to transient script resources, by decoupling them completely from resources and shared information. This also solves race conditions with data that should be shared between scripts instead of saved in the session.
A more complex, but scalable solution is to write a Memento implementation. The state of an object or an object graph is saved into an external data structure created and consumed by itself.
In PHP, a particular Memento can be obtained by defining custom __sleep() and __wakeup() magic methods, although I suggest to only user the former. A __sleep() method should define an array whose values are the name of the fields to serialize, and so can exclude the resources from this list. For reconnecting the object instead, __wakeup() methods expose a bootstrap problem (they must refer to static classes or singletons in order to work).
Problem: class versioning (classes change but object stored remain the same)
This is only an issues with long running sessions. It's quite acceptable for the user to lose session data when a codebase update is rolled out (maybe unfortunately). We certainly do not want to replicate database migration issues in the sessions world too.
Problem: server clusters are in place
Where more than one server host the application, the session data must be shared between all of them, or the user must be sent by the balancing mechanisms always to the same server. The latter mechanism presents a problem on failover: a server going down will lose the session information of its groups of users.
Problem: lifetime
Sessions are usually not closed explicitly (there is no interaction where the client decides to close them) unless you decide to create a Logout button that the user will promptly ignore.
Thus, sessions data should be thrown away after a certain time, or after a period of inactivity from the user.
Problem: session ids can be hijacked
You can regenerate the id after a certain number of requests, to decrease the probability that an external host can succeeed with a brute force attack. Attacks which pass a forgered session id in the URL are usually already blocked by the php.ini session.use_only_cookies directive.
Example
The semantic of $_SESSION is that of a variable shared between HTTP requests. Given that PHP has no shared memory between requests (often served by different processes), one can wonder how it is done.
When using $_SESSION, PHP saves a cookie with the name PHPSESSID, which identifies the client and points to a map in the server's memory. If output buffering is not used, sessions must be started before any output is produced, or you'll see an Headers already sent error, like for server-side redirection.
<?php
$user = new stdClass();
$user->nick = 'giorgio';
$user->permissions = array('forum', 'wiki');
$_SESSION['user'] = $user;
//in other scripts
if (in_array('forum', $_SESSION['user']->permissions)) {
echo "You can write new posts.\n";
}
It's also quite common to build an abstraction over the plain $_SESSION array (such as Zend_Session_Namespace), to allow sharing the variable between components and to ease testing from the command line where $_SESSION is not available. Mock objects can then be injected to manage the storage without trying to set session cookies on a terminal.
Different storages
The session_set_save_handler() comes handy when using different storages from the in-memory one. It accepts some callback functions that will be called while opening or closing the storage, and reading or writing variable. In the relative manual page, you can find a file-based custom implementation and its setup code.
Opinions expressed by DZone contributors are their own.
Comments