24

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?

Community
  • 1
  • 1
edorian
  • 38,542
  • 15
  • 125
  • 143

5 Answers5

29

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

edorian
  • 38,542
  • 15
  • 125
  • 143
  • The method "init" will be called static but it isn't. – mabe.berlin Apr 11 '12 at 06:18
  • I see you must have made `init` static since mabe's comment. You'd do better to make this a regular protected method and call it using `$this`, otherwise you won't be able to initiate instance variables. – RobMasters Sep 11 '12 at 08:02
  • Wouldn't it be helpfull to make the init function abstract? I believe this is supported and works as advertised, so you'd enforce implementing an init function. This would take the empty function from the trait to some of the classes (the ones that wouldn't have implemented it in the first place), but makes the flow easier to understand, and you can 'enforce' the usage of the class a bit better? – Nanne Dec 12 '13 at 11:50
  • @Nanne i think it wouldn`t because some of classes dont need __construct or init at all, so it will be just a wasting lines of code – kirugan Nov 29 '14 at 20:41
  • If you use an IDE that can do static analysis, one issue you have is the result type of getInstance() is always the wrong type, so, say you have a singleton ConnectionFactory, SA can't see any methods for the returned singleton. So, adding a class-level docblock that advertises the result type for the ConnectionFactory ... * @method static ConnectionFactory getInstance() Get the ConnectionFactory instance. allows SA to see that the result of getInstance() is a ConnectionFactory! But, then SA will now complain "Cannot override final method Singleton::getInstance()". Sigh! – Richard A Quadling Jun 16 '16 at 10:34
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.

Klathmon
  • 273
  • 1
  • 11
  • 1
    There is no need to use reflection to instantiate - this should be `new self()`. Using reflection is just adding extra steps (and CPU cycles) – mwieczorek Jan 10 '21 at 11:55
2

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;
}
  • This isn't the singleton pattern. Your instance variable is scoped to the function, so that call to `is_null` will always return true. The `$instance` variable must be declared in the class. What you have here will always return a new instance, thus it's merely a facade for creating a new instance. – mwieczorek Jan 10 '21 at 11:53
  • @mwieczorek this is a static variable, which means it will be declared only one time and it will be the same variable even for different function calls – Sergey Semushin Mar 27 '21 at 21:26
  • @SergeySemushin Yes, but a static class variable (property) must be declared in the class or trait body. You're declaring it inside the `Instance()` method, which will always be null when that method is called, as it is declared right there in the line above. – mwieczorek Apr 03 '21 at 08:38
  • @mwieczorek no, it still would be the same variable each call, even though it is declared inside function. Here are some references: 1. https://www.php.net/manual/en/language.variables.scope.php#language.variables.scope.static 2. https://stackoverflow.com/questions/6188994/static-keyword-inside-function – Sergey Semushin Apr 03 '21 at 09:07
  • @SergeySemushin fair enough, but the fact that from the onset it LOOKS like it doesn't work, it's an anti-pattern. Even though it can be put inside the function, doesn't mean it should. From a readability standpoint, that static variable *should* be defined in the class body. – mwieczorek Apr 28 '21 at 18:11
  • @mwieczorek I would not agree with you on "it LOOKS like it doesn't work". For me, it is obvious that it works and obvious how it works. I think, for you it looks like it doesn't work only because you never had an experience with static variables inside functions. It is not some kind of quirk of php or workaround, it is a documented feature. – Sergey Semushin Apr 29 '21 at 19:24
1

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.

Cyril
  • 79
  • 1
  • 2
  • A bit late to the party but `impossible to provide an @return in compliance with the consumer type in the getInstance method doc bloc` this statement is not accurate. In fact In Eclipse you can `* @return self` from the getInstance method's Doc-block and I will resolve methods in the class that uses the trait where getInstance is defined. And thus auto-completion works as normal even though getInstance is not in the class itself. – ArtisticPhoenix Apr 01 '18 at 10:31
0

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

enter image description here

ArtisticPhoenix
  • 21,464
  • 2
  • 24
  • 38