Только для читателей Lifeexample возможно открыть интернет-магазин на Moguta.CMS со скидкой в 15%
PHP Singleton (Синглтон)
Здравствуйте, уважаемые читатели блога LifeExample, сегодня мы будем разбираться с очередным шаблоном программирования в PHP — Синглтоном (Singleton).
Паттерн Singleton по-настоящему полезен в крупных проектах, и на частных примерах будет не просто понять, для чего он нужен. Поэтому я предлагаю сделать нечто большее, чем просто привести код класса Singleton, как это делается в большинстве статей и материалах по теме.
Подобно тому, как мы разбирались с паттерном MVC, я хочу показать вам, как по шагам сделать маленькую систему работы с базой данных, используя в php синглтон, для подключения к ней.
Концепция шаблона Singleton
Страшное слово синглтон ассоциируемое у меня, по названию, c мелафоном из сериала «Гостья из будущего» (1984 г.), совершенно не имеет никакого сходства с машиной времени.
Singleton – это обычный php класс, в логику работы которого включена проверка на эксклюзивность его создания. Т.е. объект класса, построенный по шаблону синглтона, может быть создан лишь один раз. Все будущие попытки обратиться к его методу или свойству, создав новый объект, будут нейтрализованы логикой его работы и перенаправлены на уже имеющийся экземпляр.
Таким образом, класс, построенный по принципу синглтона, будет защищен от многочисленного создания и дублирования действий при интерпретации кода.
Представим, что у нас есть класс, инициализирующий какие либо настройки. Для инициализации он может считывать информацию из текстовых файлов и/или таблиц БД.
Также в этом классе присутствуют интерфейсы для получения тех или иных настроек. Так вот, когда в разных частях программы нам необходимо получить тот или иной параметр настроек, мы создаем новый объект и с помощью его интерфейса получаем данные.
В ходе работы системы, проведение таких операций может потребоваться очень часто, и каждый раз будет создаваться новый экземпляр класса, а соответственно будет дублироваться работа по сбору настроек из БД и файлов, что не очень лестно скажется на работе всей системы.
Как вариант можно создать глобальный объект и пользоваться только им, но ничто не защитит его от повторного создания, и пришедший в команду новый неопытный разработчик, пренебрегая соглашением об использовании глобального объекта, будет создавать дубликат столько раз, сколько ему захочется.
Решением этой проблемы как раз и занимается шаблон Singleton.
Обычный пример PHP Singleton
Давайте ознакомимся с концепцией и классическим примером реализации php синглтона. Взгляните на этот код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <?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) класса стандартным образом:
1 | obj= new someClass(); |
то мы получим сообщение об ошибке, в котором сказано, что конструктор доступен только изнутри класса.
1 | Call to private someClass::__construct() from invalid context in… |
Для того чтобы создать объект нам нужно вызвать статический метод getInstance(); таким образом:
1 | someClass::getInstance(); |
Единственной точкой входа в класс является статическая функция getInstance() цель которой – создать первый и единственный экземпляр класса, записав его в переменную $_instance. При вызове getInstance() функция создает экземпляр класса, а следовательно задействует механизм конструктора.
Неотъемлемой частью синглтона в PHP является объявление приватных магических методов __clone() и _wakeup() , предотвращающих возможные дублирования объекта.
На этом классическое описание модели, во многих ресурсах сети заканчивается. Но остается вопрос, как пользоваться синглтоном в PHP, а главное зачем?
Пока не попробуете на практике, смысла не уловите, поэтому я настоятельно советую вам потратить еще 10 минут своего свободного времени, и посмотреть как можно на практике использовать паттерн Singleton в PHP.
PHP Singleton на примере класса для работы с БД
Если кто-то забыл, или не знает основ для работы с базами данных в php, читайте статью PHP работа с базой данных (Часть 1)
Самым подходящим примером будет маленькая система для работы с БД. Под маленькой системой я подразумеваю три файла :
- index.php — точка входа в систему
- class.db.php — singleton для работы с БД
- class.order.php — некоторый вымышленный класс внутри которого идет работа с БД с помощью синглтона
Первым делом создадим index.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <?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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | <?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.
Многие новички делают не правильные выводы относительно данного шаблона программирования и пытаются сделать наследование дочерних классов от синглтона.
При таком подходе теряется весь смысл синглтона как механизма, защищающего дублирование объектов.
Если вы хотите наследовать некоторый класс от синглтона, то в самом синглтоне вам придется открыть публичный доступ к конструктору класса. В противном случае вы будете получать ошибку:
1 | Call to private someClass::__construct() from invalid context in… |
А открыв публичный доступ ломается вся защита.
Поэтому наследовать PHP Singleton, не рационально и бессмысленно, так как можно обращаться к классу синглтона из любой точки программы, инициализировав его единожды в index.php.
В следующем классе это наглядно продемонстрировано:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <?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 допишите следующее:
1 2 3 4 5 6 7 8 | require 'class.order.php'; $order = new Order(); echo $order->getOrderList(); //попробуем создать новый экземпляр DB echo "<span style='color:red'>"; $DB2 = new DB(); |
Запустив приложение вы должны будете наблюдать такую картину:
Ошибка в конце страницы сгенерирована намеренно, для демонстрации защиты от дублирования PHP Singleton. Избавиться от ошибки можно, удалив последнюю строку:
1 | $DB2 = new DB(); |
На этом, я предлагаю завершить наше знакомство с очередным шаблоном программирования PHP Singleton‘ом, надеюсь, материал окажется вам полезен. Подписывайтесь на RSS обновления и не пропустите следующей статьи о другом не мало известном паттерне программирования Registry.
Читайте также похожие статьи:
Чтобы не пропустить публикацию следующей статьи подписывайтесь на рассылку по E-mail или RSS ленту блога.
Комментарии
Отлично, Марк!! Замечательная статья!! Ты прям читаешь мысли, хотел тебя просить доходчиво объяснить про паттерны программирования. Очень полезно!!
На здоровье, рад что понравилось. )
Неплохая статья, начинаю как раз изучать паттерны — все доступно разжевано. Ошибок только много грамматических, немного раздражает ). Но это мелочи. Еще хотелось бы видеть примеры использования Singleton скажем с PDO или mysqli (так как поддержка mysql модуля будет прекращена), а так же вариант отдельного хранения настроек БД скажем БД в ini или xml, но никак не в index.php.
Спасибо за внимание ) И за статью.
С грамматикой у меня проблемы. Поправил явные ошибки и опечатки, теперь по легче читать будет. Про PDO и mysqli? в планах, есть статья. Зачем хранить настройки для подключения к базе в INI или XML? Чем хуже php файл?
Вот такой вот вопрос: Почему не используется конструктор в синглтоне? Например для задания\чтения настроек. Или синглтон не предусматривает каких либо свойств класса и дополнительных методов (в т.ч. в конструкторе)?
Почему не использую? В данном примере конструктор используется для установки соединения с базой и задания кодировки таблиц.
>Или синглтон не предусматривает каких либо свойств класса и дополнительных методов (в т.ч. в конструкторе)?
Синглтон, не исключает использование свойств и методов класса. В примере также демомнстрируется обращение к методу query().
Примечание! Для более оптимизированной работы с синглтоном, можно внести вызов getInstance(); внутрь используемых классом публичных функций. Это поможет устранить необходимость явного определения экземпляра класса описанным выше методом DB::getInstance();
Спасибо, наконец-то нашёл нормальное объяснение синглтон с наследованием.
Спасибо!
Сегодня и я понял зачем он )
Спасибо, очень полезная статья!
Маленькая только ошибочка в классе: вместо
надо
— тогда кодировка работает.
Я вот не совсем понял с:
— public function fetch_object
— public function fetch_array
— public function insert_id
В index.php DB::fetch_object вызываются как статическая, а в самом классе они не являются таковыми.
Почему так?
Спасибо.
Это ошибка, конечно нужно объявлять функции статическими: static .
Поправил пример.
Огромное спасибо за статью на человеческом языке. Так и паттерны учить можно )
Спасибо! Статья просто супер!
спасибо человеческое) побольше бы таких авторов и статей) удачи)
Спасибо большое, благодаря Вашей статьи наконецто произошел сдвиг с мертвой точки в обучении патернов.
Всегда рад, помочь. Кроме PHP Singleton (Синглтон) на блоге можно найти еще статьи по паттернам.
А почему в классе не определено свойство connect?
2
3
4
5
6
echo "<br/><em>1. Установка соединения с хостом...";
//подключаемся к БД
$this->connect = mysql_connect(HOST, USER, PASSWORD) or die("Невозможно установить соединение".mysql_error());
// выбираем таблицу
....
или вот еще пример
2
3
4
if(isset($obj->connect)){
$obj->count_sql++;
....
Вроде как также отсутствует объявление в классе count_sql.
Как в Singleton можна реализовать переключение между базами
Можно реализовать. Создайте метод для этого внутри класса.
Спасибо за статью!
Вопрос: все public методы надо объявлять static?
Добавление: если класс объявить final, то и наследовать
будет нельзя.
Огромное спасибо! Очень полезная статья.Действительно почти нигде не встретишь вот такой красивый пример применения паттерна.
+1 к «Виталий 18.11.2013»
У вас не объявлены свойства класса:
$obj->connect
$obj->count_sql
Магических геттеров нет. Так как же это работает?
Я че-то не пойму- зачем в методе __construct делать подключение к базе. Если нерадивый программист попытается создать новый объект- ошибка конечно выведется, но после подключения к базе. Или я не прав?
Смысл сингл тона в том чтобы не допустить нескольких подключений к базе. Код в теле конструктора будет отрабатывать только один раз. А при последующих попытках создать экземпляр класса будет игнорироваться.
Марк, спасибо большое, очень ясное изложение мыслей!
Простите,но вы так и не ответили на вопрос откуда взято это:
$obj->count_sql.Спасибо
Аналогично не понял как это работает
Ошибочка в коде:
$this->query(‘SET names «utf8″‘);
Этот запрос выполнен не будет, ибо
if(isset($obj->connect)){
…
}
Проверочку не пройдёт. Метод вызывается из конструктора и на момент вызова объект ещё создан не будет.
В вашем скриншоте сообщение о выполнении запроса с кодировкой отсутствует)
В синглтоне можно организовать что если $exp != null mysql_close() и вернуть уже пустую $exp ?