Singleton pattern UML

Why Singleton Pattern in PHP sucks!

Yesterday ended with a workshop Mariusz Gil - we were talking about design patterns and started to discuss on Singleton - the easiest design pattern.
I've been trying to implement secure singleton pattern in PHP. It may sound ridiculous but there actually is no way to implement this pattern the Right Way. Speaking of the Right Way in this case I mean implementing a class which can produce one single instance of self and protecting against another objects intance creation.

Singleton pattern is one of the simplest design patterns. This type of design pattern comes under creational pattern as this pattern provides one of the best ways to create an object.

This pattern involves a single class which is responsible to create an object while making sure that only single object gets created. This class provides a way to access its only object which can be accessed directly without need to instantiate the object of the class.

Singleton pattern UML

There are some who are critical of the singleton pattern and consider it to be an anti-pattern in that it is frequently used in scenarios where it is not beneficial, introduces unnecessary restrictions in situations where a sole instance of a class is not actually required, and introduces global state into an application.

Implementation

Let's consider simple Timer implementation using singleton pattern.

class Timer
{
    private static $instance;
    private $time;

    private final function __construct()
    {
        $this->time = 0;
    }
    public final static function getInstance() : self
    {
        if (self::$instance instanceof self) {
            return self::$instance;
        }

        return self::$instance = new self();
    }
}

Now we can instantiate Timer simply using getInstance() method.

$timer = Timer::getInstance();
$anotherTimer = Timer::getInstance();

var_dump($timer === $anotherTimer); // bool(true)

$clonedTimer = clone $timer;
var_dump($timer === $clonedTimer); // bool(false)

Preventing clone

But there is a whole in thing. We still can clone Timer. To avoid it we need to throw exception while cloning, let's add magic method __clone().

class Timer
{
    private static $instance;
    private $time;

    private final function __construct()
    {
        $this->time = 0;
    }
    public final static function getInstance() : self
    {
        if (self::$instance instanceof self) {
            return self::$instance;
        }

        return self::$instance = new self();
    }
    public final function __clone()
    {
        throw new LogicException("Cloning timer is prohibited");
    }
}

OK, so we're secure right now. Right? Actually no! There are still some sort of hacks for Timer instantiation.

$timer = Timer::getInstance();
$anotherTimer = unserialize(serialize($timer));

var_dump($timer === $anotherTimer); // bool(false)

Preventing serialization/unserialization

What do we need to do? We need to block __sleep() method.

serialize() checks if your class has a function with the magic name __sleep(). If so, that function is executed prior to any serialization php.net

class Timer
{
    private static $instance;
    private $time;

    private final function __construct()
    {
        $this->time = 0;
    }
    public final static function getInstance() : self
    {
        if (self::$instance instanceof self) {
            return self::$instance;
        }

        return self::$instance = new self();
    }
    public final function __clone()
    {
        throw new LogicException("Cloning timer is prohibited");
    }
    public final function __sleep()
    {
        throw new LogicException("Serializing timer is prohibited");
    }
}

So we're secure instead of unserialize(serialize()) hack right now? NO! There is another hack in PHP.

$timer = Timer::getInstance();
$anotherTimer = unserialize(sprintf(
    "O:%d:\"%s\":1:{s:%d:\"\x00%s\x00time\";i:0;}", 
    strlen(Timer::class), 
    Timer::class, 
    strlen(Timer::class) + strlen('time') + 2, 
    Timer::class
));

var_dump($timer === $anotherTimer); // bool(false)

There have to be __wakeup() method overrided php.net

class Timer
{
    private static $instance;
    private $time;

    private final function __construct()
    {
        $this->time = 0;
    }
    public final static function getInstance() : self
    {
        if (self::$instance instanceof self) {
            return self::$instance;
        }

        return self::$instance = new self();
    }
    public final function __clone()
    {
        throw new LogicException("Cloning timer is prohibited");
    }
    public final function __sleep()
    {
        throw new LogicException("Serializing timer is prohibited");
    }
    public final function __wakeup()
    {
        throw new LogicException("UnSerializing timer is prohibited");
    }
}

OK, so now we're secure in case of cloning and unserialize/serialize, is there any other dirty hack in PHP? Of course there is...

ReflectionClass creates new instance without constructor

Using ReflectionClass we can still create new instance aithout even calling constructor. There is a change that counter wouldn't be setted to the right value but ReflectionParameter will help us with that.

$reflection = new ReflectionClass($timer);
$anotherTimer = $reflection->newInstanceWithoutConstructor();
$reflectionProperty = new ReflectionProperty($anotherTimer, 'time');
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($anotherTimer, 0);
$reflectionProperty->setAccessible(false);

var_dump($timer === $anotherTimer); // bool(false)

In above example we're using ReflectionClass on Timer class to instantiate $anotherTimer. Then we're setting new value for time property using ReflectionProperty with unlocking access to it and locking after setValue on that object.

What can we do with that? Sadly nothing...

The whole thing with reflection mechanism is it can do everything with PHP objects, the only way is to compile PHP without reflection mechanism. But that is impossible in environments using reflection in libraries for code generators or annotation readers. We need them to code less it's obvious.

Conclusion

We've discussed singleton design pattern and implemented protection of another instance creation by cloning and unserialize/serialize. But the whole idea of secure implementation is broken when using reflection mechanism. That's why there's no chance for implementing singleton pattern and be protected against another instance creation in PHP at all.

Google+
LinkedIn
{{ message }}

{{ 'Comments are closed.' | trans }}