В прошлом туториале мы установили Kohana на сборку XAMPP и запустили простейший трехстраничный сайт. Фактически, он представлял собой «голый» контроллер, выводящий в браузер запрошенную страницу. Бесспорно, такой подход имеет право на существование, особенно в веб-приложениях без пользовательского интерфейса. Однако при наличии достаточно сложной верстки отдаваемых посетителю страниц очень неудобно делать все в контроллере. Хочется выделить разметку в отдельный файл (или файлы; хотя это менее удобно для верстальщика, многие современные CMS делают именно так) и только «натягивать» их на данные по мере необходимости. Тут нам на помощь приходит реализованный в фреймворке Kohana архитектурный паттерн Model-View-Controller (MVC) в той своей части, которая обозначается как View-Controller. View как раз представляет собой шаблоны разметки для вывода данных, сами же данные предоставляются контроллером (хотя можно и напрямую из модели брать), который компонует Views в отдаваемую страницу. Впрочем, теория лучше понимается в сочетании с практикой. Давайте добавим к уже написанному нами контроллеру шаблоны отображения (Views).
Однако сначала обновимся до второго релиз-кандидата Kohana3. Не обновляем только наш контроллер и вручную (либо с помощью системы управления версиями) перезаписываем все, что нужно, в application/bootstrap.php, потому как некоторые приятные изменения там произошли. После обновления все должно заработать как раньше.
Итак, прикрутим к нашему котроллеру какой-нибудь View. Например, к нашей супер-авто-навигации. Создадим папку application/views/elements/, куда и закинем файл navigation.php. Шаблон будет очень простой (я не дизайнер):
<ul id="site_nav">
<li style="display: inline; font-weight: bold;">Navigation: </li>
<?php
if (is_array($items) AND ! empty($items))
{
foreach ($items as $item)
{
?>
<li style="display: inline;"><?php echo $item; ?></li>
<?php
}
} else {
echo 'No navigation presented';
}
?>
</ul>
Сейчас нужно подключить его в контроллере, попутно передав нужные данные. Думаю, что логичнее всего будет сделать это в нашем методе Controller_Welcome::_simple_nav() отвечающем за формирование навигационных элементов. Да, предварительно надо переделать метод так, чтобы он формировал массив навигационных элементов. Это просто: вместо конкатенации строк будет добавление элемента массива
$slugs[] = HTML::anchor($slug, url::title($slug));
Ну а дальше классически присоединим шаблон, передав в него данные
$view = new View('elements/navigation');
$view->items = $slugs;
Кстати, можно присоединить шаблон более элегантно, с использованием «фабричной» генерации объекта и метода View::set('template_var', $controller_var), позволяющего присвоить переменной шаблона $template_var значение переменной $controller_var:
$view = View::factory('elements/navigation')
->set('items',$slugs);
Все работает!
Есть способ несколько упростить формирование отдаваемой страницы, избавившись от рутинной необходимости явно цеплять основной шаблон и выводить его в $this->request->response. Для этого достаточно унаследовать наш контроллер не от стандартного класса Controller, а от не менее стандартного Controller_Template:
class Controller_Welcome extends Controller_Template {
Главное, не забыть, что по умолчанию для основного шаблона ищется и используется views/template.php. Если у вас он другой, нужно это явно декларировать, например, так
public $template = 'custom_template';
Создадим простой базовый шаблон custom_template.php и поместим его в views/, сверстав как-то так:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="keywords" content="" />
<meta name="description" content="" />
<title><?php if ( ! empty($title)) echo $title; ?></title>
</head>
<body>
<div class="container">
<div id="navigation">
<?php echo $navigation; ?>
</div>
<div id="content">
<?php echo $content; ?>
</div>
</div>
</body>
</html>
Теперь из методов контроллера заполним его данными. Вот, например, как я это сделал в методе Controller_Welcome::action_index():
public function action_index()
{
$this->template->title = 'Main page';
$this->template->content = 'hello, world!!';
$this->template->navigation = $this->_simple_nav();
}
Заметьте: уже нет необходимости явно присваивать свойству $this->request->response сгенерированный вид. Присвоение сейчас осуществляется в унаследованном методе Controller_Welcome::after().
Давайте добавим еще немного удобства в рутинную процедуру заполнения шаблонов данными. В этом нам поможет метод View::bind(), позволяющий на самой ранней стадии «привязать» к переменной шаблона какую-либо переменную контроллера и потом уже не следить за тем, где ее инициализировать. Чтобы наглядно показать простоту такого способа работы, создадим новый вид для отображения области контента: application/views/elements/content.php
<p>
<?php echo $p1; ?>
</p>
<p>
<?php echo $p2; ?>
</p>
Назначив его, мы должны заполнить $p1 и $p2, однако не можем сделать это раньше, чем эти переменные определены:
$this->template->content = View::factory('elements/content')
->set('p1',$par1)
->set('p2',$par2);
$par1 = 'news 1';
$par2 = 'news 2';
выдаст ошибку.
Но здесь можно поступить так:
$this->template->content = View::factory('elements/content')
->bind('p1',$par1)
->bind('p2',$par2);
$par1 = 'news 1';
$par2 = 'news 2';
Нетрудно убедиться на практике, что сейчас все проходит как надо. В чем преимущество View::bind()? На практике в контроллере зачастую приходится переопределять значения переменных, выводимых в шаблон. Данный метод позволяет не отслеживать все такие переопределения, назначив «ответственные» переменные на ранней стадии обработки и делая дальше все, что душа пожелает.
Удачи!
ЗЫ. Кому нужно, можно скачать архив папки application/
Comments: 6 RSS
1 Sezarin 12-08-2009 12:57
Прочитав очередной пост-главу у меня возникли некоторые вопросы по построению шаблона страницы, в частности, возможна ли вставка в шаблон дополнительных блоков, скорее всего - виджетов, привязанных к контролерам отдельных модулей? Например:
И, если да, то как при этом будет осуществляться вызов соответствующих контролеров.
2 Александр Купреев 12-08-2009 17:36
Конечно, возможна
Об этом будут следующие главы
3 Броткин Иван 19-08-2009 13:43
По поводу bind() - очень удобно при цикличном выводе блоков (комментарии к примеру):
Вроде так
4 Александр Купреев 19-08-2009 14:47
Спасибо, Иван, очень классная иллюстрация удобства View::bind()! Действительно, в этом случае отпадает необходимость в цикле переназначать $subview, достаточно это сделать однажды перед циклом.
5 Ахмадишин Ренат 07-09-2009 00:37
Было просто великолепно узнать как все же вставить свои виджеты.
6 Александр Купреев 07-09-2009 10:16
хорошо, подождите несколько дней -- напишу продолжение