Простейший HMVC в Kohana3 RC2.1

Просмотров: 6469Комментарии: 5
Web frameworks

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

Прежде всего, несколько слов о HMVC (Hierarchical-Model-View-Controller). При использовании обычного паттерна MVC у нас есть главный контроллер, которому передается управление согласно URL и который затем запрашивает через модель нужные ему данные, «запихивает» их в шаблон и отдает посетителю. Такой подход часто абсолютно достаточен, но при разработке достаточно сложных приложений иногда он здорово ограничивает. Когда на странице планируется большое количество разнородных информационно-управляющих блоков (например, виджеты, но — не обязательно, могут быть, например, несколько разных форм или управляющих элементов), в соответствующем методе главного контроллера заваривается настоящая «каша». (Все чаще с этим сталкиваюсь на собственном опыте с Kohana 2.3.х.) Тут тебе и проверка POST,  и запрос таблицы, и пару информационных виджетов. Конечно, по сути это не очень страшно, можно справиться, однако с некоторых пор ощущается зудящее желание оставить в главном контроллере только главную управляющую логику. При работе с Коханой версии 2.3.x добиваюсь этого, используя библиотеки как полуконтроллеры, однако такое, в прямом смысле, половинчатое решение мне не нравится. И вот в Kohana3 мы получили встроенную реализацию паттерна HMVC благодаря тому, что можем вызвать Request::factory()->execute() в нужном месте в нужное время. Как это может выглядеть на практике? Мой пример будет достаточно простым, чтобы не затрагивать еще не изученных нами тем, однако, надеюсь, покажет, как это возможно делать (хотя, конечно, и не обязательно именно так).

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

Отлично, создаем новый контроллер application/classes/controller/psy.php, который будет осуществлять психотерапию, и в нем два метода:

class Controller_Psy extends Controller_Template {
    public $template = 'psy_template';   
      
    public function action_nice()
    {
        $this->template->content = 'You are very NICE!';
       
    }
   
    public function action_intellectual()
    {
        $this->template->content = 'You are very INTELLECTUAL!';
       
    }
      
}

Тут же озаботимся простейшим демонстрационным шаблоном views/psy_template.php для контроллера:

<div style="color: green;">
        <?php echo $content; ?>
</div>

Далее дополним методы нашего главного контроллера вызовом нашего вспомогательного контроллера:

public function action_page1()
{
      
    $out = Request::factory('psy/nice')->execute();
       
    $this->template->title = 'Page 1';
    $this->template->content = 'hello, world<br />page 1!<br />'.$out;
    $this->template->navigation = $this->_simple_nav();
       
}

Аналогично и другой. Заметьте: для вызова вспомогательного контроллера мы используем Request::factory(), а не Request::instance(), который является синглетоном!

Хорошо. Переходим на страницу ko3.loc/page1 и… упс! Ошибка. Кохана жалуется на отсутствие метода action_psy. В самом деле: если мы посмотрим на наш дефолтный роут из application/bootstrap.php, то легко убедимся, что psy там соответствует имени метода. Поэтому и ищется метод. Что ж делать? Для нашего вспомогательного контроллера нужен дополнительный роут, хотя бы такой:

Route::set('psy', 'psy/(<action>)')
    ->defaults(array(
        'controller' => 'psy',
        'action'     => 'index',
    ));

В нем четко определяется, что если URL имеет вид psy/(<action>), то запрашиваться будет нужный нам в данном случае контроллер psy. Только не забудьте поместить этот роут перед дефолтным, поскольку более общее правило последнего будет «жадничать», «поглощая» и наш случай вызова контроллера psy.

Конечно, сам факт того, что при введении новой иерархии контроллера нужно вносить изменения в application/bootstrap.php, несколько портит впечатление. Хотелось бы «все и сразу». Однако давайте будем честными сами с собой: система работает разумно и логично. Хотите красивые  короткие ссылки — добавляйте для всех вспомогательных контроллеров роуты. Не хотите коротких ссылок — можете обойтись «образцовым» роутом, данным нам в качестве примера

