Powered by CodeIgniter

Разработка

(21)
17
17 голосов
Разработка новой версии cogear. Прямой эфир с места событий.
Шестеренка — наше всеПродолжаем курс вводных топиков во вторую часть cogear. Следует помнить, что следуя концепции МИСО мы стараемся сделать все просто. В прошлом полугодии ваш покорный слуга сполна вкусил системного анализа, поэтому и здесь мы структурируем и определяем взаимосвязи, а после целое делим на элементы и приводим их к общему знаменателю. Ни для кого не секрет, что в первой версии мы ввели свое обозначение компонента системы — шестеренка. Почему? Потому что мы называем CMS движком, а как CMS состоит из компонентов, так и внутри движка трудятся шестеренки, обеспечивая его работоспособность. Во второй части мы продолжаем развивать эту философскую линию. Мы убираем .info файлы, как характеризующие шестеренку, и ставим на их место классы на PHP. Настройки переезжают в базу/кеш (пока, непонятки при обновлении!), вся структура определяется внутри класса. Кроме того, теперь разработчик сам волен распоряжаться структурой свой шестеренки, ведь любые ограничение на файлы и папки сняты.
Мы помним про файловую структуру и про автозагрузку. Как создать новую шестеренку? Разместить в папке gears своего сайта подпапку, внутри которой будет лежать класс, отнаследованный от базового Gear.

Создаем шестеренку

