Внутренние роуты в Kohana 3
Воскресенье, 31 января 2010 г.Рубрика: Web frameworks
Метки: Kohana | плагин
Просмотров: 2591
Подписаться на комментарии по RSS
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><img src="http://kupreev.com/uploads/smiles/wink.gif" width="19" height="19" alt="wink" style="border:0;" class="smiley">')
->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, поэтому он у меня все равно генерируется. Ну и небольшой рефакторинг -- вынесение общей логики в отдельный метод.
Эти модификации пока не подвергались тщательному тестированию, но интересующиеся могут их скачать на свой страх и риск ![]()
Скачать/Download 30 (zipped application/, ~3 KiB)
Надеюсь продолжить эксперименты. Критика встречается на "Ура!"
Комментариев: 4
А зачем нужен роут, если контроллер/метод будет доступен только изнутри? Используйте reflection (как в Request->execute()) в каком-то отдельном методе базового класса.
Роут нужен для унификации интерфейса работы с контроллерами. Конечно, если я пишу систему полностью сам, то нужные методы смогу вызвать через их отражения. Но если работаю с расширяемой системой, то хочется больше гибкости.
Описанное выше -- это начало эксперимента по воплощению моих смутных фантазий. Я не могу гарантировать, что воплощение будет достойным, или что оно вообще приобретет какую-либо законченную форму, времени на это очень мало. Публикую же потому, что возможно кому-то будет полезно или наведет на интересные мысли
Ну, ИМХО, роуты нужны в первую очередь для внешнего обращения. Все, что делается при разборе маршрутов, можно повторить отдельно, в удобной для себя редакции, причем один раз (в своем специальном методе или статическом классе, если хочется). Зачем смешивать роуты, придумывать костыли, если изначально Вы хотите их разделить друг от друга?
В конце концов, добавьте эти несчастные внутренние роуты ПОСЛЕ отработки Request::instance(), например в before() базового класса (можно добавить проверку $this->request === Request::instance(), чтобы постоянно не добавлять роуты).
Спасибо за комментарий, Иван, возможно я действительно "намудрил" без надобности. Буду думать в контексте того, что планирую сделать.