Skip to content
Advertisement

PHP7.3: How can I inherit a protected static property with the thightest possible scope without redeclaring it in the child class?

In the application I’m working on, the Model part of the MVC stack is designed to work trough singletons; each Model has a __getInstanceMethod which is

protected static $singleton;
public static function __getInstance(): self {
    if(self::$singleton === null) {
        self::$singleton = __CLASS__;
        self::$singleton = new self::$singleton;
    }
    return self::$singleton;
}

End result is, if __getInstance() is called twice on the same Model class, it returns the same exact object both times.

I tried to reduce code duplication by moving the __getInstance() method to the Model’s parent class, BaseModel, by editing it like so.

class BaseModel {
    protected static $singleton;
    public static function __getInstance(): self {
        if (static::$singleton === null) {
            static::$singleton = static::class;
            static::$singleton = new static::$singleton();
        }
        return static::$singleton;
    }
}
class AModel extends BaseModel {
    protected static $singleton;
    /** ... */
}
class BModel extends BaseModel {
    protected static $singleton;
    /** ... */
}

AModel::__getInstance(); // AModel
BModel::__getInstance(); // BModel

Problem is, I need to manually add a $singleton property to each and every Model class, otherwise I’ll always get returned the instance of the first Model class I called the method on.

class BaseModel {
    protected static $singleton;
    public static function __getInstance(): self {
        if (static::$singleton === null) {
            static::$singleton = static::$class;
            static::$singleton = new static::$singleton();
        }
        return static::$singleton;
    }
}
class AModel extends BaseModel {}
class BModel extends BaseModel {}

AModel::__getInstance(); // AModel
BModel::__getInstance(); // Still AModel

Is there a way I can avoid doing that?

Advertisement

Answer

You could switch to an “instance map”, e.g.:

<?php
declare(strict_types=1);

error_reporting(-1);
ini_set('display_errors', 'On');

class BaseModel
{
    protected static $instances = [];

    public static function __getInstance(): self
    {
        if (!isset(static::$instances[static::class])) {
            static::$instances[static::class] = new static();
        }

        return static::$instances[static::class];
    }
}

class AModel extends BaseModel
{
}

class BModel extends BaseModel
{
}

echo get_class(AModel::__getInstance()), "n";
echo get_class(BModel::__getInstance());

https://3v4l.org/qG0qJ


and with 7.4+ it could be simplified to:

<?php
declare(strict_types=1);

error_reporting(-1);
ini_set('display_errors', 'On');

class BaseModel
{
    private static array $instances = [];

    public static function __getInstance(): self
    {
        return static::$instances[static::class] ??= new static();
    }
}
User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement