Здравствуйте, уважаемые читатели, сегодня мы будем разбираться с очередным шаблоном программирования в 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.