Смотрите, вы решили написать фотогалерею. Назовем ее, а каждая шестеренка имеет базовое машинное имя, к примеру, Gallery. Разрабатываете вы на локальном домене localhost, поэтому заходим в папку с шестеренками сайта /sites/localhost/gears/ и создаем в ней подпапку Gallery(внимание, названия шестеренок пишутся с Большой Буквы!), а в ней разместим файл Gear.php. Вспоминаем про автозагрузку и пишем код класса.
<?php /** * Gallery gear * * @author Dmitriy Belyaev <admin@cogear.ru> * @copyright Copyright © 2011, Dmitriy Belyaev * @license http://cogear.ru/license.html * @link http://cogear.ru * @package Core * @subpackage * @version $Id$ */ class Gallery_Gear extends Gear{ protected $name = 'Gallery gear!'; protected $type = Gear::PLUGIN; protected $author = 'Dmitriy Belyaev'; } Шестеренка готова и теперь будет обнаруживаться в системе. Обратите внимание на название класса, помните что оно привязано к его месторасположению в файловой системе.
Указан необходимый минимум параметров, переопределяющие значение таковых родительского класса.
По-умолчанию, во время инициализации шестеренки класс Assets, который ответственен за вывод скриптов и стилей в шаблон, собирает скрипты и стили из подпапкок js и css, если таковые существуют. Это сделано для удобства — бросили файл в папку, и он уже используется при выводе сайта. Конечно, когда вы перейдете из режим разработки в рабочий режим, система будет брать информацию из кеша, вместо того, чтобы шерстить по папкам в поисках скриптов и стилей.
Как отключить данную фичу? Просто.
Обратимся к коду файла базового класса шестеренки /gears/Core/Gear.php:
/** * Initialization */ public function init(){ $cogear = getInstance(); $scripts = $this->dir.DS.'js'; $styles = $this->dir.DS.'css'; $forms = $this->dir.DS.'forms'; is_dir($scripts) && $cogear->assets->addScriptsFolder($scripts); is_dir($styles) && $cogear->assets->addStylesFolder($styles); $cogear->hooks->invoke('gear.init',$this); } Это и есть функция инициализации.
Давайте уберем парсинг директорий и вызов хуков для нашего конкретного случая.
Фрагмент листинга /sites/localhost/gears/Gallery/Gear.php:
public function init(){ } Мы просто переопределили родительский метод инициализации. Как же вернуть все обратно, да еще и фукнционала добавить? Будем парсить на предмет скриптов папка super-js, также находящуюся в корне шестеренки:
public function init(){ parent::init(); // Вот вот — вызываем метод инициализации родителя $cogear =& getInstance(); // Зовем управляющего на помощь $cogear->assets->addScriptsFolder($this->dir.DS.'super-js'); } Готово! Обратите внимание, что класс шестеренки, в отличие от привычных контроллеров и моделей в CodeIgniter не наследуется от базового класса. Таким образом мы уходим от бесконечной рекурсии.
Вместо
$this->db->… теперь пишем
$cogear =& getInstance(); $cogear->db->… Кода несколько больше, но работает в любом месте любого файла, а не то, что раньше. Упростили, свели к одному.

Базовый класс шестеренки

Для любопытных, кто хочет узнать, какие свойства и методы базового класса шестеренки вы можете переопределять, привожу полный листинг /gears/Core/Gear.php:
<?php /** * Gear class * * * @author Dmitriy Belyaev <admin@cogear.ru> * @copyright Copyright © 2011, Dmitriy Belyaev * @license http://cogear.ru/license.html * @link http://cogear.ru * @package Core * @subpackage Gear * @version $Id$ */ abstract class Gear extends Cogear implements Interface_Gear { /** * Gear name * @var string */ protected $name; /** * Gear description * @var string */ protected $description; /** * Gear version * @var string */ protected $version = '1.0'; /** * Gear type * * 0 Core * 1 Module * 2 Plugin * 3 Theme * * @var int */ protected $type = Gear::MODULE; /** * Gear authors name * * @var string */ protected $author = 'Dmitriy Belyaev'; /** * Gear email * * Contact email to resolve everything * @var string */ protected $email = 'admin@cogear.ru'; /** * Path to class file * * @var string */ protected $path; /** * Directory where gear is located * @var string */ protected $dir; /** * Relative path to folder with class * * @var string */ protected $folder; /** * Order in gear stack * * Value can be positive or negative to load after or before other gears. * * @var int */ protected $order = 0; /** * Class reflection * * Metaclass that stores all the info about current class * * @var ReflectionClass */ protected $reflection; /** * Gear name * * How does it stored in database and in Loader->gears->$name * * @param string */ protected $gear_name; /** * Required gears [version is optoinal] * * array( * 'Phpinfo', * // or with version * 'Phpinfo 1.1', * // or even with condition * 'Phpinfo > 1.1', * ) * @var array */ protected $required = array(); /** * Constants */ const CORE = 0; const MODULE = 1; const PLUGIN = 2; const THEME = 3; /** * Constructor */ public function __construct(){ $this->reflection = new ReflectionClass($this); $this->getPath(); $this->getDir(); $this->getFolder(); $this->getGearName(); } /** * Initialization */ public function init(){ $cogear = getInstance(); $scripts = $this->dir.DS.'js'; $styles = $this->dir.DS.'css'; $forms = $this->dir.DS.'forms'; is_dir($scripts) && $cogear->assets->addScriptsFolder($scripts); is_dir($styles) && $cogear->assets->addStylesFolder($styles); $cogear->hooks->invoke('gear.init',$this); } /** * Activation */ public function activate(){ $cogear = getInstance(); $cogear->loader->markGear($this->gear_name,'active'); } /** * Deactivation */ public function deactivate(){ $cogear = getInstance(); $cogear->loader->markGear($this->gear_name,'inactive'); } /** * Installation */ public function install(){ $cogear = getInstance(); $cogear->loader->markGear($this->gear_name,'installed'); } /** * Deinstallation */ public function deinstall(){ $cogear = getInstance(); $cogear->loader->markGear($this->gear_name,'uninstalled'); } /** * Gear info * * @param string $var * @return array */ public function info($var = NULL){ if($var){ return isset($this->$var) ? $this->$var : NULL; } else return array( 'name' => $this->name, 'gear_name' => $this->gear_name, 'description' => $this->description, 'version' => $this->version, 'package' => $this->package, 'type' => $this->type, 'author' => $this->author, 'email' => $this->email, 'url' => $this->url, 'path' => $this->path, 'dir' => $this->dir, 'folder' => $this->folder, ); } /** * Get Gear Path * * @return string */ protected function getPath(){ return $this->path = $this->reflection->getFileName(); } /** * Get Gear directory * * @return string */ protected function getDir(){ if(!$this->path) $this->getPath(); return $this->dir = dirname($this->path); } /** * Get Gear relative folder * * @return string */ protected function getFolder(){ if(!$this->dir) $this->getDir(); $this->folder = str_replace(ROOT,'',$this->dir); return self::normalizePath($this->dir); } /** * Get Gear name * * @return string */ protected function getGearName(){ $this->gear_name = Loader::prepareGearNameFromClass($this->reflection->getName()); } /** * Normalize relative path * * For example, under windows it look like \cogear\Theme\Default\, but wee need good uri to load css, js or anything else. * It transorm that path to /cogear/Theme/Default/. * @param string $path * @return string */ public static function normalizePath($path){ $path = str_replace(DS,'/',$path); return $path; } /** * Get gear name by path * * @param string $path * @return string|boolean Gear name or FALSE if path is not correct. */ public static function getGearNameFromPath($path){ foreach(array(SITE.DS.GEARS_DIR,SITES_ALL.DS.GEARS_DIR,CORE) as $dir){ if(strpos($path,$dir) !== FALSE){ is_file($path) && $path = dirname($path); $path = str_replace($dir,'',$path); $path = trim($path,DS); $pieces = explode(DS,$path); $gear_folder = ''; foreach($pieces as $piece){ $gear_folder .= $piece.DS; $gear_name = str_replace(DS,'_',trim($gear_folder,DS)); $gear_class = $gear_name.'_Gear'; if(file_exists($dir.DS.$gear_folder.DS.'Gear'.EXT) && class_exists($gear_class)){ return $gear_name; } } } } return FALSE; } /** * Prepare path * * Errors.index = Core/Errors/$dir/index * * @param string $name * @param string $dir * @param string $default * @return string Path without file extension */ public static function preparePath($name,$dir = '',$default = 'index') { if ($pieces = preg_split('#[\s><.-]#', $name, -1, PREG_SPLIT_NO_EMPTY)) { if (sizeof($pieces) == 1) { array_push($pieces, $default); } $gear = ucfirst(array_shift($pieces)); $cogear = getInstance(); if (isset($cogear->loader->gears->$gear)) { $gear_dir = $cogear->loader->gears->$gear->info('dir'); $file_name = implode(DS, $pieces); return $path = $gear_dir . DS . $dir. DS . $file_name; } } return NULL; } } Помните, что шестеренки грузятся в порядке, определенном значением свойства $order каждой из них. Хотите подгрузить стили и скрипты раньше какой-нибудь базовой шестерни или же получить успеть прохукать вывод раньше остальных? Просто поменяйте порядок загрузки. Значение свойства может быть любым, даже отрицательным или дробным.

Темы

В стремлении к простоте мы идем еще дальше и приравниваем тему к шестеренке. Неожиданно? Да! В большинстве движков темы чаще всего живут обособленно, у нас же они являются точно такой же базовой сущностью.
Класс темы точно также наследуется от класса шестеренки с небольшими изменениями. Да, что и говорить, смотрите сами! Листинг файла /gear/Core/Theme.php:
<?php /** * Theme * * @author Dmitriy Belyaev <admin@cogear.ru> * @copyright Copyright © 2011, Dmitriy Belyaev * @license http://cogear.ru/license.html * @link http://cogear.ru * @package Core * @subpackage * @version $Id$ */ abstract class Theme extends Gear { protected $name = 'Theme'; protected $description = 'Theme for cogear.'; protected $type = Gear::THEME; protected $version = '1.0'; protected $author = 'Dmitriy Belyaev'; protected $email = 'admin@cogear.ru'; protected $url = 'http://cogear.ru'; protected $order = 0; public $template; /** * Init */ public function init(){ parent::init(); $cogear = getInstance(); $cogear->theme =& $this; $cogear->hooks->register('output.render',__CLASS__.'->render'); $this->template = new Template($this->gear_name.'.index'); } /** * Render theme */ public function render(){ $cogear = getInstance(); $cogear->output->append('page',$cogear->theme->template->compile()); } } Чувствуете? Просто, очень просто. Она точно также подключит свои стили и скрипты, как любая шестеренка. От базовой шестеренки ее отличает лишь тип (мы же хотим работать с темами отдельно в админке? :-) и шаблон, который будет главным для темы.

Тема по-умолчанию

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

Темы по-умолчанию расположены в ядре, в папке /gears/Theme/. Обратим внимание на структуру темы Default.

Структура темы по-умолчанию


Все точно также, как и у шестеренки, только добавлена папка templates с шаблоном index.tpl.
Листинг шаблона /gears/Core/Theme/Default/templates/index.tpl:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>{$title}</title> {$styles} {$header} </head> <body> {$content} {$footer} {$scripts} </body> </html> Вы понимаете, что дизайн может быть любым. Располагаете скрипты и стили темы, и она уже готова к работе.

Заключение

Наконец-то мы пришли к унифицированному элементу. Странно, что нигде этого не было раньше, потому как, например, в том же Друпале все виды сущностей подставлены нодой. Концепция та же, только на другом уровне.
В следующем уроке вы узнаете про наследование шестеренок.
Приятных выходных!
22:35 ← 28 января 2011 Отправить в Твиттер adminadmin  RSS comments 15

Комментарии (15) ↓

Isildien Isildien time 22:55 ← 28 января 2011 #
Первый, спасибо за интересную статью :)
Автор
admin admin time 23:04 ← 28 января 2011 #
Пожалуйста! Осилил, на ночь глядя!
inetlover inetlover time 23:10 ← 28 января 2011 #
Гениально!

