Внутренние роуты в Kohana 3

Views: 7320Comments: 4
Web frameworks

Briefly in English (I'm going to translate this article a little bit later):
A modification of Kohana 3 Request and Route classes allows to hide some controllers/routes from "outside" interaction.

Задача

Разрешить доступ к некоторым контроллерам системы (Kohana 3) только изнутри системы.


Вариант решения

Простейший случай -- проверять в контроллере $this->request === Request::instance() и отказ в случае TRUE. Однако я бы хотел немного усложнить задачу: система вообще не должна знать про "скрытый" контроллер, если запрос осуществляется "извне". Зачем так? Это может быть полезно, в частности, при реализации Front Controller с общей аутентификацией, назначением прав и т.п. Конечно, не так сложно сделать это распределенно, но... тогда считайте рассказанное ниже моим экспериментом.


Итак, будем решать "в лоб". Заведем у роута еще одно защищенное свойство, ответственное за его "внутренность":

protected $_is_inner = FALSE;

(разумеется, работаем не в оригинальном, а в наследованном классе).


Делать роут внутренним будем при его объявлении (в bootstrap.php или init.php), используя для этого функцию Route::set_inner() :

public static function set_inner($name, $uri, array $regex = NULL)
{
    return Route::$_routes[$name] = new Route($uri, $regex, TRUE);
}  

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

if ((bool) $is_inner)
{
    $this->_is_inner = TRUE;   
}

Наконец, модифицируем метод Route::all(), возвращающий список роутов. Пусть он при необходимости умеет игнорировать внутренние:

public static function all($consider_inner = FALSE)
{
    if ($consider_inner)
    {
        return Route::$_routes;           
    }
       
    return array_filter(Route::$_routes, 'Route::not_inner');
       
}
public static function not_inner($route)
{
    return ! $route->_is_inner;
}

Поскольку "внешний" запрос работает через Request::instance(), а внутренний -- через Request::factory(), то без правки класса Request тоже не обойтись (оригинальный не трогаем, только наследование!). Пусть при инициализации через Request::factory() принимаются во внимание все роуты (соответственно, Request::instance() запрашиват только нескрытые, потому что по умолчанию Route::all($consider_inner = FALSE))

public static function factory($uri)
{
    return new Request($uri, TRUE);
}

И да, конструктор тоже перепишем, добавив во входящие один параметр, ответственный за полноту списка роутов:

public function __construct($uri, $consider_inner = FALSE)
{
    // Load routes
    $routes = Route::all((bool) $consider_inner);

Кажись, все. Прописав какой-нибудь роут как внутренний,

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

можно убедиться, что он не виден "снаружи" (если запросить ko3.loc/notebook), но виден "изнутри"

Request::factory('notebook/list')
    ->execute();
          

или

Request::factory(
    Route::get('notebook')->uri(
        array(
            'controller' => 'notebook',
            'action' => 'list'
            )
        ))
    ->execute();
  

или даже

Request::factory(
    array('notebook' => array(
        'controller' => 'notebook',
        'action' => 'list'
            )
        ))
    ->execute();

  

В последнем случае вы видите еще одну модификацию связки классов  Request/Route, смысл которой поясняется в части третьей статьи хабраюзера homm "KO3: HMVC и роутинг": избежание бесполезного парсинга URL на фрагменты, которые нам итак известны. homm написал модуль, позволяющий в упрощенной и убыстренной форме выполнять запрос, если известно название роута и его параметры. Идея мне понравилась. К сожалению, его модификация не "склеивается" с моей, поскольку обе они претендуют на один и тот же параметр в конструкторе Request. Можно, конечно, "разрулить" по разным местам, но это некрасиво. Поэтому я воплотил его задумку немного по-своему: теперь $uri в конструкторе рассматривается либо как строка URI, либо как массив вида

array(
    имя роута => array(
        параметры роута
        ...
        )
    )

  

Кроме того, я не решился оставить систему без честного URI, поэтому он у меня все равно генерируется. Ну и небольшой рефакторинг -- вынесение общей логики в отдельный метод.


Эти модификации пока не подвергались тщательному тестированию, но интересующиеся могут их скачать на свой страх и риск smile

Скачать/Download 102 (zipped application/, ~3 KiB)


Надеюсь продолжить эксперименты. Критика встречается на "Ура!"

Comments: 4 RSS

1 Броткин Иван 01-02-2010 10:06

А зачем нужен роут, если контроллер/метод будет доступен только изнутри? Используйте reflection (как в Request->execute()) в каком-то отдельном методе базового класса.

2 Александр Купреев 01-02-2010 13:59

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

Описанное выше -- это начало эксперимента по воплощению моих смутных фантазий. Я не могу гарантировать, что воплощение будет достойным, или что оно вообще приобретет какую-либо законченную форму, времени на это очень мало. Публикую же потому, что возможно кому-то будет полезно или наведет на интересные мысли smile

3 Броткин Иван 02-02-2010 12:14

Ну, ИМХО, роуты нужны в первую очередь для внешнего обращения. Все, что делается при разборе маршрутов, можно повторить отдельно, в удобной для себя редакции, причем один раз (в своем специальном методе или статическом классе, если хочется). Зачем смешивать роуты, придумывать костыли, если изначально Вы хотите их разделить друг от друга?

В конце концов, добавьте эти несчастные внутренние роуты ПОСЛЕ отработки Request::instance(), например в before() базового класса (можно добавить проверку $this->request === Request::instance(), чтобы постоянно не добавлять роуты).

4 Александр Купреев 02-02-2010 21:49

Спасибо за комментарий, Иван, возможно я действительно "намудрил" без надобности. Буду думать в контексте того, что планирую сделать.

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


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

     

  

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

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

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