My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Wednesday, September 02, 2009

PHP 5.3 Singleton - Fast And Abstract

In this same blog I talked different times about Singleton Pattern, in latter link "poorly" implemented in PHP 5.

I say poorly, because being Singleton a pattern, there are many ways to implement it and via PHP 5.3 things are more interesting.

There are several ways to define a Singleton class and to extend it, being able to automatically create another one that will follow that pattern.

Here is my implementation, which is extremely fast, logic, and simple as well.

<?php // Singleton :: The WebReflection Way

namespace pattern;

// this is just a pattern ...
// so no new Singleton is allowed
// thanks ot abstract definition
abstract class Singleton {

// note, no static $INSTANCE declaration
// this makes next declaration a must have
// for any extended class
// protected static $INSTANCE;

// @constructor
final private function __construct() {
// if called twice ....
if(isset(static::$INSTANCE))
// throws an Exception
throw new Exception("An instance of ".get_called_class()." already exists.");
// init method via magic static keyword ($this injected)
static::init();
}

// no clone allowed, both internally and externally
final private function __clone() {
throw new Exception("An instance of ".get_called_class()." cannot be cloned.");
}

// the common sense method to retrieve the instance
final public static function getInstance() {
// ternary operator is that fast!
return isset(static::$INSTANCE) ? static::$INSTANCE : static::$INSTANCE = new static;
}

// by default there must be an inherited init method
// so an extended class could simply
// specify its own init
protected function init(){}

}

?>


The static Trick


static is a magic keywords able to open hundreds of closed doors with old PHP versions. Thanks to this keyword it is possible to avoid a lot of redundant code, being sure that when static is called, it will be the current class call and not the one where self has been used. Thanks to this keyword is then possible to refer directly the current class instance, the one that will extend pattern\Singleton, using its own init method if preset, the empty abstract inherited otherwise.

Example



<?php

// for this test only, this class
// is in the root, same level of autoload.php

// include basic stuff ...
require_once 'autoload.php';

// define a singleton class
class SingA extends pattern\Singleton {

// my Singleton requires a
// protected static $INSTANCE variable
// if not present, nothing will
// be executed - Fatal error
protected static $INSTANCE;

// let's define something else
protected $a = 'A';
protected $b;

// let's try the init method
protected function init(){
// assign a random value to b
$this->b = rand();
}
}

// define another singleton class
class SingB extends pattern\Singleton {
protected static $INSTANCE;
}

// let's try the Singleton
$sa = SingA::getInstance();
$sb = SingB::getInstance();
$sb->runTime = 'here I am';
$sa2 = SingA::getInstance();

echo '<pre>',
var_dump($sa),
var_dump($sb),
var_dump($sa2),
var_dump($sa === $sa2),
var_dump($sa !== $sb),
'</pre>'
;

?>

Above test case will produce exatly this output:

object(SingA)#2 (2) {
["a":protected]=>
string(1) "A"
["b":protected]=>
int(31994)
}
object(SingB)#3 (1) {
["runTime"]=>
string(9) "here I am"
}
object(SingA)#2 (2) {
["a":protected]=>
string(1) "A"
["b":protected]=>
int(31994)
}
bool(true)
bool(true)

assuming the file autoload.php is present in the same level:

<?php

// simple autoload function
spl_autoload_register(function($class){

// assuming this file is in the root
// (just as example)
require __DIR__.
DIRECTORY_SEPARATOR.
// fix namespace separator ...
str_replace(
'\\',
DIRECTORY_SEPARATOR,
$class
).
// add classes suffix
'.class.php'
;
});

// that's it

?>


Why This Is Faster, Why This Is Better


The reason I posted about my own PHP 5.3 implementation is a php mailing list discussion which pointed to another extended Singleton for PHP 5.3.
That implementation will perform for each getInstance() call a callback which aim is to discover the class caller, get_called_class(), plus 2 up to 3 lookups plus an assignment over a static associative array, self::$instance[$class].
Finally, even if it could not make sense, extended Singleton classes cannot access to their own Singleton instances, aka: less control and overhead being Singleton a truly common pattern.
At least you know there is an alternative which aim is to let us remember that a singleton is a unique instance, the one we need to define as protected $INSTANCE, and that performances are always welcome, at least in my daily code/life style.

Enjoy!

16 comments:

RStankov said...

Great :) I really can't wait to use PHP5.3 in production

Andrea Giammarchi said...

well, 5.3 is one of the most stable release ever - they did not even came out with a single patch after its first announcement - so no needs to wait, imho ;)