Дима, а когда уже можно будет попробовать Cogear2?
Isildien Isildien time 23:12 ← 28 января 2011 #
Бета будет через две-три недели :)
inetlover inetlover time 23:23 ← 28 января 2011 #
Значит Альфы не будет? :-)
Автор
admin admin time 23:12 ← 28 января 2011 #
Когда можно будет — сразу скажу. Я стараюсь, но не могу сидеть у монитора 24/7.
inetlover inetlover time 23:23 ← 28 января 2011 #
Я никого не подгоняю, просто последние дни очень много публикуешь постов — похоже на финишную прямую.
Автор
admin admin time 23:29 ← 28 января 2011 #
Я пишу о том, что есть, чтобы вы выражали свое мнение и, возможно, вносили коррективы в курс.
Работы еще более чем.
Ramir Ramir time 00:53 ← 29 января 2011 #
Полагаю, что в целом видишь только ты. Поэтому мало кто может подсказать тебе то, что ты хочешь. Т.к. ты уже давно в этом япона-супе варишься.
Вот меня лично интересуют такие вопросы — какой функционал будет у беты, можно ли будет ее сразу в дело пускать (какая у нее багонасыщеность) и когда?

Потому что сейчас приходиться править Когер1 под себя, а от мысли, что через месяц делать тоже самое для Когера2, становиться грустно ((

Я бы сейчас уже поставил бы локально альфа-версию и начал настраивать под себя, паралельно отлавливая баги.
Автор
admin admin time 01:14 ← 29 января 2011 #
Пока еще рано об этом говорить. Ориентировочно — такой же функционал, как у первой.
den den time 13:04 ← 29 января 2011 #
Ну вот, хорошо что можно переопределять подключение стилей, это реально мешало. Хотя я, увы, не очень в этом разбираюсь. Этот «образовательный» курс статей очень нужен и подробнее!))
JiLiZART JiLiZART time 21:35 ← 30 января 2011 #
Очень занимательно про темы особенно, но как будут обстоять дела с переопределением шаблонов шестерёнок, если Тема это тожэ шестерёнка, получаем: Если мы поставим order одной из шестерёнок до небес, то тема не сможет переопределить шаблон? Как обстоят дела с настройками темы, к примеру сделать изменяемую ширину и задний фон ( или это тоже хуком делать будем? )?
Ещё думаю не менее важный вопрос, будет ли поддержка noSQL бд, аля Redis, т.к это направление сейчас довольно популярно.
Автор
admin admin time 00:28 ← 31 января 2011 #
Мы изначально работаем с Zend_Table, которая поддерживает множество различных БД, но кроме того, можно и сам $cogear->db переопределить хуками.
Isildien Isildien time 22:46 ← 30 января 2011 #
Ждем новых статей…
Автор
admin admin time 00:30 ← 31 января 2011 #
Хорошо, завтра будет новая :-) По выходным я занимаюсь музыкой и видеосъемкой обычно. Чтобы не сойти с ума от программирования, надо разнообразить досуг и виды деятельности.