Каркас для 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 и прописываем в него:
Из этого видно, что в любом случае будет загружаться контроллер main и запускаться функция index(). Далее создаем файл в папке application/controllers и называем его «main.php». Не забываем, что мы установили расширение HMVC, поэтому наши контроллеры теперь будут наследоваться не от CI_Controller, а от MX_Controller. Этот контроллер будет главным, и через него будет «проходить» всё. В то же время он будет очень простым и будет просто передавать управление в другие модули. Так выглядит функция index() у меня:
Последние две строчки из класса «tp» пока что трогать не будем. Базовый CI не использует сессии PHP, а вместо них сохраняет данные в Cookies (ввергая новичков в заблуждение, называя свою библиотеку Session, хотя она работает с Cookies). Я всё же решил использовать родные сессии PHP, хотя и не отказался полностью от функционала, предлагаемого CI для работы с Cookies (использую и то, и то).
Итак, первое, что я делаю, проверяю на язык (проекты у меня чаще мультиязычные).
Видно, что первый сегмент в URL будет отвечать за язык. В файле application/config/config.php укажите:
Чтоб изменить язык конфигурации из контроллера, используйте вот это
Если вы не используете мультиязычность, просто пропустите это.
Функция check_module() должна проверить второй сегмент УРЛа (или первый, если вы не используете мультиязычность) на то, является ли он допустимым модулем, т.е. я заранее в конструкторе прописываю разрешенные модули, например, так:
Затем в функции проверяю:
Таким образом, если второй сегмент пустой, то грузится главная страница. Если в URL допустимый модуль, то грузится он, если нет, то выкидывает на 404. Далее я создаю 2 модели tp.php и common.php в папке application/models, которые будут доступны у меня везде (прописываю их в автозагрузчике application/config/autoload.php):
В «tp» будут описаны функции для работы моего простенького шаблонизатора, расширяющего возможности очень слабенького родного. В «common» пишу все остальные функции, которые будут часто использоваться. Таким образом, $this->common->load_module($m) будет загружать модуль $m (второй сегмент из URL) и функцию index() в нем. Здесь всё просто:
Каждый модуль, загружаемый из URL должен использовать какой-нибудь шаблон всей страницы, например, такой
Основные шаблоны всей страницы создаются в папке application/views/templates (папка templates создается вручную). Разные модули могут использовать одни и те же шаблоны. Сам каркас контроллера модуля выглядит так:
В нашем модуле News создаем 3 папки – controllers, models, views. В папке controllers создаем файл news.php и записываем в него код, описанный выше. В функции index() выстраиваем свою логику. Я, к примеру, гружу там модель news_model.php, находящуюся в папке models модуля news. Уже в модели описываю функции для работы с базой или другие сложные функции, связанные с этим модулем.
В конце концов, весь результат, полученный из модуля news, записывается в метку CONTENT, которая заменяется в шаблоне на этот результат. Чтоб понять, каким образом это происходит, необходимо рассказать, как я построил логику моего «шаблонизатора». Я лишь расширил возможности базового «Парсера» и привел в «человеческий вид». Если вы не знаете, как работает базовый, вначале лучше разберитесь с этим.
Итак, рассмотрим модель «tp».
Здесь есть 2 public переменные — $D, $tpl. $D – это наш глобальный массив, который в конце концов заменит в шаблоне все метки вида на содержимое $this->D[‘LABEL’], проработанное в модулях. $tpl – это основной главный шаблон всей страницы, который прописывается в каждом модуле из УРЛа и передается затем в главный контроллер main, где вызывается $this->tp->load_tpl($this->tp->tpl).
Выше мы уже видели функцию parse(). В эту функцию обязательно должны передаваться 2 параметра, первый – это метка, в которую будет сохранен результат (кусок html), второй – этот самый кусок html, находящийся в папке views. Но parse() проверит этот html на наличие в нем меток и проработает их, в случае необходимости:
Из этого всего должно быть понятно, что если создать внутри модуля news в папке views файл news.tpl и написать туда простейший html, например
Затем запустить example.com/ru/news, то запуститься главный контроллер main.php, который передаст управление в модуль news, там загрузится шаблон p_default.tpl (из кода выше).
Затем контроллер модуля news заменит на содержания файла application/modules/news/views/news.tpl и выведет содержимое шаблона p_default.tpl на экран. Но… это пока что теоретически, ведь мы не описали функции $this->tp->load_tpl($this->tp->tpl) и $this->tp->print_page().
Функция load_tpl() принимает в качестве параметра шаблон, который является главным, т.е. шаблоном всей страницы (в папке views/templates). Затем этот шаблон проверяется на другие метки, которые могут быть либо модулями, либо просто переменными (как например, заголовок или копирайт). Метки в верхнем регистре и с числами – это модули, в нижнем – простые переменные. Если замена меткам не найдена, то они просто затираются (удаляются). Вот сам код:
В конце нашего главного контроллера main выполняется функция print_page(), которая должна вывести проработанный шаблон на экран:
Все выше описанное я старался как можно больше упростить. На самом деле, у меня все гораздо сложней и расширенней, но это вы сможете сделать сами (т.к. и это, возможно, не все дочитали до конца). В моделе «tp» у меня еще куча всяких функций для шаблонизатора, например:
Понять их логику несложно. С помощью $this->tp->assign(‘page_title’, ’Главная страница’), например, можно просто заменить на «Главная страница» в шаблоне.
Внутри шаблонов также могут быть модули, которые могут что-то выводить, например, последние новости или меню. В шаблоне они вставляются внутри фигурных скобок, например, или . Функция parse(), встретив такую метку, проверит, является ли эта метка модулем, и если да, то заменит ее на то, что выдаст этот модуль. Такие модули также располагаются внутри папки modules и имеют свою M-V-C. Главный контроллер модуля header, например, выглядит так:
Последняя функция $this->tp->parse($this->tag, $this->mname.'/'.$this->mname.'.tpl') заменит на содержание файла modules/header/views/header.tpl
Следует отметить, что всегда первым проработает модуль, который загружается из URL и является главным, затем подгружается шаблон и parse() прорабатывает все модули внутри него. Для наглядности всё, описанное выше, изобразил на картинке
Прошу простить за примитивную графику, я всё же программист, а не дизайнер.
Если лень писать всё это вручную, можете скачать CodeIgniter с моими небольшими доработками и поковыряться там (ссылка внизу). Я специально вырезал оттуда почти все свои функции, модули, модели и всё остальное, ограничившись лишь описанным в статье, дабы вы попробовали расширить функционал сами и не отвлекались на всё остальное.
Конечно, можно было еще много чего дописать, но статья итак получилась длинная.