4 июля 2011 г.

Стандартный роутер ZF или "будь бдителен!"

В zf, как известно, существует стандартный роутер по имени "default". Но есть и возможность описывать свои "человекоподобные" урлы.

Итак:
Есть пачка роутеров с параметрами. Параметров много, в роутерах задаются дефолтные значения и регулярки для валидации. Казалось бы - в контроллере эти переменные проверять ненадо, ведь если человек дошёл до контроллера, значит все проверки роутера выполнились. И тут (ВНЕЗАПНО!) я вспоминаю про стандартный роутер, без всех этих проверок.
Само собой ничего критичного там нет, да и prepared state дают защиту от SQL inject, но возможность вывести сразу все данные на одной странице (пагинация с изменяемым количеством итемов на странице) тоже может доставить хлопот.

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

Доку пролистал вдоль и поперёк. Что нарыл:
1.Можно убить стандартный роутер вообще (ну или заменить на заведомо левый). Но этот вариант заставляет писать роутеры для всех страниц
2.Можно в критичных действиях проверять ($this->getFrontController()->getRouter()->getCurrentRouteName() == 'default' ). Достаточно гибкое решение, но генерирует слишком много повторного кода.
3. Зашить предыдущую проверку в плагин фронт контроллера и сочинить конфиг (для определения критичных действий). Тут уже можно разгуляться, но всё равно это смотрится костылём.



А больше, собственно, вариантов я не нашёл, поэтому представляю реализацию 3го описанного варианта.

Сам плагин

Copy Source | Copy HTML
  1.  
  2. /*
     * Плагин убивает стандартный роутер для заданных действий
     * позволяет проводить проверку переменных в роутерах (что есть та ещё КОРОВЬЯ СИЛА!!!11)
     * @author esemi
     */
  3. class MyDefaultRouterCrunch extends Zend_Controller_Plugin_Abstract
  4. {
  5.  
  6.     public function routeShutdown(Zend_Controller_Request_Abstract $request)
  7.     {
  8.         $front = Zend_Controller_Front::getInstance();
  9.  
  10.         //составной хеш, сравнивается с параметрами в конфигах
  11.         $hash = $request->getControllerName() . '_' . $request->getActionName();
  12.  
  13.         //имя роутера
  14.         $name = $front->getRouter()->getCurrentRouteName();
  15.  
  16.         //действия для ограничений
  17.         $hits = $front->getParam('bootstrap')->getOption('defaultRouterCrunch');
  18.  
  19.  
  20.         if( $name == 'default' && in_array($hash, $hits) )
  21.              throw new Exception('Доступ в обход роутера. Если Вы не виноваты - сообщите разработчикам');
  22.  
  23.     }
  24. }

Пример конфига

Copy Source | Copy HTML
  1. resources.frontController.plugins[] = MyDefaultRouterCrunch
  2.  
  3. ;действия, для которых критичен переход именно по роутеру (controller_action)
  4. defaultRouterCrunch[] = "worlds_history"
  5. defaultRouterCrunch[] = "worlds_alliances"
  6. defaultRouterCrunch[] = "worlds_players"
  7. defaultRouterCrunch[] = "alliance_players"
  8. defaultRouterCrunch[] = "alliance_colony"
  9. defaultRouterCrunch[] = "player_quick"


ПыСы: посоветовался с симфонистами - мы, говорят, стандартный роутер в жизни не видели, всегда описывали каждый доступный юрл. Может и верная практика.

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