We recently had a discussion if it was possible to build a trait Singleton
PHP Traits and we played around with it a possible Implementation but ran into issues with building one.
This is an academic exercise. I know that Singletons have very little - if not to say no - use in PHP
and that one should 'just create one'
but just for exploring the possibilities of traits:
<?php
trait Singleton
{
protected static $instance;
final public static function getInstance()
{
return isset(static::$instance)
? static::$instance
: static::$instance = new static;
}
final private function __construct() {
static::init();
}
protected function init() {}
final private function __wakeup() {}
final private function __clone() {}
}
class A {
use Singleton;
public function __construct() {
echo "Doesn't work out!";
}
}
$a = new A(); // Works fine
reproduce: http://codepad.viper-7.com/NmP0nZ
The question is: It is possible to create a Singleton Trait in PHP?
Quick solution we've found (thanks chat!):
If a trait and a class both define the same method, the one of class if used
So the Singleton trait only works if the class that uses it doesn't define a __construct()
Trait:
<?php
trait Singleton
{
protected static $instance;
final public static function getInstance()
{
return isset(static::$instance)
? static::$instance
: static::$instance = new static;
}
final private function __construct() {
$this->init();
}
protected function init() {}
final private function __wakeup() {}
final private function __clone() {}
}
Example for a consuming class:
<?php
class A {
use Singleton;
protected function init() {
$this->foo = 1;
echo "Hi!\n";
}
}
var_dump(A::getInstance());
new A();
The var_dump now produces the expected output:
Hi!
object(A)#1 (1) {
["foo"]=>
int(1)
}
and the new fails:
Fatal error: Call to private A::__construct() from invalid context in ...
Demo
I created one a while ago when i was bored trying to learn traits. It uses reflection and the __CLASS__
constant
Trait:
trait Singleton
{
private static $instance;
public static function getInstance()
{
if (!isset(self::$instance)) {
$reflection = new \ReflectionClass(__CLASS__);
self::$instance = $reflection->newInstanceArgs(func_get_args());
}
return self::$instance;
}
final private function __clone(){}
final private function __wakeup(){}
}
This way you can continue to use the __construct() method and don't need to use an arbitrary function as the constructor.
The thing is that the type of getInstance return will be ambigous since it depends on the consumer. This gives a weak-typed method signature.
For instance it makes it impossible to provide an @return in compliance with the consumer type in the getInstance method doc bloc.
This is guys all what you need.
If you wish you could use private static member, but there is no a real need...
Tested, works despite the fact that you might think that static will be global or something :)
trait Singleton
{
/**
* Singleton pattern implementation
* @return mixed
*/
public static function Instance()
{
static $instance = null;
if (is_null($instance)) {
$instance = new self();
}
return $instance;
}
}
usage:
class MyClass
{
use Singleton;
}
A bit late to the party, but I wanted to show how (in Eclipse Oxygen PDT at least) you can do the DocBlock where auto-completion will work for this
trait SingletonTrait{
/**
*
* @var self
*/
private static $Instance;
final private function __construct()
{
}
final private function __clone()
{
}
final private function __wakeup()
{
}
/**
*
* Arguments passed to getInstance are passed to init(),
* this only happens on instantiation
*
* @return self
*/
final public static function getInstance(){
if(!self::$Instance){
self::$Instance = new self;
self::$Instance->init();
}
return self::$Instance;
}
protected function init()
{
}
}
As you can see both $instance
and getInstance
are defined as self
. Eclipse is smart enough to work this out so that when you use it in a class all the auto-completion works just as normal.
Test