PHP

PHP Singleton (Синглтон)

Здравствуйте, уважаемые читатели, сегодня мы будем разбираться с очередным шаблоном программирования в PHP – Синглтоном (Singleton).

Паттерн Singleton по-настоящему полезен в крупных проектах, и на частных примерах будет не просто понять, для чего он нужен. Поэтому я предлагаю сделать нечто большее, чем просто привести код класса Singleton, как это делается в большинстве статей и материалах по теме.

Подобно тому, как мы разбирались с паттерном MVC, я хочу показать вам, как по шагам сделать маленькую систему работы с базой данных, используя в php синглтон, для подключения к ней.

Концепция шаблона Singleton

Страшное слово синглтон ассоциируемое у меня, по названию, c мелафоном из сериала «Гостья из будущего» (1984 г.), совершенно не имеет никакого сходства с машиной времени.

Singleton – это обычный php класс, в логику работы которого включена проверка на эксклюзивность его создания. Т.е. объект класса, построенный по шаблону синглтона, может быть создан лишь один раз. Все будущие попытки обратиться к его методу или свойству, создав новый объект, будут нейтрализованы логикой его работы и перенаправлены на уже имеющийся экземпляр.

Таким образом, класс, построенный по принципу синглтона, будет защищен от многочисленного создания и дублирования действий при интерпретации кода.

Представим, что у нас есть класс, инициализирующий какие либо настройки. Для инициализации он может считывать информацию из текстовых файлов и/или таблиц БД.

Также в этом классе присутствуют интерфейсы для получения тех или иных настроек. Так вот, когда в разных частях программы нам необходимо получить тот или иной параметр настроек, мы создаем новый объект и с помощью его интерфейса получаем данные.

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

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

Решением этой проблемы как раз и занимается шаблон Singleton.

Обычный пример PHP Singleton

Давайте ознакомимся с концепцией и классическим примером реализации php синглтона. Взгляните на этот код:
<?php
 class someClass {
    protected static $_instance; 

        private function __construct() {        
    }

    public static function getInstance() {
        if (self::$_instance === null) {
            self::$_instance = new self;   
        }
 
        return self::$_instance;
    }
  
    private function __clone() {
    }

    private function __wakeup() {
    }     
}
?>

В данном коде представлен некоторый класс, в логике работы которого, реализован паттерн – синглтон.

Первым делом объявляется защищенная статическая переменная $_instance, назначением которой является хранение в себе единственного экземпляра данного класса.

Конструктор в синглтоне защищен от публичного вызова, а следовательно исключает дублирование объектов.

Другими словами, если попытаться создать объект (PHP Singleton) класса стандартным образом:
obj= new someClass();

то мы получим сообщение об ошибке, в котором сказано, что конструктор доступен только изнутри класса.
Call to private someClass::__construct() from invalid context in…

Для того чтобы создать объект нам нужно вызвать статический метод getInstance(); таким образом:
someClass::getInstance();

Единственной точкой входа в класс является статическая функция getInstance() цель которой – создать первый и единственный экземпляр класса, записав его в переменную $_instance. При вызове getInstance() функция создает экземпляр класса, а следовательно задействует механизм конструктора.

Неотъемлемой частью синглтона в PHP является объявление приватных магических методов __clone() и _wakeup() , предотвращающих возможные дублирования объекта.

На этом классическое описание модели, во многих ресурсах сети заканчивается. Но остается вопрос, как пользоваться синглтоном в PHP, а главное зачем?

Пока не попробуете на практике, смысла не уловите, поэтому я настоятельно советую вам потратить еще 10 минут своего свободного времени, и посмотреть как можно на практике использовать паттерн Singleton в PHP.

PHP Singleton на примере класса для работы с БД

Самым подходящим примером будет маленькая система для работы с БД. Под маленькой системой я подразумеваю три файла :

index.php – точка входа в систему
class.db.php – singleton для работы с БД
class.order.php – некоторый вымышленный класс внутри которого идет работа с БД с помощью синглтона
Первым делом создадим index.php:
<?php
error_reporting(E_ALL);
require 'class.db.php';

// константы для подключени к БД

define('HOST', 'localhost'); //сервер
define('USER', 'root'); //пользователь
define('PASSWORD', ''); //пароль
define('NAME_BD', 'test');//база    
 
DB::getInstance(); // инициализация экземпляра класса дл работы с БД
//свободное использование класса

//вывод таблицы продуктов
$result=DB::query("SELECT id,product FROM `product`");
echo '<h2>Таблица продуктов:</h2> <table border="1">';
while($obj=DB::fetch_object($result)){
    echo '<tr><td>'.$obj->id.'</td><td>'.$obj->product.'</td><tr>';     
}
echo '</table>';

В коде index.php подключается файл class.db.php, содержащий реализацию синглтона, и производится инициализация констант для доступа к базе данных, а также инициализация объекта для работы с БД DB::getInstance();.

Примечание! Для более оптимизированной работы с синглтоном, можно внести вызов getInstance(); внутрь используемых классом публичных функций. Это поможет устранить необходимость явного определения экземпляра класса описанным выше методом DB::getInstance();

Далее следует пример работы с классом синглтона, в котором выводится содержимое таблицы product.

