Как стать автором
Обновить

Шаблон проектирвоания «завод».

Пишу в гугл переводчике «завод» — выдает factory. Пишу factory — выдает завод…
В моем понимании завод — что-то чуть-чуть круче чем фабрика. Теперь о паттернах.
Можно придумать огромное множество вариантов реализании данного шаблона (http://www.php.net/manual/ru/language.oop5.patterns.php), Но суть, на то он и шаблон одна.
Ниже будет мой вариант реализации шаблона. Соль — возможность не явного создания объектов известных классов.
Однако сдеть слишком «узкий» пример, но не судите строго.

<?php
class TEST
{
/**
* @var stdClass Объект конфигурации портала
* @access public
*/
public $conf = null;

/**
* Запуск механизма создания модулей,
* которые необходимо автозагрузить.
*
* @param stdClass $config объект конфигурации
* @access public
* @version 2.0.1
*/
public function __construct(stdClass $config = null)
{
//Конфигурация ядра
$this->conf = $config;
unset($config);

//По умолчанию - Turn on output buffering
if(!$this->conf->option->ob_start_off)
{
//Отключаем страндартный поток вывода. Перенаправляем в буффер.
ob_start();
}

//Если включен режим отладки
if(!defined(TEST_DEBUG))
{
define(TEST_DEBUG,($this->conf->option->debug ? 1 : 0));
}

//Если существуют разделы модулей
if($this->conf->boot)
{
//Выбираем разделы модулей (для автозагрузки)
foreach ($this->conf->boot as $kboot => &$aboot)
{
//Если модуль ядра необходимо подключить (автозагрузка)
if($aboot)
{
//Выбираем поъядра раздела ядра для автозагрузки
foreach ($this->conf->{$kboot} as $akey => &$aval)
{
//Если подъядро необходимо подключить (автозагрузка)
if($aval)
{
try
{
//Пробуем создать модуль
$this->factory($akey,$kboot);
}
catch (Exception $e)
{
//Если появились исключения - вывести ошибку и остановить работу
die($e->getMessage().$e->getTraceAsString());
}
}
}
}
}
}
}

/**
* Агрегирование модулей.
* По сути паттерн factory
*
* @param string $akey имя класса
* @param string $kboot тип класса (в каком разделе находиться класс) - опционально
* @todo $this->factory($akey,$kboot) обычно востребовано только системой
* @access public
* @version 1.0
*/
public function factory($akey = null, $kboot = null)
{
//Если свойство (объект) уже объявлено в системе и
//если свойство (объект) нужного класса (имя объекта и название класса - совпадают)
if(property_exists($this, $akey) && is_a($this->$akey, $akey))
{
//Возвращаем обект
return $this->$akey;
}

//Если класс еще не агрегирован в систему и
//если не указан тип класса,
//то производим поиск во всех подразделах конфигурации
if(!class_exists($akey) && !$kboot && $this->conf->boot)
{
//Здесь очень важный момент, который в будущем необходимо исправить.
//Переменная $f никогда не используется, т.к. в foreach на самом деле нам необходимо
//узнать ключи, а не значения. Но как организовать такой "пробег"
//по ключам объекта(!) stdClass`а, мне пока не пришло на ум...
foreach ($this->conf->boot as $k => &$f)
{
//Если в разделе найден класс - завершаем циклический обход,
//запоминая при этом необходимый нам подраздел
if(isset($this->conf->{$k}->{$akey}))
{
$kboot = $k;
break;
}
}

//Если не обявлен раздел ядра (и не удалось осуществить поиск)
if(!$kboot)
{
//Создаем исключение ошибки
throw new Exception($akey);
}

//попытка подключения файла, в котором находится класс :
//путь к файлу - это директория, указанная в конфигурации
//ключа dir->раздел_модулей, например $cong->dir->core, $cong->dir->extension
//плюс название класса (так же должен называться файл, содержащий этот класс)
//плюс расширение файла - конфигурация filePostfix (по примеру dir)
if(!@include_once($this->conf->dir->{$kboot}.$akey.$this->conf->filePostfix->{$kboot}))
{
//Создаем исключение ошибки
throw new Exception($this->conf->dir->{$kboot}.$akey.$this->conf->filePostfix->{$kboot});
}
}

//Попытка создать объект
if(!$this->{$akey} = new $akey //Имя объекта устанавливается как имя класса
(
(@is_object($this->conf->{$akey}) //Если для этого модуля
? $this->conf->{$akey} //указаны дополнительные
: null //настройки
),
$this //Передаем ссылку на объект ядра
//для того, чтобы модуль имел возможность
//обращаться к другим модулям
)
)
{
//Если не получилось создать объект - создаем исключение ошибки загрузки ядра
throw new Exception($akey);
}

//Возращаем объект
return $this->{$akey};
}

/**
* Медот автомитаческого создания
* необходимых системе модулей
* Отрабатывает при обращении к объекту
* класса ядра, которого нет (пока) в системе.
*
* @todo $this->какой_либо_объект
* @param object $wh имя объекта
* @return object|false
* @access protected
* @version 1.0
*/
protected function __get($wh)
{
try
{
//Пробуем создать объект, который
//(пока) не присутствует в системе.
//В ходе работы данного метода возможно
//создание икслючений об ошибке
$wh = $this->factory($wh);
}
catch (Exception $e)
{
//Ошибка создания объекта
return false;
}

//Возращаем созданный объект
return $wh;
}
}

abstract class Test_module
{
/**
* Конструктор класса.
* Определение общих свойств и методов класса-наследника.
* Многие методы имеет смысл перегрузить.
*
* @param object|array $conf конфигурация свойств класса-наследника
* @return true|die()
* @todo parent::__construct($conf,$this)
* @access protected
*/
protected function __construct($conf = null)
{

if(!is_object($conf) && !is_array($conf))
{
return is_string($conf) ? $this->conf = $cong : false;
}

foreach ($conf as $akey => &$aval)
{
$this->{$akey} = $aval;
}

return true;
}

/**
* Деструктор класса.
*
* @return true
* @access public
*/
public function __destruct()
{
//простая "заглушка"
return true;
}
}
?>


И предпоследнее, но не по значению, объясню как вообще устроено все в окружении того куска кода, который я показал.
Суть на пальцах — в index.php создаеться класс, кторорый наследуются от приведеного выше TEST. Назовем его site.
В конструкторе site пихаем в stdClass настроки, кормим в конструктор класса-родителя (TEST) и готово.
Пример — ВСЕ ЧИТАТЬ НЕ НУЖНО
<?php

//Загрузка микроядра HERUVIM
include_once('/opt/www/_core/core.php');

/**
*
* @version 0.1
* @final
*/
class stroy_site extends TEST
{

/**
* Начальная конфигурация системы
*
* @var stdClass $config
*/
private $config;

public function __construct()
{
//Директория этого проекта
define('ROOT_DIR', realpath(dirname(__FILE__)).'/');

//Создаем конфигурацию системы
$this->config = new stdClass();

//Сайт_Id понадобиться для спецификаций выборки данных из общей БД
$this->config->siteId = 2;

//Некоторые модули в разделе необходимо автозагружать, а если нет таких модулей в разделе, то поставить false. но объявить все равно нужно
$this->config->boot->core = true;
$this->config->boot->extensions = true;

//Директория модулей
$this->config->dir->core = '/opt/www/_core/';
//Расширение файлов модулей
$this->config->filePostfix->core = '.php';

//Директория расширений системы
$this->config->dir->extensions = '/opt/www/_extensions/';
//Окончание файлов расширений системы
$this->config->filePostfix->extensions = '.php';

/* Используемые модули системы */
$this->config->core->tools = true; //true - автозагрузка
$this->config->core->headers = true;
$this->config->core->controller = true;
$this->config->core->db = true;
$this->config->core->user = true;
$this->config->core->xml = true;
$this->config->core->html = true;
$this->config->core->view = true;
$this->config->core->cache = false; // false - "дозагрузка" при необходимости
/* Используемые модули системы HERIVIM */

/* Расширения системы */
$this->config->extensions->catalog = false;
$this->config->extensions->company = false;
$this->config->extensions->banner = false;
$this->config->extensions->statistic = true;
$this->config->extensions->person = true;
$this->config->extensions->prompting = false;
$this->config->extensions->search = false;
$this->config->extensions->price = false;
$this->config->extensions->geoIP = false;
/* Расширения системы */

/* Настройки DB */
$this->config->db->DBtype = 'mysql';
$this->config->db->DBserver = 'localhost';
$this->config->db->DBlogin = 'root';
$this->config->db->DBpassword = '';
$this->config->db->DBname = 'test';
$this->config->db->DBencoding = 'utf8';
$this->config->db->debug = false;
/* Настройки DB */

/* Настройки HEADERS */
$this->config->headers->initSession = 691200;
$this->config->headers->upload_max_filesize = '10M';
$this->config->headers->error_reporting = 6143;
/* Настройки HEADERS */

/* Настройки VIEW */
$this->config->view->is = 'xml';
/* Настройки VIEW */

/* Настройки USER */
$this->config->user->authMethod = 'POST';
$this->config->user->authLogin = 'login';
$this->config->user->authPassword = 'password';
$this->config->user->useDir = false;
$this->config->user->cookie = false;
$this->config->user->infoIsProperty = false;
$this->config->user->AuthSQL = create_function('$login,$password','return \'SELECT id FROM Users WHERE Email="\'.addslashes($login).\'" AND ( Password="\'.md5($password).\'" OR Old_password= OLD_PASSWORD("\'.addslashes($password).\'") ) AND Activation IS NULL LIMIT 1\';');
$this->config->user->selectInfo = create_function('$id,$what = null','return \'SELECT \'.( (isset($what)) ? $what : \'*\' ).\' FROM Users WHERE ID = "\'.addslashes($id).\'" LIMIT 1\';');
$this->config->user->updateSQL = create_function('$id,$name,$value = null','return \'UPDATE Users SET \'.$name.\' = "\'.addslashes($value).\'" WHERE ID = "\'.addslashes($id).\'" LIMIT 1\';');
$this->config->user->debug = false;
/* Настройки USER */

/* Настройки XML */
$this->config->xml->version = '1.0';
$this->config->xml->encoding = 'UTF-8';
$this->config->xml->rootTag = 'data';
$this->config->xml->filePostfix = '.xsl';
$this->config->xml->fileDir = ROOT_DIR.'tmpl/';
$this->config->xml->debug = false;
/* Настройки XML */

/* Настройки CONTROLLER */
$this->config->controller->controllerDir = ROOT_DIR.'controllers/';
$this->config->controller->defaultController = 'main';
$this->config->controller->controllerError = ROOT_DIR.'controllers/error.php';
$this->config->controller->controllerPostfix = '.php';
$this->config->controller->controllerMethod = 'GET';
$this->config->controller->controllerName = 'controller';
$this->config->controller->parametrsMethod = 'GET';
$this->config->controller->parametrsName = 'parametrs';
$this->config->controller->explodeChar = '/';
/* Настройки CONTROLLER */

/* Настройки CACHE */
$this->config->cache->dir = ROOT_DIR.'cache/';
$this->config->cache->postfix = '.html';
$this->config->cache->time = 900;
$this->config->cache->controllers = array('catalog');
/* Настройки CACHE */

/* Настройки BANNER */
$this->config->banner->viewType = 'html';
/* Настройки BANNER */

/* Настройки STATISTIC */
$this->config->statistic->sID = $this->config->siteId;
/* Настройки STATISTIC */

/* Настройки PROMPTING */
$this->config->prompting->hotLine = true;
$this->config->prompting->workDays = array(1,2,3,4,5);
$this->config->prompting->workDayStart = '00:00';
$this->config->prompting->workDayEnd = '23:59';
/* Настройки PROMPTING */

/* Настройки CATALOG */
$this->config->catalog->sID = $this->config->siteId;
/* Настройки CATALOG */

/* Настройки search */
$this->config->search->sID = 1;//$this->config->siteId;
$this->config->search->sphinxPath = '/sphinx/sphinx.php';
$this->config->search->sphinxServer = '87.242.73.135';
$this->config->search->sphinxPort = 3312;
$this->config->search->index = 'price_index_stroy';
$this->config->search->sortMode = 'lastUpdated DESC, price ASC';
$this->config->search->groupBy = 'IdCompany';
$this->config->search->fieldWeights = array('name' => 40, 'desc' => 1);
$this->config->search->increment = 60;
/* Настройки search */

/* Настройки COMPANY */
$this->config->company->sID = $this->config->siteId;
/* Настройки для облака тегов */
$this->config->company->minFontSize = 1;
$this->config->company->maxFontSize = 3;
$this->config->company->maxWordCount = 30;
/* Настройки для облака тегов */
/* Настройки COMPANY */

/* Настройки GEOIP */
$this->config->geoIP->idDefault = 0;
$this->config->geoIP->nameDefault = 'Москва';
/* Настройки GEOIP */

/* Настройки PRICE */
$this->config->price->maxFileSize = 3 * 1024 * 1024;
$this->config->price->allowTypes = array('xls');
$this->config->price->uploadDir = '...';
$this->config->price->excelPath = '/Excel/';
$this->config->price->sid = 2;
/* Настройки PRICE */

//Режим отладки системы
$this->config->debug = false;

//Запускаем работу TEST
parent::__construct($this->config);


//Всегда используются заголовки
$this->headers->add('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
$this->headers->add('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
$this->headers->add('Cache-Control: no-store, no-cache, must-revalidate');
$this->headers->add('Cache-Control: post-check=0, pre-check=0');
$this->headers->add('Content-type: text/html; charset=UTF-8');
$this->headers->add('Pragma: no-cache');


//Инициализация работы модуля сбора статистики
$this->statistic->init();

//Если был отправлен запрос, на выход из системы пользователем
if(isset($_POST['logout']))
{
//Производим "разлогинивание" пользователя (не важно, был он авторизован или нет)
$this->user->logout();
}

//Включение модуля кеширования html страниц
//$this->cache;

}
}

//Создание системы и её запуск
new stroy_site();
?>


К сожалению много кода, но, для наглядности, когда буду разбирать минусы.

Теперь так — после того, как в клссе stroy_site я напишу $this->cache — у меня инициализируется объект класса кэширования. Или так, напишу $stroy_site->search->search('бла-бла') — автоматически создасться объект класса search (и тем же именем) и отработает метод его search…
Короче — некоторые вещи нужно, чтобы создавались всегда, когда должен отрабатывать скрипт, некоторые — не всегда, а некоторые вещи, особенно довльно громоздкие — вообще редко. Скажем, необходимо использовать класс для работы с эм… графиками, JPGraph, к примеру, а для этого нужно подгрузить не маленькую библиотеку… Вот тогда — удобно. Один раз объявил что, скажем в разделе extensions (извиняюсь за английский) есть модуль graph ($this->config->extenxion->graph = false — смотрите выше на примере core и extensions). Ниже обозначил настройки для этого модуля по умолчанию и все. А когда понадобиться "$this->graph->рисуй()" — как буд-то всегда был создан.

Как все это работает, думаю никакого смысла рассказывать нет. И так все понятно (даже мне).
Минусов также много. Скажем, если для «факторизации» будет больше десятка модулей — то опинание настроек это уже большое количество времени… Так если используются классы не написанные под этот метод — тут нужно смотреть на то, что передаеться объекту при его создании, то придется делать либо какую-то прослойки, либо переписывать конструктор этого класса. Хотя может кому хоть кусочек кода пригодиться…
Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.