Универсальная шаблонизация для Kohana 3.1 из песочницы

Здравствуйте, уважаемые жители Хабра.

Достаточно долгое время я работал с различными движками, фреймворками и иными самописными вариантами двигателя прогресса PHP, в следствие чего тогда еще юный и жаждущий перемен мозг сложил свое и, как это обычно бывает, весьма амбициозное впечатление о том, что нужно делать всё. Вот, совсем всё.

Копаясь в таких монструозных штуках, как Drupal или Joomla, я понимал, что да, обилие развитого API и наличие большого количества модулей делает такие движки незаменимыми при создании сайтов практически любой сложности, однако, переходя на более простые вещи, вроде MVC фреймворков CodeIgniter или Kohana (последний, по существу, следует концепциям HMVC), приходило понимание, что колоть орехи ракетами земля-земля не всегда удобно, и легкость не только в обращении с кодом, но и в работе самого сайта, что называется, «решает».

В настоящий момент я работаю на MVC (или HMVC, если быть точным) фреймворке Kohana, в частности, на ветках версий 3.0 и 3.1, и, попытавшись найти наиболее полное и элегантное решение для адекватной шаблонизации сайта, я с удивлением обнаружил, что либо мои навыки гугления морально устарели, либо действительно результаты подвели, но однозначно объективного лидера для моей задачи нет.

Собрав некоторые из своих мыслей и наработок, я решил объединить их в удобную и интуитивную систему шаблонизации.

Сразу говорю, приводить весь код не буду, так как он занимает довольно много строк, плюс, включает в себя не описание не только контроллера, но и некоторых представлений, потому расскажу общий принцип работы шаблонизатора с небольшими примерами.

В основе шаблонизации лежит контроллер template.php, который необходимо положить в APPPATH.classes/controller/

Сам управляющий контроллер описан так:
abstract class Controller_Template extends Controller

Следовательно, чтобы ваш контроллер смог использовать возможности шаблонизации, его необходимо наследовать от управляющего, например:
class Controller_Welcome extends Controller_Template

Общие возможности шаблонизатора


  • meta, script, style — функции добавления meta-тегов, ссылок на js скрипты и css-файлы.
  • Заголовок — Установка заголовка страницы.
  • Хлебные крошки — позволяет строить цепь «хлебных крошек» навигации на основе уже созданных в пути.
  • Контент — «блочное» заполнение ключевой переменной $content, являющейся основным содержимым страницы.
  • Регионы — «блочное» заполнение контент-переменных для различных мест на странице, вроде header, footer и так далее.
  • Пейджер — Простая генерация линейки ссылок на странице на основе номера текущей страницы и общего количества элементов.
  • Возвращение трех разных видов контента в зависимости от запроса (HMVC, AJAX или обычный).


На вид, количество функций не особо и впечатляет, но я не ставил перед собой целью (во всяком случае, на первое время) сотворить из этого функционал на уровне phptemplate или чего-то другого более громоздкого и всеобъемлющего. Указанных выше функций хватает, чтобы определить центральную систему шаблонизации сайта, и больше не мучиться, и это главная задача, которая стояла передо мной.

Дальше приведу краткое описание происходящих в контроллере махинаций.

Работа контроллера


Для того, чтобы наш контроллер, для которого мы включаем шаблонизацию, не ругался, необходимо в его функции before указать строчку parent::before() для того, чтобы инициализировать шаблонизатор.

Во время инициирования шаблонизатора и, соответственно, автоматического выполнения его функции before происходят следующие действия:
— Загрузка конфига шаблонизатора
— Проверка и установка пейджера, если передан аргумент page
— Создание «пустого» представления и обнуление шаблонных переменных

После этих действий шаблон уже готов к работе и может запускаться даже без установки каких-либо параметров. Сам запуск осуществляется автоматически, без вызова каких-либо функций вроде render() или других, что избавляет нас от необходимостью следить за процессом генерации контента.

Однако, наша цель ведь, наоборот, стояла в том, чтобы обеспечить нам возможность легкого управления шаблоном, не так ли?

Для модификации страницы можно использовать следующие конструкции в любом месте функций контроллера:

