Singleton implementation

PHP 5.3 permits a proper singleton implementation. But before giving any examples using the new functionality provided by PHP 5.3 lets first explore our possiblities with PHP 5.2.

This is a simple example of a singleton implementation:

<?php
class singleton_example
{
private static $instance;

public function get_instance() {
if(!is_object(self::$instance)) {
$c = __CLASS__;
self::$instance = new $c();
}
return self::$instance;
}
}

$obj = singleton_example::get_instance();
print get_class($obj);
?>

This example works but is far from being comfortable to use because for each singleton class you will have redundant code (the get_instance() method). It would be much better if the classes could inherit one basic singleton class. The get_instance() method of this basic class would have to detect somehow which object is requested so it could create it. Here is an example using the magic constant __CLASS__:

<?php
class singleton
{
private static $instance;

public function get_instance() {
if (!is_object(self::$instance)) {
$c = __CLASS__;
self::$instance = new $c();
}
return self::$instance;
}
}

class singleton_test extends singleton
{
}

$obj = singleton_test::get_instance();
print get_class($obj);
?>

Unfortunately this code does not procude the expected result as __CLASS__ contains the name of the class in which it is referred not the context in which it is referred (it is defined at compile-time). So this code will produce instances of "singleton" not the "test_singleton".

We can try to help our singleton class to guess the requested class by providing him with a hint in the form of a defined variable  in the child class containing the class name:

<?php
class singleton
{
private static $instance;

public function get_instance() {
if (!is_object(self::$instance)) {
$c = self::$class;
self::$instance = new $c();
}
return self::$instance;
}
}

class singleton_test extends singleton
{
protected static $class = __CLASS__;
}

$obj = singleton_test::get_instance();
print get_class($obj);
?>

Unfortunately this code wont run at all - there is an error as we try to use an undeclared static variable $class. Why is that? THe answer is in the class definition - our singleton class has no static variable $class; it is its child that has it. The keyword "self" refers always to the class in which is used not to the context in which is used (again compile-time against run-time).

Here comes to help us the Late Static Binding (LSB) introduced in PHP 5.3. Now it is possible to "see" the context in which the code is executed. This is achieved by using in the place of the keyword "self" the key word "static". Dont be consfuesed - in this context the word "static" is used for scope resolution not for defining anything static (which wasits only usage in the previous versions of PHP). Here is the next example with the only change being the replaced keyword:

<?php
class singleton
{
private static $instance;

public function get_instance() {
if (!is_object(self::$instance)) {
$c = self::$class;
self::$instance = new $c();
}
return self::$instance;
}
}

class singleton_test extends singleton
{
protected static $class = __CLASS__;
}

$obj = singleton_test::get_instance();
print get_class($obj);
?>

With only this change we have our goal achieved. But still our singleton class needs a little hint (in the form of the static variable) onto what object to create. It is possible to remove even that:

<?php
class singleton
{
private static $instance;

public function get_instance() {
if (!is_object(self::$instance)) {
$c = get_called_class();
self::$instance = new $c();
}
return self::$instance;
}
}

class singleton_test extends singleton
{
}

$obj = singleton_test::get_instance();
print get_class($obj);
?>

By using the new function get_called_class() the class name can be retreived (still in static context!).

The last example is still a very simplified and not correct implementation (as it wont work with many classes) so here is a full one:

<?php
class singleton
{
private static $instances;

public function __construct() {
$c = get_class($this);
if(isset(self::$instances[$c])) {
throw new Exception('You can not create more than one copy of a singleton.');
} else {
self::$instances[$c] = $this;
}
}

public function get_instance() {
$c = get_called_class();
if (!isset(self::$instances[$c])) {
$args = func_get_args();
$reflection_object = new ReflectionClass($c);
self::$instances[$c] = $reflection_object->newInstanceArgs($args);
}
return self::$instances[$c];
}

public function __clone() {
throw new Exception('You can not clone a singleton.');
}
}

class singleton_test extends singleton
{
}

$o = singleton_test::get_instance();
print get_class($o);
?>

Here is the list with the changes:

  • As in the real world the singleton class will have many children the $instance must be an array that holds the instances of all requested objects not just one as it was in our example
  • The arguments supplied to the get_instance() method are apsswd as arguments to the constructors of the objects (in the previous examples it wasnt possible to pass any arguments to the constructors)
  • A proper singleton implementation should prevent the cloning of the objects so the __clone() magic method throws an exception.