Pull to refresh

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

Пишу в гугл переводчике «завод» — выдает 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->рисуй()" — как буд-то всегда был создан.

Как все это работает, думаю никакого смысла рассказывать нет. И так все понятно (даже мне).
Минусов также много. Скажем, если для «факторизации» будет больше десятка модулей — то опинание настроек это уже большое количество времени… Так если используются классы не написанные под этот метод — тут нужно смотреть на то, что передаеться объекту при его создании, то придется делать либо какую-то прослойки, либо переписывать конструктор этого класса. Хотя может кому хоть кусочек кода пригодиться…
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.