Список предоставляемых функций

  • target($path) — устанавливает путь для главного файла представления, на котором будет работать шаблонизатор. Если путь не установлен, берется стандартный из папки шаблона в представлениях.
  • meta_name($title, $content) — устанавливает мета-тег вида \<meta name='name' content='content'\>.
  • meta_equiv($title, $content) — устанавливает мета-тег вида \<meta http-equiv='name' content='content'\>.
  • breadcrumb($link, $title, $strict = false) — устанавливает хлебную крошку в уже имеющийся путь. С параметром $strict по умолчанию новый путь приклеится к уже существующему, в противном случае будет создана абсолютная ссылка.
  • title($title) — устанавливает заголовок страницы.
  • content($html, $clear = false) — добавляет блок в контент-переменную страницы. По умолчанию в функцию передается готовый к рендеру объект класса VIEW, но в случае установки флага $clear содержимое понимается как чистый html.
  • region($region, $html, $clear = false) — добавляет блок в одну из региональных переменных страницы. Работает по аналогии с функцией content, только требует указать название региона.
  • alias($region, $alias = '') — создает алиас для уже созданного региона страницы. По умолчанию, все регионы хранятся в массиве $regions, а эта функция позволяет задать им прямую переменную вида $алиас
  • add_style($path) — добавляет css файл. Путь берется относительно медиа-пути, указанного в конфиге шаблонизатора.
  • add_script($path) — добавляет js файл. Путь берется относительно медиа-пути, указанного в конфиге шаблонизатора.
  • pager($all) — создает линейку пейджера на основе общего количества каких-либо элементов. Если страница не задана, а функция вызывается, шаблонизатор генерирует линейку для первой страницы.


Мелкие замечания

Указанные выше функции работают в любом порядке и действуют как накопители информации, которая компилируется и рендерится уже после выполнения пользовательского экшена, поэтому нет нужды следить за правильной последовательностью генерации шаблона.

Функции генерации хлебных крошек, пейджера и блоков контента и регионов использую свои представления, хранящиеся в папке представлений шаблонизатора по путям, соответственно, breadcrumbs, pager и blocks.

В зависимости от типа запроса, которым вызывается тот или иной action, шаблонизатор возвращает разный контент. Для HMVC запроса это будет только скомпилированная и отрендеренная контент-переменная страницы, для AJAX запроса будет та же переменная, но завёрнутая в json_encode, а обычный запрос полностью выполнит рендер всей страницы.

Итого



Подводя итоги, скажу еще раз, что шаблонизатор не претендует на звание лучшей темизационной машины для Kohana, а представляет собой просто удобную совокупность идей для облегченного управления видом страницы. Замечу, что здесь есть, куда расти, например, добавить возможность рендера в html или xhtml, прикрутить механизм выгрузки css-стилей и js-скриптов в соответствующие теги style и script для отображения их в результате в качестве текста, а не ссылок на случай AJAX или HMVC-запроса, и еще много других вкусных плюшек, но это будет делаться по мере надобности.

Вскоре эта ветка будет залита в гит, чтобы каждый желающий мог легко скачать и обновлять файлы по мере расширения функционала, а пока приношу извинения за ссылку на файл. Модификации и видоизменения приветствуются.

Надеюсь, этот шаблонизатор кому-то поможет и избавит от лишних проблем.

Спасибо всем за внимание, буду рад услышать конструктивную критику и предложения!
+5
26 марта 2011, 15:42
28
Melorian 8,9

комментарии (33)