Чтобы это все заработало нужно создать файл class.db.php
<?class DB {

        protected static $_instance;  //экземпляр объекта
  
        public static function getInstance() { // получить экземпляр данного класса 
            if (self::$_instance === null) { // если экземпляр данного класса  не создан
                self::$_instance = new self;  // создаем экземпляр данного класса 
            } 
            return self::$_instance; // возвращаем экземпляр данного класса
        }
   
        private  function __construct() { // конструктор отрабатывает один раз при вызове DB::getInstance();
                echo "<br/><em>1.  Установка соединения с хостом...";
                //подключаемся к БД
                $this->connect = mysql_connect(HOST, USER, PASSWORD) or die("Невозможно установить соединение".mysql_error());
                // выбираем таблицу
                echo "<br/>2.  Выбор базы...";
                mysql_select_db(NAME_BD, $this->connect) or die ("Невозможно выбрать указанную базу".mysql_error());
                // устанавливаем кодировку таблицы
                echo "<br/>3.  Устанавливаем кодировку базы: ";
                $this->query('SET names "utf8"');   
                echo "<br/> Конструктор успешно открыл соединение с БД! и установил кодировку.</em>";
        
        }
 
        private function __clone() { //запрещаем клонирование объекта модификатором private
        }
        
        private function __wakeup() {//запрещаем клонирование объекта модификатором private
        }
   
        public static function query($sql) {
        
            $obj=self::$_instance;
        
            if(isset($obj->connect)){ 
                $obj->count_sql++;
                $start_time_sql = microtime(true);
                $result=mysql_query($sql)or die("<br/><span style='color:red'>Ошибка в SQL запросе:</span> ".mysql_error());
                $time_sql = microtime(true) - $start_time_sql;
                echo "<br/><br/><span style='color:blue'> <span style='color:green'># Запрос номер ".$obj->count_sql.": </span>".$sql."</span> <span style='color:green'>(".round($time_sql,4)." msec )</span>";             
                
                return $result;
            }
            return false;
        }   
    
        //возвращает запись в виде объекта
        public static function fetch_object($object)
        {
            return @mysql_fetch_object($object);
        }

        //возвращает запись в виде массива
        public static function fetch_array($object)
        {
            return @mysql_fetch_array($object);
        }
        
        //mysql_insert_id() возвращает ID, 
        //сгенерированный колонкой с AUTO_INCREMENT последним запросом INSERT к серверу
        public static function insert_id()
        {
            return @mysql_insert_id();
        }
        
    
}

Сей класс является рабочей реализацией паттерна singleton в php, а также наглядно демонстрирует важность единственности объекта. Для тех кто не видит важности поясню: в конструкторе данного класса производится подключение к БД, если убрать логику синглтона, то нерадивые программисты смогут организовать тысячи бессмысленных подключений к БД .

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

Раз уж заговорил о возможностях класса, расскажу и о том, что кроме количества запросов, ведется подсчет затраченного на исполнения времени, а это бывает очень полезным при отладке приложения.

Кстати, если в местах вывода заменить echo на вывод в файл, то получится наглядный лог запросов.

На этом описание класса предлагаю завершить, но если есть вопросы пишите в комментариях, отвечу.

Теперь файл index.php можно запускать. Ах да, конечно же вам понадобится импортировать дамп тестовой базы, приложенный в исходниках, или обращаться к своей БД :

Наследование Singleton в PHP

Мы уже имеем возможность обращаться к функциям синглтона, теперь попробуем сделать это в отдельном классе Order.

Многие новички делают не правильные выводы относительно данного шаблона программирования и пытаются сделать наследование дочерних классов от синглтона.

При таком подходе теряется весь смысл синглтона как механизма, защищающего дублирование объектов.

Если вы хотите наследовать некоторый класс от синглтона, то в самом синглтоне вам придется открыть публичный доступ к конструктору класса. В противном случае вы будете получать ошибку:
Call to private someClass::__construct() from invalid context in…

А открыв публичный доступ ломается вся защита.

Поэтому наследовать PHP Singleton, не рационально и бессмысленно, так как можно обращаться к классу синглтона из любой точки программы, инициализировав его единожды в index.php.

В следующем классе это наглядно продемонстрировано:
<?php
//попробуем создать класс и внутри него работать с БД
class Order {
    public function getOrderList(){
    
       // $result=DB::query("INSERT `order` SET id=4,content=777");
        $result=DB::query("SELECT id,content FROM `order` where id=4");
        
        $html='<h2>Таблица заказов:</h2> <table border="1">';
        while($obj=DB::fetch_object($result)){
            $html.='<tr><td>'.$obj->id.'</td><td>'.$obj->content.'</td><tr>';       
        }
        $html.='</table>';
        return $html;
    }       
}

Создайте class.order.php и вставьте в него вышеприведенный код. В конец файла index.php допишите следующее:
require 'class.order.php';

$order = new  Order();
echo $order->getOrderList();

//попробуем создать новый экземпляр DB 
echo "<span style='color:red'>";
$DB2 = new  DB();

Запустив приложение вы должны будете наблюдать такую картину:



Ошибка в конце страницы сгенерирована намеренно, для демонстрации защиты от дублирования PHP Singleton. Избавиться от ошибки можно, удалив последнюю строку:
$DB2 = new  DB();

На этом, я предлагаю завершить наше знакомство с очередным шаблоном программирования PHP Singleton‘ом, надеюсь, материал окажется вам полезен. Подписывайтесь на RSS обновления и не пропустите следующей статьи о другом не мало известном паттерне программирования Registry.
Теги:
Singleton, Синглтон
Добавлено: 26 Сентября 2013 02:15:23 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...