diff --git a/src/eTools/Utils/Singleton.php b/src/eTools/Utils/Singleton.php index 1020177a..dae42b49 100644 --- a/src/eTools/Utils/Singleton.php +++ b/src/eTools/Utils/Singleton.php @@ -13,14 +13,53 @@ abstract class Singleton { protected static $instances = array(); + private static $override_instances = array(); + + /** + * Returns a singleton object of a given class. Extend this class, then call \YourClass::getInstance() from anywhere + * in the application. + * + * If an override class is registered, that will be returned instead. Thi + * + * @return \stdClass A singleton of the requested class + */ public static function getInstance() { $class = get_called_class(); + $args = func_get_args(); + + // Check if an override is set, and replace the class name to load if it is + if (isset(self::$override_instances[$class])) { + $class = self::$override_instances[$class]; + } + + // Create a singleton instance of the class if it doesn't already exist if (!isset(self::$instances[$class])) { - self::$instances[$class] = new $class(); + if(is_array($args) && sizeof($args) > 0) { + $reflectionClass = new \ReflectionClass($class); + self::$instances[$class] = $reflectionClass->newInstanceArgs($args); + } else { + self::$instances[$class] = new $class(); + } } + return self::$instances[$class]; } + /** + * Configures an override class, that can later be returned by self::getInstance(). + * + * @param string $oldClass The class name to replace + * @param string $newClass The class to replace this with + * @return void + */ + public static function set_override_instance($oldClass, $newClass) { + self::$override_instances[$oldClass] = $newClass; + } + + public static function clear_instances() { + self::$instances = array(); + } + protected function __construct() { } diff --git a/tests/eTools/Utils/SingletonTest.php b/tests/eTools/Utils/SingletonTest.php new file mode 100644 index 00000000..c13139a1 --- /dev/null +++ b/tests/eTools/Utils/SingletonTest.php @@ -0,0 +1,91 @@ +assertEquals(false, $obj->getCheck()); + $obj->toggleCheck(); + + /** @var SingletonSubclass $obj2 */ + $obj2 = SingletonSubclass::getInstance(); + $this->assertEquals(true, $obj2->getCheck()); + + $obj2->toggleCheck(); + $this->assertEquals(false, $obj->getCheck()); + } + + public function testGetInstanceSetsConstructorArgs() { + /** @var SingletonSubclass $obj */ + $obj = SingletonSubclass::getInstance('arg1', 'arg2'); + $this->assertEquals('arg1', $obj->getConstructorArg1()); + $this->assertEquals('arg2', $obj->getConstructorArg2()); + } + + /** + * This test ensures that the Singleton::set_override_instance() method overrides classes correctly + */ + public function testOverrideInstances() { + // Before setting an override, we should get the normal class + $singletonSubclass = SingletonSubclass::getInstance(); + $this->assertInstanceOf('eTools\Tests\Utils\SingletonSubclass', $singletonSubclass); + + // Set the override and try again, we should now get the overridden method + Singleton::set_override_instance( + 'eTools\Tests\Utils\SingletonSubclass', + 'eTools\Tests\Utils\SingletonOverrideSubclass' + ); + + // Ensure it hasn't changed the existing instance somehow + $this->assertInstanceOf('eTools\Tests\Utils\SingletonSubclass', $singletonSubclass); + $this->assertInstanceOf('eTools\Tests\Utils\SingletonOverrideSubclass', SingletonSubclass::getInstance()); + + // Ensure override works every time once it's set + $this->assertInstanceOf('eTools\Tests\Utils\SingletonOverrideSubclass', SingletonSubclass::getInstance()); + } +} + +/** + * Class SingletonSubclass + * @package eTools\Tests\Utils + * + * This is a sample class - it should be used only for testing + */ +class SingletonSubclass extends Singleton { + private $check = false; + private $constructorArg1 = null; + private $constructorArg2 = null; + + public function __construct($arg1 = null, $arg2 = null) { + $this->constructorArg1 = $arg1; + $this->constructorArg2 = $arg2; + } + + public function toggleCheck() { + $this->check = !$this->check; + } + + public function getCheck() { + return $this->check; + } + + public function getConstructorArg1() { + return $this->constructorArg1; + } + + public function getConstructorArg2() { + return $this->constructorArg2; + } +} + +class SingletonOverrideSubclass extends Singleton {} \ No newline at end of file