0
Atrax #
Стесняюсь спросить — а как же реализована буковка V аббревиатуры MVC? Если шаблонизатор нужно отдельно писать…
+1
Melorian #
Буковка V в аббревиатуре означает, что какой-либо контроллер загружает какое-либо представление. В случае коханы, кодигниттера и иже с ними представления можно включать в другие представления. Так, например, имея на сайте разделы (контроллеры) «мои жжшечки» и «ни о чем», обладающие одинаковыми, скажем, хидерами и футерами, но разным контентом, мы можем как создать два отдельных view под каждый раздел, так и один view-каркас, и два view-содержимых для, соответственно, бложиков и ниочем'а, и потом включать тело одно их них в карсас. Собственно, мой модуль и создан для того, чтобы упростить и автоматизировать сей процесс, сведя всё к «каркасу».
0
ajaxtelamonid #
Да, штатной организации шаблонов в Кохане нет и это задача, с которой сталкивается любой, кто пишет на этом фреймворке. У меня вот такая затравка. Я её где-то нашел, не помню уже где, и слегка переделал под себя.
0
Melorian #
Да, знаю это решение, но оно показалось мне не достаточно полным и удобным)
+1
Chode #
Почему именно 3.1? Какие особенности используются?
+3
VasilioRuzanni #
А можно узнать (не работал с Kohana, я из мира ASP.NET MVC и Rails):
«Для модификации страницы можно использовать следующие конструкции в любом месте функций контроллера:… список...»

А почему такие строго View-related-штуки вообще должны использоваться в контроллере?
0
Melorian #
Ну как бы, MVC предполагает, что мы обращаемся к контроллеру, который загружает модель, работает с ней, и выдает нам конкретный View. Потому эта система шаблонизации загружает нужные представления, как любой другой контроллер, просто она делает это в нужные места)
0
Melorian #
Маленькое дополнение, все эти конструкции, в первую очередь, являются не выводящими контент, а накопителями, контент формируется уже после окончания работы экшена)
+2
VasilioRuzanni #
Ну, вот о том и речь — есть, к примеру, в этом списке, функции, типа «title», а то и куда «хлеще» — «add_style» или «add_script» — это же вещи, про которые контроллер, по идее, вообще не должен знать — ему все равно какие стили будут наложены, чтобы оформить данные, которые он готовит.
0
Melorian #
Здесь была выбрана реализация шаблонизатора контроллером, а не модулем, для того, чтобы облегчить наследование контроллеров. Когда мы наследует этот главный контроллер, мы получаем полный набор управляющих конструкций, за которыми не нужно следить. Точно так же, как у контроллера есть функции, скажем, after и before, различные add_style и другие добавляют лишь функционал.
А в случае реализации модулем, приходилось бы создавать инстанс модуля, пичкать его управляющими конструкциями, да и вообще, это было бы подключением лишнего класса, что не очень экономно. Хотя, не спорю, эту вещь можно легко переписать и под модуль, но я пока не встречал ни одного такого проекта)
+1
VasilioRuzanni #
Я не совсем в курсе, что такое модуль именно в кохановской архитектуре, но предполагаю, что слой View можно просто оформить отдельным слоем. И как по мне — это правильно.

«различные add_style и другие добавляют лишь функционал.»
Этот функционал надо бы добавлять как раз к слою, ответственному за View. Например, некий абстрактный ViewPage. У этого самого ViewPage может быть любое количество View-Helper-методов, строго для View-логики.

«А в случае реализации модулем, приходилось бы создавать инстанс модуля, пичкать его управляющими конструкциями, да и вообще, это было бы подключением лишнего класса.»
Нууу, что значит «лишнего класса»? Ведь вся суть MVC в SoC (Separation of Concerns), которая и обеспечивает проектам на этом паттерне такой высокий уровень «maintainability». А иначе, если так подумать — почему бы не запихать все в один класс, вообще всю систему — ведь иначе же «лишние классы, инстансы».
0
Melorian #
Ну, дописывать функционал самого VIEW, понятное дело, не стоит, ибо системный класс. Расширить его? Как вариант, конечно, но будет некоторая путаница. Единственным вариантом «прослойки» остается отдельный модуль, наполняющий и возвращающий определенный макет. В дальнейшем я планирую сделать две реализации: развить текущую, «контроллерную», и сделать модуль, а пока собираю полезные советы) В любом случае, спасибо за идеи, наталкиваете на мысли!)
+1
VasilioRuzanni #
Ну я просто исхожу как раз из того, как это сделано в ASP.NET MVC и RoR. В ASP.NET MVC именно что «из коробки» есть классы HtmlHelper, UrlHelper, AjaxHelper, к которым очень просто дописываются новые методы расширения. И (тада!) все эти хелперы являются свойствами объекта View. Полное разделение полномочий по уровням.

