Building a Singleton Trait with PHP 5.4

2019-02-02 05:48发布

问题:

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?

回答1:

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



回答2:

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.



回答3:

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.



回答4:

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;
}


回答5:

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



标签: php traits