In my current job I’m working with some PHP codes for which I’ve implemented the singleton pattern. My testcase is quite straightforward and consists of a singleton that stores the time at which it was instantiated:
class MySingleton {
private static $myVar;
private static $instance;
private function MySingleton() {
self::$myVar = time();
}
public static function getInstance() {
if (! isset(self::$instance)) {
self::$instance = new MySingleton();
}
return self::$instance;
}
public function getMyVar() {
return self::$myVar;
}
}
echo "Time: " . MySingleton::getInstance()->getMyVar();
Now, let’s test it. This is the result for the first invocation:
Time: 1274882765
and now a second invocation:
Time: 1274882775
So what’s going on here? PHP architecture is designed to fork a new process on every page request. That means that the singleton only lasts for a request. On the next request, it is instantiated again, that’s why we get different timestamps. What if we want for the singleton to span several requests, i.e. a session? Here is a modified version:
class MySingleton {
private static $myVar;
private static $instance;
private function MySingleton() {
self::$myVar = time();
}
public static function getInstance() {
if (! isset(self::$instance)) {
self::$instance = new MySingleton();
}
return self::$instance;
}
public function getMyVar() {
return self::$myVar;
}
}
session_start();
if (! isset($_SESSION["mySingleton"])) {
$_SESSION["mySingleton"] = MySingleton::getInstance()->getMyVar();
}
echo "Time: " . $_SESSION["mySingleton"];
The result is weird:
Time:
There’s no output because I used “static” scope for $myVar in MySingleton and PHP won’t serialize it. So I must remove the “static” keyword to make it an instance variable:
myVar = time();
}
public static function getInstance() {
if (! isset(self::$instance)) {
self::$instance = new MySingleton();
}
return self::$instance;
}
public function getMyVar() {
return $this->myVar;
}
}
session_start();
if (! isset($_SESSION["mySingleton"])) {
$_SESSION["mySingleton"] = MySingleton::getInstance();
}
echo "Time: " . $_SESSION["mySingleton"]->getMyVar();
and now it works wonderfully thru all invocations… well, at least for the same session. If there are multiple sessions, each of them will have their own singleton so at any point in time, there will be N “singletons” given N clients.
In conclusion
PHP singletons are request scoped, or at most session scoped. However, it might not worth the effort to span its scope to session since PHP has to serialize sessions to disk, memcached, or whatever mechanism used for session serialization, and it might lead to an I/O problem. You should weight the cost of instantiation of objects and session serialization. In general, I would recommend session variables as an exception. Try to externalize states by embedding them to the URL (such as the current page in a paginated request) and reserve session variables for simple variables such as page hits counter in a session fashion.