Building a Singleton Trait with PHP 5.4

落爺英雄遲暮 提交于 2019-11-28 19:20:35

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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!