A friend of mine, who is working on a sick little diet and exercise tracking application, asked me today if PHP allowed singleton objects to be stored in a session. My first reaction was absolutely they can be so long as you follow the normal rules of unserializing objects in PHP… namely, making sure the class is in scope before attempting to create an object from it OR having an autoloader that does that for you. But as I thought about it more I began to think that maybe this might not work as expected since singletons generally have no public access to their constructors. However, when an object is unserialized, it is instantiated, for lack of a better term, and populated with the data that was exposed from the serialization process.
Note: for those of you that are unfamiliar with the way PHP handles session data or are just wondering why I am using serialization so much in my examples, the basics are you start a session and PHP assigns a random MD5 hash to it as an id. You write data to the session via the $_SESSION superglobal array and when the script terminates or you call session_write_close(), PHP serializes the $_SESSION array and writes that serialized string to the file system for retrieval on subsequent requests so long as the session time limit has not expired. I really did not want to run this entire session process and inspect what was being written to my file system so I decided to mock that interaction myself by creating an arbitrary array, serializing it, grabbing the serialized string and unserializing it.
As I began to dig into this I started playing with PHP’s magic __sleep() and __wakeup() methods. Those seemed to do what I wanted them to do except in the case of protected and private properties of my class. As soon as I added those in my serialization took a crap all over itself, even when I cloned the singleton object and got the public properties exposed through get_object_vars(). So, thanks to the PHP manual – the most awesome tool in any PHP developer’s arsenal – I discovered the PHP Serializable interface.
This interface defines two methods: serialize() and unserialize(). These methods override the default behavior of __destruct() and __construct() so if you are using these you should keep that in mind and either put your __destruct() and __construct() code into these methods or allow for calls to __destruct() and __construct() from within these methods. Your homework is to decide which is the better implementation.
Now, back to the lesson at hand… the following object – let’s say it is saved to a file named dummy.php – is a super simple implementation of a basic singleton pattern. It includes both a serialize() and unserialize() method to allow it to be serialized for storage.
<?php class DummySingleton implements Serializable { /** * Test properties of various visibility * @var string */ public $dProp; public $dPropX = 'Nothing to see here folks'; private $_prop = 'y u try see dis?'; /** * Instance holder for this singleton * * @var DummySingleton */ private static $_instance = null; /** * Private singleton constructor, simply sets a property */ final private function __construct() { $this->dProp = 'I was finally built'; } /** * Singleton instance getter * * @return DummySingleton */ public static function getInstance() { if (self::$_instance === null) { self::$_instance = new DummySingleton(); } return self::$_instance; } /** * Implementation of the Serializable interface, called automatically just * before this object is serialized. This will override any calls to a * __destruct method, so if you have a destructor you should call it from * here. * * @return string */ public function serialize() { return serialize(get_object_vars($this)); } /** * When this object is created from an unserialization this method is called. * It implements the Serializable interface and uses the data from the * serialize() method herein. Since this method overrides the __construct * method you should make any accomodations for constructing your object * in this method. */ public function unserialize($data) { // "Instantiate" our singleton self::getInstance(); // Set our values if (is_array($data)) { foreach ($data as $k => $v) { $this->$k = $v; } } } } ?> |
If you build up this object then serialize it, then rebuild it from the serialization, you can see that it remains in tact. Let’s test this by simulating the session array, first creating an array then adding the DummySingleton object to that array, then serializing it:
<?php // Grab the class include 'dummy.php'; // Get our instance $o = DummySingleton::getInstance(); // Set something into it for testing $o->setFromOutside = 'What the hell is this madness?'; // Make an array of stuff $a = array(); $a['name'] = 'Robert'; $a['id'] = 23; // Add the singleton $a['dummy'] = $o; // Serialize it $s = serialize($a); ?> |
The serialized output from this yields:
a:3:{s:4:"name";s:6:"Robert";s:2:"id";i:23;s:5:"dummy";C:14:"DummySingleton":187:{a:4:{s:5:"dProp";s:19:"I was finally built";s:6:"dPropX";s:25:"Nothing to see here folks";s:5:"_prop";s:16:"y u try see dis?";s:14:"setFromOutside";s:30:"What the hell is this madness?";}}} |
If you take that string and subsequently load it and unserialize it then inspect it, you’ll see that the singleton object has been restored along with all private properties of it:
<?php // $s is our serialized string from above $o = unserialize($s); var_dump($o); ?> |
Output:
array(3) {
["name"]=>
string(6) "Robert"
["id"]=>
int(23)
["dummy"]=>
object(DummySingleton)#1 (3) {
["dProp"]=>
NULL
["dPropX"]=>
string(25) "Nothing to see here folks"
["_prop":"DummySingleton":private]=>
string(16) "y u try see dis?"
}
} |
Since this is, in essence, the process that PHP follows for its default session management it’s safe to say that you can, indeed, store singleton objects in a PHP session. Now onto to bigger and better questions, like…
Why would you ever want to store a singleton object instance in a PHP session?