Новый взгляд на старый Singleton.

Шаблон проектирования Singleton (одиночка) это основа, с него начинается изучение паттернов, потому что он самый простой в понимании, и в целом описывает суть шаблонов.

class Product {
    private static $instance;  
    
    public static function getInstance() { 
        if ( empty(self::$instance) ) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    private function __construct(){}  
    private function __clone()    {} 
    private function __wakeup()   {}  
}

Принято, что в Singleton присутствует статический метод под названием getInstance(), поэтому если в коде встретится getInstance() , — знайте, перед вами на 99,9 % Singleton и не имеет значение, что сам класс называется не Singleton, а к примеру Product.

Основная магия происходит в нем, а именно, метод getInstance() проверяет статическое свойство $instance, если оно пустое, значит создает объект с помощью конструкции new self() и помещает его в $instance. В противном случае возвращает то самое $instance с объектом в нем. Вариантов реализации getInstance() вы встретите много, но все они сводятся к одному.

public static function getInstance() {    
        if ( empty(self::$instance) ) {
            self::$instance = new self();
			
        }
        return self::$instance;
    }

Еще один:

 public static function getInstance() { 
        if (static::$instance === null) {
            static::$instance = new static();
        }
        return static::$instance;
    }

и еще …

public static function getInstance()
    {
        if (!(self::$instance instanceof self)) {
            self::$instance = new self();
        }
        return self::$instance;
    }

Стоит заметить, что мы можем сколько угодно обращаться к getInstance(), но он будет возвращать только один созданный объект, все остальные переменные будут содержать ссылку на него.

$firstProduct = Product::getInstance();
$secondProduct = Product::getInstance();
var_dump($firstProduct); // object(Product)#1 (0) { }
var_dump($secondProduct); // object(Product)#1 (0) { }

Для проверки создания объектов мы используем var_dump, а не привычное print_r, что бы увидеть созданный объект и его порядковый номер.

Конечно, нам надо обезопасить свой Singleton и закрыть все возможности создания объекта в обход new self(). Сделать это можно с помощью пустых «магических» методов __construct(), __clone() и __wakeup().

Почему статический метод, а не обычный в Singleton?

Статический метод принадлежит классу, а не объекту, то есть для его вызова нам не надо создавать объект, поэтому это дополнительная гарантия, что при вызове getInstance() не будет создан новый объект.

Сейчас рассмотрим практический пример использования Singleton при создании класса соединения с базой данных. Данный пример взят из реального проэкта, который используется для тарифицирования абонентов путем ежедневного списания средств за предоставляемые услуги.

<?php
/**
 * Created by PhpStorm.
 */

class conPDO {

    /**
     * @var $link_crm
     */
    private $link_crm;

    /**
     * @var $link_billing
     */
    private $link_billing;

    /**
     * @return connectDB
     */
    private static $instance;

    public function __construct()
    {
        $options['crm']['user'] = 'crmUser';
        $options['crm']['host'] = '10.00.00.160';
        $options['crm']['pass'] = 'ceArfWcqwrWE';
        $options['crm']['dbname'] = 'db_1';

        $options['billing']['user'] = 'billingUser';
        $options['billing']['host'] = '10.00.00.255';
        $options['billing']['pass'] = 'VuGfgdGdfgdg';
        $options['billing']['dbname'] = 'db_2';
        
        //Делаем коннекты с CRM и Billing
        foreach($options as $name => $prop) {
            try {
                $connect = 'link_'.$name;
                $dsn = "mysql:dbname={$prop['dbname']};host={$prop['host']}";
                $this->$connect = new PDO($dsn, $prop['user'], $prop['pass']);
                $this->$connect->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
                $this->$connect->query('SET NAMES utf8');
            } catch (PDOException $e) {
                echo $e->getMessage();
                exit();
            }
        }
    }

    /**
     * Метод Singleton
     * @return connectPDO
     */
    public static function getInstance()
    {
        //Если экземпляр класса не создан - создаем
        if( empty( self::$instance) ) {
            self::$instance = new conPDO();
        }
        return self::$instance;
    }

    /**
     * Коннект к BD CRM
     * @return PDO
     */
    public function getConnectCRM()
    {
        return $this->link_crm;
    }

    /**
     * Коннект к BD billing
     * @return object PDO
     */
    public function getConnectBilling()
    {
        return $this->link_billing;
    }

    /**
     * Выбираем соединение
     * @return object PDO
     */
    public function checkConnection($bd)
    {
        if($bd == 'crm') {
            $connect = self::$instance->getConnectCRM();
        } else {
            $connect = self::$instance->getConnectBilling();
        }
        return $connect;
    }

    /**
     * Метод для выполнения запроса
     * @return object PDOStatement
     */
    static public function query($connect,$query,$params = null)
    {
        try {
            $connect = self::$instance->checkConnection($connect);
            $sth = $connect->prepare($query);
            $sth->execute($params);
       } catch (PDOException $e) {
            exit();
        }
        return $sth;
    }

    /**
     * Метод возвращающий последний id
     * @return object PDOStatement
     */
    static public function lastInsertId($connect)
    {
        $connect = self::$instance->checkConnection($connect);
        $sth = $connect->lastInsertId();
        return $sth;
    }

    /**
     * Метод beginTransaction
     * @return object PDOStatement
     */
    static public function beginTransaction($connect)
    {
        try {
            $connect = self::$instance->checkConnection($connect);
            $sth = $connect->beginTransaction();
        } catch (PDOException $e) {
            exit();
        }
        return $sth;
    }

    /**
     * Метод rollBack
     * @return object PDOStatement
     */
    static public function rollBack($connect)
    {
        try {
            $connect = self::$instance->checkConnection($connect);
            $sth = $connect->rollBack();

        } catch (PDOException $e) {
            exit();
        }
        return $sth;
    }

    /**
     * Метод commit
     * @return object PDOStatement
     */
    static public function commit($connect)
    {
        try {
            $connect = self::$instance->checkConnection($connect);
            $sth = $connect->commit();
        } catch (PDOException $e) {
            exit();
        }
        return $sth;
    }

    /**
     * Закрываем доступ к функции вне класса.
     * Паттерн Singleton не допускает вызов
     * этой функции вне класса
     * @return false
     */
    private function __clone()
    {
        return false;
    }

    /**
     * Закрываем доступ к функции вне класса.
     * Паттерн Singleton не допускает вызов
     * этой функции вне класса
     * @return false
     */
    public function __wakeup()
    {
        return false;
    }
}

В данном примере видим, что для соединения с двуми базами данных используется класс PDO и его встроенные методы: lastInsertId(), beginTransaction(), rollBack(), commit().

Добавить комментарий