Common code for Singletons using PHP's traits
I hate singletons. Some say that singletons are not bad, instead “singletonism” is the evil. I would argue. Anyway, I’ve been working on some common Singleton pattern code in PHP for a particular reason - I had to create common code for two basecodes:
- Laravel app using Facades
- some other custom API
Usage
class DataUtilsInst {
use Singleton;
public static function getClassName() {
return '\NS\common\utils\DataUtils';
}
}
Singleton code
trait Singleton {
static $__INSTANCES = [];
public static function getInstance() {
$className = static::getClassName();
if (!isset(static::$__INSTANCES)) {
static::$__INSTANCES = [];
}
if (!isset(self::$__INSTANCES[$className])) {
self::$__INSTANCES[$className] = new $className;
}
return self::$__INSTANCES[$className];
}
/**
* Handle dynamic, static calls to the object.
*
* @param string $method
* @param array $args
* @return mixed
*/
public static function __callStatic($method, $args)
{
$instance = static::getInstance();
switch (count($args))
{
case 0:
return $instance->$method();
case 1:
return $instance->$method($args[0]);
case 2:
return $instance->$method($args[0], $args[1]);
case 3:
return $instance->$method($args[0], $args[1], $args[2]);
case 4:
return $instance->$method($args[0], $args[1], $args[2],
$args[3]);
default:
return call_user_func_array(array($instance, $method),
$args);
}
}
}
I took inspiration from Laravel:
- __callStatic comes from Facade
- instancing came from Illuminate\Container’s share() method often used in service providers.
static function getClassName???
Yeah, you can change trait to class which will be extended and use get_called_class() instead but here’s why it didn’t work for me:
- It’s impossible to extend class by multiple classes in PHP, so I used trait. It’s rather future benefit.
- I didn’t want to make long calls to Singleton like DataUtils::getInstance()->getFromCache($key, ..). Instead, DataUtilsInst::getFromCache($key, …) works better for me and actually makes it quite impossible (without dirty hacks) to save reference.
Wait, why would I need getInstance()? Because __callStatic() is called when called static function name isn’t used by any class method. Speaking otherwise, when you call someImportantFunction() wanting to get called it through __****callStatic(), then PHP throws error stating that tou can’t run a non-static function statically!
Non-static method MyUtils::someImportantFunction() should not be called statically, assuming $this from incompatible context
More on this issue can be found on PHP (not a) bug report.