3
\$\begingroup\$

Even though singletons are bad practice and often unnecessary, I still see many developers using this pattern over and over. Since implementation of this pattern usually requires some common code, I thought we could just make a base class to be extended.

I came up with this so far.

<?php namespace wl; /** * @dosc allows only one instance for each extending class * @example use it for database connection, config setup... * Be aware, the singleton pattern is consider to be an anti-pattern * because it can be hard to debug. * In most cases you do not need to use singleton pattern * so take a longer moment to think about it before you use it. */ class Singleton { /** * holds an single instance of a class * * @var array of objects */ protected static $instance = []; /** * @desc provides a single slot to hold an instance interchanble between all child classes. * @return object */ public static function getInstance(){ $class = get_called_class(); if(!isset(self::$instance[$class]) || !self::$instance[$class] instanceof $class){ self::$instance[$class] = new static(); // create and instance of child class which extends Singleton super class echo "new ". $class . PHP_EOL; // remove this line after testing return self::$instance[$class]; // remove this line after testing } echo "old ". $class . PHP_EOL; // remove this line after testing return static::$instance[$class]; } /** * do not allow create new instance by new keyword * */ protected function __construct(){} /** * Do not clone the object */ protected function __clone(){} /** * Do not allow reserialization of this object */ protected function __wakeup(){} } 
/** * ----------------------------------------------USE EXAMPLE--------------------------------------------------- * @docs example database class by extending singleton class implements singleton pattern */ class Database extends Singleton { public function __construct(){ } } /** * @docs Config class by extending singleton class implements singleton pattern */ class Config extends Singleton { public function __construct(){ } } /** * @example create new Database */ $bd1 = Database::getInstance(); // new $bd2 = Database::getInstance(); // old /** * @example create new Config */ $bd1 = Config::getInstance(); // new $bd2 = Config::getInstance(); // old $bd3 = Config::getInstance(); // old $bd4 = Database::getInstance(); // old $bd5 = Database::getInstance(); // old $bd6 = Config::getInstance(); // old 

Any suggestions are very welcome. github

\$\endgroup\$
2
  • \$\begingroup\$ I have rolled back the Rev 7 → 5. Please see What to do when someone answers. \$\endgroup\$ Commented Sep 28, 2016 at 17:23
  • \$\begingroup\$ Ok i get the point thx :) should I then post and answer to my own question also if I want to make an update? \$\endgroup\$ Commented Sep 28, 2016 at 17:28

3 Answers 3

4
\$\begingroup\$

Not a Singleton

class Database extends Singleton { public function __construct(){ } } 

This is not a singleton. Add the following code:

$bd7 = new Config(); 

You have a new Config object.

class Config extends Singleton { protected function __construct() { } } 

This is a singleton. It will give an error if you try to create a Config object directly. Or

class Database extends Singleton { } 

Is a singleton that gives an error if you create a Database object directly as it inherits its constructor from its parent.

Registry

You don't need to use inheritance to get this functionality.

 public static function getInstance(){ $class = get_called_class(); if(!isset(self::$instance[$class]) || !self::$instance[$class] instanceof $class){ self::$instance[$class] = new static(); // create and instance of child class which extends Singleton super class echo "new ". $class . PHP_EOL; // remove this line after testing return self::$instance[$class]; // remove this line after testing } echo "old ". $class . PHP_EOL; // remove this line after testing return static::$instance[$class]; } 

Change to

 public static function getInstance($class, $object = null) { if (!isset(self::$instance[$class]) && $object != null) { self::$instance[$class] = $object; } return static::$instance[$class]; } 

or just

 public static function getInstance($key) { return static::$instance[$key]; } 

with

 public static function setInstance($key, $object) { static::$instance[$key] = $object; } 

And change

$bd1 = Database::getInstance(); // new 

to

Registry::setInstance('database', new Database()); $bd1 = Registry::getInstance('database'); 

Now, it's possible that in a real application you would make more use of what inheritance gives you. But in this toy application, you don't.

I changed the name from Singleton to Registry, as better fitting this pattern.

You lose the just-in-time instantiation of the original, but you gain control over how it is instantiated. An advantage of this form is that you can also do things like

Registry::setInstance('database', new MockDatabase()); 

Your original form prevented this, exacerbating the Singleton pattern's problems with user testing.

Nitpicks

/** * @dosc allows only one instance for each extending class * @example use it for database connection, config setup... * Be aware, singleton pattern is consider to be an antipatern and becaouse of it build it is hard to debug. * In most cases you do not need to use singleton patern so make a longer moment to think about it befor you use it. */ 

Without the spelling errors:

/** * @dosc allows only one instance for each extending class * @example use it for database connection, config setup... * Be aware, the singleton pattern is consider to be an anti-pattern * because it can be hard to debug. * In most cases you do not need to use singleton pattern * so take a longer moment to think about it before you use it. */ 

I also changed the wording a bit and broke up two lines so that they don't cause scrolling (at least in my browser).

\$\endgroup\$
3
  • \$\begingroup\$ Yeh I alredy added protected to childs constructor on my github repo. Btw. I am still reading your post :) \$\endgroup\$ Commented Sep 28, 2016 at 16:12
  • \$\begingroup\$ Looks like my SIngleton class is not pure singleton pattern. My Singleton class acts a bit as some kind of registry, But, from the childs point of view it still provide a valid singleton behavior. If i swich it to registry as you advice it will be able to hold many instance of same object ? Am I right ? This is something I would like to avoid. I took your advice and slightly change my arhitecture concept. I change Singleton class to be abstract, and I forced on the extending classes to implemnt protected constructor to keep valid singleton behaviour. Ps sorry for my typo, and thank You. \$\endgroup\$ Commented Sep 28, 2016 at 16:57
  • \$\begingroup\$ + 1 for pointind public constructors in child classes \$\endgroup\$ Commented Sep 29, 2016 at 0:43
1
\$\begingroup\$

Don't really have a lot to add to the other answers which already demonstrate how you are not actually implementing a singleton here.

One questionable thing in code is this:

/** * holds an single instance of a class * * @var array of objects */ protected static $instance = []; 

Why is $instance an array of this is a singleton?

It should probably be:

protected static $instance = null; 

I wonder if what you are really getting at is a dependency injection framework. It seems that you want to be able to instantiate authoritative dependencies which can be pass around your application. Rather than implementing an abstract singleton class which all your concrete classes inherit (something that would be VERY limiting to your design flexibility due to PHP only having single inheritiance), perhaps you look at dependency injection frameworks or patterns to do what your want.

Or perhaps you have class structures like:

/* base abstract implementation */ abstract class SingletonClassVendor /* a singleton-based provider for Database objects */ class DatabaseVendor extends SingletonClassVendor 

Where the singleton just instantiates and vends a particular type of class, in essence holding the authoritative dependency. But, the concrete objects (Database, Config, etc.) themselves have no singleton in their inheritance chain.

\$\endgroup\$
3
  • \$\begingroup\$ Yes Mike you are correct. This is more less what it is at the moment. Have a look at my update. The class it self is not singleton it only provides functionality of singleton for the classes that extends from it. We could call it "SingletonClassVendor" I gues. Thanks. \$\endgroup\$ Commented Sep 29, 2016 at 0:02
  • \$\begingroup\$ There is a reason why static instance holds array instead of single object. I want to provide a solution that allows to extend more then one class in a way that they do not overwrite each other. This could be important if you use them interchangeably rather that in sequence. \$\endgroup\$ Commented Sep 29, 2016 at 0:04
  • \$\begingroup\$ + 1 for "SingletonClassVendor" \$\endgroup\$ Commented Sep 29, 2016 at 0:42
0
\$\begingroup\$

CLASS UPDATE - based on your comments

I updated the script, SingletonClassVendor class is now abstract. It also force protected constructor on extending child classes (by defining an abstract method).

<?php namespace wl; /** * @dosc allows only one instance for each extending class. * it acts a litle bit as registry from the SingletonClassVendor abstract class point of view * but it provides a valid singleton behaviour for its children classes * @example use it for database connection, config setup... * Be aware, the singleton pattern is consider to be an anti-pattern * because it can be hard to debug. * In most cases you do not need to use singleton pattern * so take a longer moment to think about it before you use it. */ abstract class SingletonClassVendor { /** * holds an single instance of the child class * * @var array of objects */ protected static $instance = []; /** * @desc provides a single slot to hold an instance interchanble between all child classes. * @return object */ public static function getInstance(){ $class = get_called_class(); // or get_class(new static()); if(!isset(self::$instance[$class]) || !self::$instance[$class] instanceof $class){ self::$instance[$class] = new static(); // create and instance of child class which extends Singleton super class echo "new ". $class . PHP_EOL; // remove this line after testing return self::$instance[$class]; // remove this line after testing } echo "old ". $class . PHP_EOL; // remove this line after testing return static::$instance[$class]; } /** * Make constructor abstract to force protected implementation of the __constructor() method, so that nobody can call directly "new Class()". */ abstract protected function __construct(); /** * Make clone magic method private, so nobody can clone instance. */ private function __clone() {} /** * Make sleep magic method private, so nobody can serialize instance. */ private function __sleep() {} /** * Make wakeup magic method private, so nobody can unserialize instance. */ private function __wakeup() {} } 

USE EXAMPLE:

/** * @example Database class by extending SingletonClassVendor abstract class becomes fully functional singleton * __constructor must be set to protected becaouse: * 1 to allow instansiation from parent class * 2 to prevent direct instanciation of object with "new" keword. * 3 to meet requierments of SingletonClassVendor abstract class */ class Database extends SingletonClassVendor { public $type = "SomeClass"; protected function __construct(){ echo "DDDDDDDDD". PHP_EOL; // remove this line after testing } } /** * @example Config class by extending SingletonClassVendor abstract class becomes fully functional singleton * __constructor must be set to protected becaouse: * 1 to allow instansiation from parent class * 2 to prevent direct instanciation of object with "new" keword. * 3 to meet requierments of SingletonClassVendor abstract class */ class Config extends SingletonClassVendor { public $name = "Config"; protected function __construct(){ echo "CCCCCCCCCC" . PHP_EOL; // remove this line after testing } } 

TEST:

$bd1 = Database::getInstance(); // new $bd2 = Database::getInstance(); // old $bd3 = Config::getInstance(); // new $bd4 = Config::getInstance(); // old $bd5 = Config::getInstance(); // old $bd6 = Database::getInstance(); // old $bd7 = Database::getInstance(); // old $bd8 = Config::getInstance(); // old echo PHP_EOL."COMPARE ALL DATABASE INSTANCES".PHP_EOL; var_dump($bd1); echo '$bd1 === $bd2' . ($bd1 === $bd2)? ' TRUE' . PHP_EOL: ' FALSE' . PHP_EOL; echo '$bd2 === $bd6' . ($bd2 === $bd6)? ' TRUE' . PHP_EOL: ' FALSE' . PHP_EOL; echo '$bd6 === $bd7' . ($bd6 === $bd7)? ' TRUE' . PHP_EOL: ' FALSE' . PHP_EOL; echo PHP_EOL; echo PHP_EOL."COMPARE ALL CONFIG INSTANCES". PHP_EOL; var_dump($bd3); echo '$bd3 === $bd4' . ($bd3 === $bd4)? ' TRUE' . PHP_EOL: ' FALSE' . PHP_EOL; echo '$bd4 === $bd5' . ($bd4 === $bd5)? ' TRUE' . PHP_EOL: ' FALSE' . PHP_EOL; echo '$bd5 === $bd8' . ($bd5 === $bd8)? ' TRUE' . PHP_EOL: ' FALSE' . PHP_EOL; 

TEST OUTPUT:

DDDDDDDDD new wl\Database old wl\Database CCCCCCCCCC new wl\Config old wl\Config old wl\Config old wl\Database old wl\Database old wl\Config COMPARE ALL DATABASE INSTANCES $bd1 === $bd2 TRUE $bd2 === $bd6 TRUE $bd6 === $bd7 TRUE COMPARE ALL CONFIG INSTANCES $bd3 === $bd4 TRUE $bd4 === $bd5 TRUE $bd5 === $bd8 TRUE 
\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.