Route::set('default', '(<controller>(/<action>(/<id>)))')
     ->defaults(array(
         'controller' => 'welcome',
         'action'     => 'index',
    ));

В этом случае придется «онекрасивить» наш генератор навигации Controller_Welcome::_simple_nav(), исправив

if ($m->isPublic())
{
    $slugs[] = HTML::anchor($slug, url::title($slug));
}

на

if ($m->isPublic())
{
    $slugs[] = HTML::anchor('welcome/'.$slug, url::title($slug));
}

Какой-то «закон сохранения красивости» получается.

Как мне кажется, для первой демонстрации концепции HMVC этого простейшего примера вполне достаточно. Очень вероятно, что позже придется раскрыть тему полнее.

Подытоживая, хочу сказать, что главное, на мой взгляд, достоинство HMVC состоит в возможности отделения логики фронт-контроллера (управляющего процессом общей обработки запроса) от логики контроллера бизнес-логики smile (управляющего манипуляцией данных в конкретном смысловом поле, модуле если угодно). Хотя я полностью согласен с высказывавшейся ранее моими читателями точкой зрения, что подобная «лоскутность» шаблонов — сущий ад для верстальщика. Как говорится в народе, что программисту хорошо, то верстальщику…

В общем, буду рад вашим вопросам, дополнениям и особенно найденным у меня ошибкам smile 

Комментариев: 5 RSS

1 Sezarin 24-08-2009 12:42

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

А что, если, все-таки, взять и четко разделить роутинг скрипта (в данном примере уже наблюдается неявное его разделение на основной и, скажем так - роутинг второго уровня)?

Основной роутинг пишем в bootstrap.php (вызов view/.../index.php), а остальное выносим в соответствующий индексный файл представления smile

Все файлы иерархической структуры страницы, к примеру, находятся в той-же директории, что и index.php, но они не вызываются внешним контролером - они подгружаются контролером второго уровня, который встроен в index.php.

Ведь мы же можем написать, например в файле: view/blog/index.php

примерно следующее:


2 Александр Купреев 24-08-2009 14:22

Естественно, Request::factory()->execute() можно вызвать и из view совершенно так как Вы написали.

3 Sezarin 28-08-2009 16:59

Сегодня искал на офф. форуме обсуждение вопросов работы роутинга KO3 и наткнулся на одно полезное решение, которое должно добавить гибкости в работе скрипта.

Это расширение класса Kohana_Route (в оригинале - этот файл без логики). Заполняем его следующим кодом:

class Route extends Kohana_Route {
    public static $language = '';
    public function matches($uri) {
        if (preg_match('#^([a-z]{2})/|$#', $uri, $matches) AND isset($matches[1])) {
            // Set the currently defined language
            Route::$language = $matches[1];
            // Remove the language from the URI
            $uri = substr($uri, 3);
        }
        // call the parent function
        return parent::matches($uri);
    }
}

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

Есть возможность использовать это в динамическом режиме. Например, сгенерировав линки по определенным параметрам. Допустим, сегмент /en/ должен выводиться только на странице 2 примера, описанного выше. Для этого достаточно заменить строчку в методе _simple_nav контролера welkome.php

$slugs[] = HTML::anchor($slug, url::title($slug));

на что-то типа:

$slugs[] = $slug == 'page2' ? HTML::anchor('/en/' . $slug, url::title($slug)) : HTML::anchor($slug, url::title($slug));

4 Александр Купреев 31-08-2009 12:07

Простите, что не смог прокомментировать раньше -- был офлайн

Да, расширение может быть очень полезным. Когда буду рассматривать роутинг, пригляжусь пристальнее, может еще куда-нибудь его присобачить можно smile

Спасибо!

5 Сергей 16-09-2009 00:21

2 Sezarin

Удобный способ, использовал что-то подобное в Kohana 2.3. Единственная проблема - при таком подходе неправильно работают контроллеры, названные двумя буквами (я например долго искал, почему же не работает контроллер js smile)

Оставьте комментарий!


Используйте нормальные имена.

     

  

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

MaxSiteAuth. Войти через loginza

(обязательно)