А в чем может быть путаница? По-моему, наоборот, все на своих местах :)
0
Melorian #
Нет, ну логично, я не спорю, в кохане тоже есть хэлперы (хотя уже и не в столь значимом варианте, как в предыдущих версиях). Просто до сих пор это не было связано в какую-либо систему для сайтов с изменяемым контентным содержимым и разделением на регионы) А я вот, под свои нужды, написал то, что было удобно) Хотя, судя по отзывам, идея должна жить, так что я буду ее развивать)
+1
VasilioRuzanni #
К слову — методы расширения (Extension Methods) — это концепт C#.NET, который активно используется в MVC. Но, как я и предполагал, для PHP можно крайне просто реализовать аналогичную штуку, используя немного рефлексии.

И разумеется, менять системные классы не нужно.
0
Melorian #
Ну то, что вы прислали, это как-то совсем жестко) Во всяком случае, в кохане это можно сделать чуть проще) Хотя и не далеко от вашего решения) Просто я хотел разделить обязанности, оставив view непосредственно для отображения контента, а свой контроллер для его накопления)
+1
VasilioRuzanni #
А как это можно сделать просто? Поскольку в PHP нет встроенного концепта Extension Methods — то без рефлексии никак :) А то, что я прислал — почему жестко то? 10 строк работы с рефлексией и вуаля — используем эти методы.

«Просто я хотел разделить обязанности, оставив view непосредственно для отображения контента, а свой контроллер для его накопления»

Ну я потому и зацепился за это, потому что мне кажется, что у вас контроллер делает больше, чем ему положено. А что значит «накопления»? Подготовка данных — да, однозначно, но (кроме непосредственно отображения) сборка View из кусков — это задача View-уровня.
0
Melorian #
Я написал чуточку ниже)
habrahabr.ru/blogs/webdev/116237/#comment_3774967

Если бы изначально структура view коханы предполагала такую глубокую работу с шаблоном — это было бы здорово) Но, увы, только сторонними штуками, ну то есть, встроенными в кохану, но по существу, не связанными непосредственно с представлениями)
0
VasilioRuzanni #
Ага, понял, значит это все-таки, «by-design», решение самих создателей Коханы.
0
Melorian #
Да, скорее всего) Хотя, вроде как, ветку 3.1 вообще чуть ли не один человек тянет сейчас, так что не удивительно, что ему за всем не уследить!)
+1
VasilioRuzanni #
Кстати, про наследование контроллеров. Для чего оно вообще может быть нужно. Это какая-то фича HMVC?

Просто на практике MVC мы вообще избегаем базовых контроллеров (лишний layer-supertype как гвоздь в заднице :), а вся AOP-логика задается фильтрами (уверен, в Kohana обязана быть реализация паттерна Intercepting Filter в том или ином виде), данные же, не имеющие отношения к «основному» action-у — получаются через вызовы «RenderAction» во View.
0
Melorian #
Ну, быть может, в кохане какая-то своя реализация MVC) В кохане view представляет собой средство загрузки представлений, читай, файлов html (и не только) кода, внедрения в него переменных (присоединением или привязыванием) и рендер с целью возвращения чистого html из объекта. Утрированно, конечно, но в общем)

А контроллер служит для обработки запроса пользователя. Пришел к нам пользователь со ссылкой example.com/buy/milk, мы ему откроем контроллер buy, в котором запустим функцию action_milk, которая должна уже, используя $this->response->body( <что-то>) вернуть внутри «что-то» хоть представление, хоть чистый текст вроде пресловутого «123») Хотя контроллер представляет из себя обычный класс, и наследование в нем сделано, в частности, для расстановки по папкам.
Предположим, у вас есть контроллер «личный кабинет». В нем вам в любом случае надо опознать пользователя по его сессии (или кукам), загрузить общий шаблон страницы, ну и еще что-то. А внутри подпапочки «личный кабинет» есть контроллеры, скажем «фотография», «настройки» и «государственные тайны», которые, наследуясь от «личного кабинета», смогут, во первых, сразу получить доступ к переменной пользователя, заданной в родительском контроллере, а во вторых, уже будут иметь html-окружение, в которое могут вставить свой $content) Фух…
0
VasilioRuzanni #
Ну, я понял, как это работает. Не уверен, правда, это в Kohana так что называет «by design» или это вы так используете.

