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

Просмотров: 6489Комментарии: 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 с общей аутентификацией, назначением прав и т.п. Конечно, не так сложно сделать это распределенно, но... тогда считайте рассказанное ниже моим экспериментом.


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

[code lang="php"]

protected $_is_inner = FALSE;

[/code]

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


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

[code lang="php"]

public static function set_inner($name, $uri, array $regex = NULL)

{

    return Route::$_routes[$name] = new Route($uri, $regex, TRUE);

}  

[/code]

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

[code lang="php"]

if ((bool) $is_inner)

{

    $this->_is_inner = TRUE;   

}

[/code]


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

[code lang="php"]

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;

}

[/code]


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

[code lang="php"]

public static function factory($uri)

{

    return new Request($uri, TRUE);

}

[/code]


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

[code lang="php"]

public function __construct($uri, $consider_inner = FALSE)

{

    // Load routes

    $routes = Route::all((bool) $consider_inner);

[/code]


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

[code lang="php"]

Route::set_inner('notebook', 'notebook(/<action>)')

    ->defaults(array(

        'controller' => 'notebook',

        'action' => 'index',

        )

    );

[/code]

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

[code lang="php"]

Request::factory('notebook/list')

    ->execute();

[/code]           

или

[code lang="php"]

Request::factory(

    Route::get('notebook')->uri(

        array(

            'controller' => 'notebook',

            'action' => 'list'

            )

        ))

    ->execute();

[/code]   

или даже

[code lang="php"]

Request::factory(

    array('notebook' => array(

        'controller' => 'notebook',

        'action' => 'list'

            )

        ))

    ->execute();

[/code]


  

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

[code lang="php"]

array(

    имя роута => array(

        параметры роута

        ...

        )

    )

[/code]


  

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


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

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


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

Комментариев: 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

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