Anonymous said...

very nice trick with the new static, i'll tuck that one away for later use!!

Unknown said...

I found this very useful. I'm making a change with the protected static $INSTANCE line so that it doesn't need to be defined in all the classes extending Singleton. I'm also creating tests. Want to take a look at it?

Andrea Giammarchi said...

sure :)

}:-}Noisia said...

Hey, you must be joking? How would the 'final protected __construct()' be ever executed, same as __clone() ?? Why do they have implementation code?

Andrea Giammarchi said...

Who is joking? What is not executed? What do you know about PHP OO, __construct and clone $object?
Which part is not clear?

Offshore PHP India said...

Thanks and Great :) info. I understood about Abstract class. An abstract class defines the basic skeleton for the class. It contains attributes and members but some members are incomplete and is waiting for some other class to extend it through inheritance so that the derived class provides a full functionality for the incomplete methods

immeƫmosol said...

and how( if possible) can arguments be passed to the constructor/init method from the newInstance-call ?

noisebleed said...

First, thanks for sharing this implementation of the Singleton class with inheritance. Very useful.

Just one question though: what's the disadvantage of having $instance declared in the abstract class?

Unknown said...

@noisebleed:

declaring $INSTANCE in the abstract parent class will break things if you have more than one child of the Singleton class instantiated at a time. get_instance() calls for the different children will always return the first instantiated object like so:

$a = ClassA::get_instance();
$b = ClassB::get_instance();

print_r($a);
print_r($b);

would give this output --->

ClassA Object();
ClassA Object();

Andrea Giammarchi said...

which part was not clear? :)

we need to define as protected $INSTANCE

this is for each class

Leigh said...

Under what circumstances can the constructor be called twice? I haven't had an exception raised yet, and I'm wondering if there's any point keeping the code there?

Also after testing, I'm finding that defining `protected static $instance` in the abstract class works fine (5.3.8).

abstract class Singleton {
protected static $instance;
...

class ClassA extends Singleton {}
class ClassB extends Singleton {}

$a = ClassA::getInstance();
$b = ClassB::getInstance();

var_dump($b); // object(ClassB)[2]
var_dump($a); // object(ClassA)[1]

I've also made a few modifications. I'm using `static::$instance instanceof static` in the getInstance() method, and `if (method_exists(get_called_class(), 'init'))` in the constructor, so I am not forced to implement a constructor for classes that don't want one.

Anonymous said...

@Leigh... You don't add a static for 'instance' to the Singleton class because then you can have only one Singleton derived class at all.

Consider:
Add the following to the Singleton class:

protected static $INSTANCE; // BTW, why are we shouting - why not $_instance;

Then try the following code:

class myclass_1 extends Singleton
{
public $data ="Hello, World";
}

class myclass_2 extends Singleton
{
public $data = "Goodbye, World";
}

$aa = myclass_1::instance();
$bb = myclass_2::instance();
var_dump($aa);
var_dump($bb);

This code will output:
object(myclass_1)#1 (1) { ["data"]=> string(12) "Hello, World" } object(myclass_1)#1 (1) { ["data"]=> string(12) "Hello, World" }

... which is about as wrong as wrong gets.

Anonymous said...

This blog covers a common use case for Singletons, but it does not cover all cases.

The implementation here does not account for the following use-cases.

1. Singletons that require variable __construct parameters. In this case the 'final' has prevented such a requirement from being implemented.

2. Singletons that require inheritance from another class first. This is a general issue with not having multiple inheritance in PHP rather than a problem with the article. Multiple inheritance in itself is not bad, it's just that most people don't understand the academics when it is required and so we get the 'never do it' zealots who didn't pass their degree level computer science exams. Using the given pattern we would have to derive an intermediate class from our common base class and then from Singleton - that's ugly. Further, if the base class has a non-simple constructor then the implementation here would not work as per case 1.

Ultimately, there is no actual standard inheritable way of covering all Singleton use cases. The example here is good for the most common simple case, but in PHP you will have to use a more verbose interface instead to cover the less common (but often more useful) use cases. With an interface you can force the coder to add a __clone() and static interface() and still allow the class to have a complex constructor and inheritance from another class without an intermediate step. On the downside, the new class would have to implement all the singleton logic - but that's what you have to do when you don't have multiple inheritance.

Andrea Giammarchi said...

YAGNI and KISS my favorite principles, for everything else you can penalize performances as much as you need to cover the specific edge case :)