А нет ли какой-то системы layout'ов?

Мне очень логичной кажется схема в ASP.NET MVC, которая идет прямиком из Rails — если нужно отрендерить HTML, то Action контроллера возвращает ActionResult специального типа (ViewResult), этот результат ищет нужный View в папке, загружает его и выполняет всю View-логику (шаблонизатора), включая расстановку переменных в нужные места.

Вся суть в системе layout'ов — у View назначен собственный layout. В этом layout есть директива шаблонизатора вроде «RenderBody», куда и вставляется тот View, что получился в результате работы Action-а контроллера (таким образом, один и тот же layout может быть использован несколькими View). Но это не все — как layout, так и сам View, могут содержать директивы «RenderAction» (с указанием того, какой именно контроллер и какой Action должен отработать) — вместо этих директив будут «вставлены» результаты работы этих контроллеров. Вот с помощью этих штуковин (RenderBody, RenderAction, ну плюс еще RenderPartial и RenderSection) получается, что можно сделать все, что угодно, переиспользовать как угодно, и не повторяться (aka DRY principle). И безо всякого наследования контроллеров.
0
Melorian #
Насколько мне известно, в кохане это не так развито, как у вас) Если интересно, вот ссылка на api по view: kohanaframework.org/3.1/guide/api/View

View коханы, по сути, только возвращает html. По существу, он никак не связан с контроллером, ибо вызываться может и в контроллере, и в другом view, и еще черт-те знает, где)
+1
Melorian #
Ну, во-первых, 3.1 была выбрана потому, что хотелось ее изучить, сравнить с 3.0.
А во вторых, всё-таки, Kohana должна следовать принципам ООП, что реализовано в 3.1, в частности, в нормальной проверки на тип запроса функциями is_ajax(), is_initial() и других против прямого обращения к переменным 3.0. Мне показалось это… Приятнее)
+1
egorinsk #
А почему нет примера маленького контроллера и пары шаблончиков? было бы намного нагляднее, а так трудно что-то понять.
0
Melorian #
Почему же нету?) В папке классов лежит контроллер example с применением основный функций)
0
dimmaq #
Как раз изучаю кохану, выглядит интересно.
Хорошо бы увидеть на каком-нибудь гитхабе и в виде модуля.
+1
Melorian #
Скоро будет)
+1
Big_Shark #
Макет (layout) не предполагает жесткое разделение на блоки (header, footer и т.д).
Его нужно использовать только тогда, когда несколько разных макетов, предполагают использование одни и тех же блоков.
Предлога вам строить мета теги через хелпер HTML::attributes() и ссылки через HTML::anchor()
Конструкцию
foreach ($this->scripts as $scripts)
{
$html .= $script."\n";
}

Можно заменить на
$html = implode("\n", $this->scripts)."\n";
Рекомендую исправить такие недочеты как:
$this->pager_pair(1,'1')

Насколько я понял ваш класс не рассчитан отдавать JSON на AJAX запрос?
0
Melorian #
Ну, на счет макета, шаблонизатор не предполагает обязательное использование регионов, по умолчанию в нем важен только $content, так что он может загружать как блочные макеты, так и отдельные.
На счет implode, да, была такая мысль, но, по скольку в процессе написания шаблонизатора идеи возникали не меньше, чем до начала написания, то такая конструкция была сделана с целью предумсмотреть дальнейшую возможную обработку элементов)
На счет пары пейджера, согласен, логичнее было бы сделать вторую переменную необязательной)

А JSON код отдает, вот строчки:
if (! $this->request->is_initial() ) // Если запрос вызывается путем HMVC
elseif ($this->request->is_ajax() ) // Если запрос аяксом
else // Если обычный запрос
0
visor86 #
Не удобно, что нет layouts, приходится изощряться, а идею полностью поддерживаю.
0
Melorian #
Спасибо)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.