Каркас для web-приложений, построенный на CodeIgniter
Наверняка, многие веб-программисты изучали и, может быть, даже использовали такой замечательный фреймворк как CodeIgniter. Мой выбор пал на него ввиду того, что у него самый низкий порог вхождения, он наиболее прост в изучении, хорошая документация, быстрый и т.д. и т.п. Для простых проектов самое «оно», чтоб попробовать свои силы именно как разработчик. Само собой, для более серьезных проектов лучше использовать более функциональные и навороченные фреймворки.
Далее буду описывать, как я «апгрейдил» CodeIgniter, чтобы использовать этот каркас для разных проектов, т.к. базовый его функционал и примеры из документации, мягко говоря, очень простые, а в жизни всё гораздо сложнее. Итак, начнем-с.
Перед прочтением очень рекомендуется ознакомиться с официальной документацией по CodeIgniter, т.к. в статье предполагается, что вы хотя бы прочитали основные темы и тему «Класс Template Parser» и выполнили эти примеры.
Первое, что очень неудобно в базовой конфигурации – это разделение контроллеров, моделей и отображений по разным папкам без возможности объединения какого-то модуля в одну папку. Т.е. если вы хотите написать, к примеру, модуль “News”, выводящий новости, ваш модуль расползется по трем разным папкам controllers, models, views. И вскоре будет непонятно, какой контроллер относится к какой модели и к каким «вьюшкам». Это хорошо, если у вас один такой модуль. А если их больше 10, то контролировать это становится очень тяжело.

Да, можно внутри папок (controllers, models, views) создавать подпапки (например, news, menu, comments) и кидать туда наши контроллеры, модели и вьюшки, как сказано в документации, но, мне кажется, это всё равно неудобно.
Гораздо удобнее было бы, если бы у нас была папка modules, а в ней мы создавали наш модуль News, т.е. папку, а внутри нее уже 3 папки (контр-ы, модели, вьюхи). Данный функционал нам предоставляет расширение HMVC для CodeIgniter. Скачать и прочитать инструкцию по установке можно по этой ссылке.
Это расширение позволит нам внутри каждого модуля располагать свои модели, контроллеры, вьюшки, относящиеся только к этому модулю. Также можно внутри этих модулей создавать подпапки внутри папок моделей, контр-ов или вьюшек, что очень удобно для больших модулей, имеющих много файлов.

