Wednesday, September 2, 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!

No comments:

Post a Comment