Продолжаем курс вводных топиков во вторую часть cogear. Следует помнить, что следуя концепции МИСО мы стараемся сделать все просто. В прошлом полугодии ваш покорный слуга сполна вкусил системного анализа, поэтому и здесь мы структурируем и определяем взаимосвязи, а после целое делим на элементы и приводим их к общему знаменателю. Ни для кого не секрет, что в первой версии мы ввели свое обозначение компонента системы — шестеренка. Почему? Потому что мы называем CMS движком, а как CMS состоит из компонентов, так и внутри движка трудятся шестеренки, обеспечивая его работоспособность. Во второй части мы продолжаем развивать эту философскую линию. Мы убираем .info файлы, как характеризующие шестеренку, и ставим на их место классы на PHP. Настройки переезжают в базу/кеш (пока, непонятки при обновлении!), вся структура определяется внутри класса. Кроме того, теперь разработчик сам волен распоряжаться структурой свой шестеренки, ведь любые ограничение на файлы и папки сняты.Создаем шестеренку
Смотрите, вы решили написать фотогалерею. Назовем ее, а каждая шестеренка имеет базовое машинное имя, к примеру, 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>
Вы понимаете, что дизайн может быть любым. Располагаете скрипты и стили темы, и она уже готова к работе.Заключение
Наконец-то мы пришли к унифицированному элементу. Странно, что нигде этого не было раньше, потому как, например, в том же Друпале все виды сущностей подставлены нодой. Концепция та же, только на другом уровне.В следующем уроке вы узнаете про наследование шестеренок.
Приятных выходных!


Дима, а когда уже можно будет попробовать Cogear2?
Работы еще более чем.
Вот меня лично интересуют такие вопросы — какой функционал будет у беты, можно ли будет ее сразу в дело пускать (какая у нее багонасыщеность) и когда?
Потому что сейчас приходиться править Когер1 под себя, а от мысли, что через месяц делать тоже самое для Когера2, становиться грустно ((
Я бы сейчас уже поставил бы локально альфа-версию и начал настраивать под себя, паралельно отлавливая баги.
Ещё думаю не менее важный вопрос, будет ли поддержка noSQL бд, аля Redis, т.к это направление сейчас довольно популярно.