I'm from a C++ background and a Singleton hater. C++ just doesn't need them! However, I've found they're actually very useful for databases in PHP.
Anyways, I was trying to find a generic base class for Singletons in PHP. It's actually quite easy to do in C++ (which is probably why Singleton overload is a big problem). Most articles I find offer something like this:
PHP Code:
class Singleton
{
// The sole access point for singletons.
final public static function GetInstance( $inClass )
{
static $gInstances = array();
if( !array_key_exists( $inClass, $gInstances ) )
{
self::$gInstances[$class] =& new $inClass;
}
$theInstance =& $gInstances[$inClass];
return $theInstance;
}
}
What on earth? That's not a singleton at all. It creates an array that can hold every concievable class. The array can only hold one of each class and has only a single point of access. However, the classes themselves can easily be instantiated again and/or cloned.
The cloning problem can be fixed by making the __clone function final and private in every class you want to be a singleton. Making __construct private prevents instantiation even by this class, not what you'd want at all.
Anyways, I can't really be bothered ensuring every class has a private clone function and constructor. That leads to errors. And even if they did, this class could still instantiate non-singletons as well.
So I came up with this:
PHP Code:
<?php
//------------------------------------------------------------------------------
// Singleton
// A class for managing singletons in the project.
//------------------------------------------------------------------------------
class Singleton
{
// The sole access point for singletons.
final public static function GetInstance( $inClass )
{
// Do we have one yet?
if( !array_key_exists( $inClass, self::$gInstances ) )
{
self::$gInstances[$inClass] =& new $inClass;
// Is this a singleton class?
if( !( self::$gInstances[$inClass] instanceof Singleton ) )
{
unset( self::$gInstances[$inClass] );
throw new Exception( "$inClass is not a Singleton class." );
}
self::$gInstances[$inClass]->Construct();
}
$theInstance =& self::$gInstances[$inClass];
return $theInstance;
}
// Construct function - to be overrided by sub-classes if needed.
protected function Construct() {}
// No creation or cloning.
final private function __construct() {}
final private function __clone() {}
// The instances of the class(es).
private static $gInstances = array();
}
?>
Singletons have to inherit from this class. If you call Singleton::GetInstance() with a class that isn't a Singleton, this function unsets it and throws an exception.
The Construct function is called the first time a Singleton is created. This can be overrided in sub-classes to do any initialisation the object may need.
The constructor and the clone function are declared final private - only accessible by this class and unavailable for overriding. Unfortunately, even if this constructor is private / protected this can be overrided by sub-classes as public if they are not final, which allows them to be instantiated more than once. This is the reason for the Construct() function.
If you create this test file:
PHP Code:
<?php
include_once( $_SERVER['DOCUMENT_ROOT'].'/../Include/Singleton.php' );
// A test singleton with a 'Constructer'
class TestSingleton extends Singleton
{
function Construct()
{
echo "Constructing...<br />\n";
}
public $mVar;
}
// A non-singleton class.
class NonSingleton {}
// Instantiate a Singleton...
$theSingleton = Singleton::GetInstance( 'Singleton' );
// Instantiate the Test Singleton.
$theTestSingleton = Singleton::GetInstance( 'TestSingleton' );
// Assign some text to the member variable.
$theTestSingleton->mVar = "Hello World!<br />\n";
// Create another access point to the Test Singleton.
$theOtherTestSingleton = Singleton::GetInstance( 'TestSingleton' );
// Output the member variable.
echo $theOtherTestSingleton->mVar;
// Try and instantiate a Non Singleton via Singleton.
$theNonSingleton = Singleton::GetInstance( 'NonSingleton' );
?>
You get this output:
Quote:
Constructing...
Hello World!
Fatal error: Uncaught exception 'Exception' with message 'NonSingleton is not a Singleton class.' in /home/.sambrina/theoddman/Include/Singleton.php:22 Stack trace: #0 /home/.sambrina/theoddman/www.theoddman.com/test.php(31): Singleton::GetInstance('NonSingleton') #1 {main} thrown in /home/.sambrina/theoddman/Include/Singleton.php on line 22
|
As you can see, TestSingleton::Construct() only gets called once, despite us accessing it twice. We can also see that $TestSingleton and $OtherTestSingleton reference the same object.
When we try to create NonSingleton via Singleton, we get our exception as desired (which is uncaught in this case).
The only potential problems I can see in this are:
1) You can instantiate the Singleton class itself as demonstrated in the above code. This could be prevented by checking the input value, but I don't think it's worth it - surely only the worst coders would do this?
2) Construct() can be overrided to be public. This could be good and bad, I'm not sure how I feel about it...
3) Overhead?
Anyways, what do you guys think? Any other potential problems? Is this even gonna work? :)
M