Более того, это позволяет нам загружать несколько контроллеров или моделей с разных модулей и строить удобную нам структуру (или запускать из одной модели контроллер другого модуля и т.п.).
В этом я, конечно, ничего нового не открыл. Поэтому идем дальше. В базовом CI-е URL разбирается следующим образом: example.com/class/function/ID
Т.е. первый сегмент – это вызываемый контроллер (класс), второй – функция в нем, третий – параметр, передаваемый в функцию (может быть и четвертый и пятый). Честно говоря, даже для средненького проекта это очень неудобно, поэтому я решил выстроить свою логику обработки URL, что дает мне полную свободу действий. Для этого редактируем файл routes.php в папке application/config и прописываем в него:
$route['default_controller'] = "main"; $route[':any'] = "main";
Из этого видно, что в любом случае будет загружаться контроллер main и запускаться функция index(). Далее создаем файл в папке application/controllers и называем его «main.php». Не забываем, что мы установили расширение HMVC, поэтому наши контроллеры теперь будут наследоваться не от CI_Controller, а от MX_Controller. Этот контроллер будет главным, и через него будет «проходить» всё. В то же время он будет очень простым и будет просто передавать управление в другие модули. Так выглядит функция index() у меня:
function index() { session_start(); // сессии я использую, хотя базовый CI нет $this->check_lang(); // проверяет язык из урла $this->check_module(); // проверяет модуль из урла $this->tp->load_tpl($this->tp->tpl); // загружает шаблон и проверяет на модули $this->tp->print_page(); // выводит шаблон с проработанными модулями на экран }
Последние две строчки из класса «tp» пока что трогать не будем. Базовый CI не использует сессии PHP, а вместо них сохраняет данные в Cookies (ввергая новичков в заблуждение, называя свою библиотеку Session, хотя она работает с Cookies). Я всё же решил использовать родные сессии PHP, хотя и не отказался полностью от функционала, предлагаемого CI для работы с Cookies (использую и то, и то).
Итак, первое, что я делаю, проверяю на язык (проекты у меня чаще мультиязычные).
function check_lang() { if ($s=$this->uri->segment(1)) { switch ($s) { case 'ru': define('LANG','ru'); break; case 'en': define('LANG','en'); $this->config->set_item('language', 'english'); break; default: show_404('page'); } } else { define('LANG','ru'); } }
Видно, что первый сегмент в URL будет отвечать за язык. В файле application/config/config.php укажите:
$config['language'] = 'russian';
Чтоб изменить язык конфигурации из контроллера, используйте вот это:
$this->config->set_item('language', 'english');
Если вы не используете мультиязычность, просто пропустите это.
Функция check_module() должна проверить второй сегмент УРЛа (или первый, если вы не используете мультиязычность) на то, является ли он допустимым модулем, т.е. я заранее в конструкторе прописываю разрешенные модули, например, так:
function __construct() { $this->modules=array('auth','cabinet','ads','root'); // разрешенные модули }
Затем в функции проверяю:
function check_module() { if ($m=$this->uri->segment(2)) { if (in_array($m,$this->modules)) { $this->common->load_module($m); $this->tp->tpl=$this->$m->tpl; } else { show_404('page'); } } else { $this->load_main_page(); // Если нет второго сегмента, то загружает главную страницу } }
Таким образом, если второй сегмент пустой, то грузится главная страница. Если в URL допустимый модуль, то грузится он, если нет, то выкидывает на 404. Далее я создаю 2 модели tp.php и common.php в папке application/models, которые будут доступны у меня везде (прописываю их в автозагрузчике application/config/autoload.php):
$autoload['model'] = array('tp','common');
В «tp» будут описаны функции для работы моего простенького шаблонизатора, расширяющего возможности очень слабенького родного. В «common» пишу все остальные функции, которые будут часто использоваться.
Таким образом, $this->common->load_module($m) будет загружать модуль $m (второй сегмент из URL) и функцию index() в нем. Здесь всё просто:
function load_module($module) { if (is_dir('application/modules/'.$module)) // проверяет, существует ли модуль { $this->load->module($module); $this->$module->index(); } }
Каждый модуль, загружаемый из URL должен использовать какой-нибудь шаблон всей страницы, например, такой:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>{page_title}</title> <link rel="stylesheet" type="text/css" href="{SITEURL}/css/main.css"> </head> <body> <div class="page"> {HEADER} <div class="content"> <div class="page_title"> {title_of_page} </div> <div class="needwidth"> <div class="rightside"> {LOOKED_ADS} </div> <div class="sam_kontent"> {MSG} {CONTENT} </div> <div class="clear"></div> </div> </div> {FOOTER} </div> </body> </html>
Основные шаблоны всей страницы создаются в папке application/views/templates (папка templates создается вручную). Разные модули могут использовать одни и те же шаблоны.
Сам каркас контроллера модуля выглядит так:
class News extends MX_Controller { private $mname; public $tpl; function __construct() { $this->tpl='p_default.tpl'; // шаблон страницы $this->mname=strtolower(get_class());// имя модуля } public function index() { // здесь выполняем всякие действия, грузим модели и т.д. $this->tp->parse('CONTENT', $this->mname.'/'.$this->mname.'.tpl'); } }
В нашем модуле News создаем 3 папки – controllers, models, views. В папке controllers создаем файл news.php и записываем в него код, описанный выше. В функции index() выстраиваем свою логику. Я, к примеру, гружу там модель news_model.php, находящуюся в папке models модуля news. Уже в модели описываю функции для работы с базой или другие сложные функции, связанные с этим модулем.
В конце концов, весь результат, полученный из модуля news, записывается в метку CONTENT, которая заменяется в шаблоне на этот результат. Чтоб понять, каким образом это происходит, необходимо рассказать, как я построил логику моего «шаблонизатора».
Я лишь расширил возможности базового «Парсера» и привел в «человеческий вид». Если вы не знаете, как работает базовый, вначале лучше разберитесь с этим.
Итак, рассмотрим модель «tp».
Здесь есть 2 public переменные — $D, $tpl. $D – это наш глобальный массив, который в конце концов заменит в шаблоне все метки вида {LABEL} на содержимое $this->D[‘LABEL’], проработанное в модулях. $tpl – это основной главный шаблон всей страницы, который прописывается в каждом модуле из УРЛа и передается затем в главный контроллер main, где вызывается $this->tp->load_tpl($this->tp->tpl).
Выше мы уже видели функцию parse(). В эту функцию обязательно должны передаваться 2 параметра, первый – это метка, в которую будет сохранен результат (кусок html), второй – этот самый кусок html, находящийся в папке views. Но parse() проверит этот html на наличие в нем меток и проработает их, в случае необходимости:
function parse($label, $tpl) { $TPL=$this->load->view($tpl, FALSE, TRUE); $pattern = '/{[A-Za-z0-9_]+}/'; // метки могут быть лишь такими preg_match_all($pattern, $TPL, $MODULES); // находит метки в шаблоне foreach ($MODULES[0] as $MODULE) { $module=substr($MODULE,1,-1); if (!isset($this->D[$module])) $this->D[$module]=$this->lang->line($module); // если они не определены, то смотрит в langs } if (isset($this->D[$label])) { $this->D[$label].=$this->parser->parse($tpl, $this->D, TRUE); } else { $this->D[$label]=$this->parser->parse($tpl, $this->D, TRUE); } }
Из этого всего должно быть понятно, что если создать внутри модуля news в папке views файл news.tpl и написать туда простейший html, например:
<h1>Мой первый модуль!</h1>
Затем запустить example.com/ru/news, то запуститься главный контроллер main.php, который передаст управление в модуль news, там загрузится шаблон p_default.tpl (из кода выше).
Затем контроллер модуля news заменит {CONTENT} на содержания файла application/modules/news/views/news.tpl и выведет содержимое шаблона p_default.tpl на экран.
Но… это пока что теоретически, ведь мы не описали функции $this->tp->load_tpl($this->tp->tpl) и $this->tp->print_page().
Функция load_tpl() принимает в качестве параметра шаблон, который является главным, т.е. шаблоном всей страницы (в папке views/templates). Затем этот шаблон проверяется на другие метки, которые могут быть либо модулями, либо просто переменными (как например, заголовок или копирайт). Метки в верхнем регистре и с числами – это модули, в нижнем – простые переменные. Если замена меткам не найдена, то они просто затираются (удаляются). Вот сам код:
function load_tpl($tpl_name) { $TPL=$this->load->view('templates/'.$tpl_name, FALSE, TRUE); $pattern = '/{[A-Z0-9_]+}/'; $pattern2 = '/{[a-z_]+}/'; preg_match_all($pattern, $TPL, $MODULES); // находит модули preg_match_all($pattern2, $TPL, $VALUES); // находит переменные foreach ($MODULES[0] as $MODULE) { $module=substr($MODULE,1,-1); if (!isset($this->D[$module])) { $this->D[$module]=''; $this->common->load_module(strtolower($module)); } } foreach ($VALUES[0] as $VALUE) { $value=substr($VALUE,1,-1); if (!isset($this->D[$value])) { $this->D[$value]=''; } } $this->D['TPL']=$tpl_name; }
В конце нашего главного контроллера main выполняется функция print_page(), которая должна вывести проработанный шаблон на экран:
function print_page() { $this->parser->parse('templates/'.$this->D['TPL'], $this->D); }
Все выше описанное я старался как можно больше упростить. На самом деле, у меня все гораздо сложней и расширенней, но это вы сможете сделать сами (т.к. и это, возможно, не все дочитали до конца). В моделе «tp» у меня еще куча всяких функций для шаблонизатора, например:
function clear($label) { $this->D[$label]=''; } function kill($label) { unset($this->D[$label]); } function assign($label, $value='') { if (is_array($label)) { foreach ($label as $l=>$v) { $this->D[$l]=$v; } } else $this->D[$label]=$value; }
Понять их логику несложно. С помощью $this->tp->assign(‘page_title’, ’Главная страница’), например, можно просто заменить {page_title} на «Главная страница» в шаблоне.
Внутри шаблонов также могут быть модули, которые могут что-то выводить, например, последние новости или меню. В шаблоне они вставляются внутри фигурных скобок, например, {MENU} или {HEADER}. Функция parse(), встретив такую метку, проверит, является ли эта метка модулем, и если да, то заменит ее на то, что выдаст этот модуль. Такие модули также располагаются внутри папки modules и имеют свою M-V-C.
Главный контроллер модуля header, например, выглядит так:
class Header extends MX_Controller { public $mname, $tag; function __construct() { $this->mname=strtolower(get_class());// имя модуля $this->tag=strtoupper($this->mname); // «Тэг» в шаблоне } public function index() { $this->load->model($this->mname.'/'.$this->mname.'_model'); $model=$this->mname.'_model'; $this->$model->index($this->mname); $this->tp->parse($this->tag, $this->mname.'/'.$this->mname.'.tpl'); } }
Последняя функция $this->tp->parse($this->tag, $this->mname.'/'.$this->mname.'.tpl') заменит {HEADER} на содержание файла modules/header/views/header.tpl
Следует отметить, что всегда первым проработает модуль, который загружается из URL и является главным, затем подгружается шаблон и parse() прорабатывает все модули внутри него. Для наглядности всё, описанное выше, изобразил на картинке:

Прошу простить за примитивную графику, я всё же программист, а не дизайнер.
Если лень писать всё это вручную, можете скачать CodeIgniter с моими небольшими доработками и поковыряться там (ссылка внизу). Я специально вырезал оттуда почти все свои функции, модули, модели и всё остальное, ограничившись лишь описанным в статье, дабы вы попробовали расширить функционал сами и не отвлекались на всё остальное.
Конечно, можно было еще много чего дописать, но статья итак получилась длинная.
Буду рад услышать критику профессионалов.
Мой доработанный CodeIgniter можно скачать: https://github.com/IbrahimKZ/codeigniter
Written by: Никишин Ибрахим aka IbrahimKZ
skype: n-ibrahim
- Войдите или зарегистрируйтесь, чтобы получить возможность отправлять комментарии
- 1296 просмотров



Комментарии
3 комментария(ев)Дата: Втр, 22/11/2011 - 19:40
Отличный материал.
Нравится мне гибкость Сodeigniter'а. Можно всё самому допилить без больших усилий. В этом плюс CMF. Не то, что, например, Drupal. Код ядра откроешь, конкретно офигеешь и сразу закроешь 
Дата: СР, 23/11/2011 - 05:22
Это точно! Тоже самое могу сказать и про WordPress
Дата: Втр, 22/11/2011 - 21:23
а я вообще ими не пользуюсь. бггг страшновато как то. с нуля пишу)))