OOP: Singleton PHP realization
Glossary overview

OOP: Singleton PHP realization

Объяснение на примере написания плагина для WordPress.

Ядро плагина – абстрактный класс.

abstract class Plugin
{
    protected string $version;
    protected string $plugin_file;
 
// Хранилище экземпляров — массив, потому что может быть
// несколько наследников (SprmBLocks, другой плагин...)
    private static array $instances;

    abstract public function init(): void;

    final public static function getInstance(): static
    {
// static::class = имя класса-наследника, на котором вызвали
        $class = static::class;

        if (! isset(self::$instances[$class])) {
// Первый вызов — создаём экземпляр
            self::$instances[$class] = new static();
        }
// Все последующие вызовы — возвращаем тот же экземпляр
        return self::$instances[$class];
    }

// Конструктор закрыт — нельзя сделать new Plugin() снаружи
    final private function __construct()
    {
        // Singleton
    }

Делаем плагин на основе абстрактного класса

Используем getInstance() чтобы создать один класс.

class SprmBLocks extends Plugin
{
    protected string $version = '__PLUGIN_VERSION__';
    protected string $plugin_file = __FILE__;

    public function init(): void
    {
       ...
    }
}

SprmBLocks::getInstance()->init();

Что происходит при вызовах

Первый вызов:
SprmBLocks::getInstance()
  → $class = 'SprmBLocks'
  → $instances['SprmBLocks'] не существует
  → new static() → new SprmBLocks()
  → сохранил в $instances['SprmBLocks']
  → вернул объект

Второй вызов:
SprmBLocks::getInstance()
  → $class = 'SprmBLocks'
  → $instances['SprmBLocks'] уже существует
  → вернул тот же объект ← не создаёт новый

Три защиты Singleton

1. private __construct()   → нельзя: new SprmBLocks()
2. final __construct()     → нельзя: переопределить конструктор в наследнике
3. final getInstance()     → нельзя: переопределить логику получения в наследнике

Гарантия: экземпляр класса всегда один, создаётся при первом вызове, повторно не создаётся.