Диспетчеризация контроллеров
Phalcon\Mvc\Dispatcher — это компонент, отвечающий за создание контроллеров и выполнение необходимых действий в приложении MVC. Понимание его работы и возможностей помогает нам получить больше от сервисов, предоставляемых фреймворком.
Цикл диспетчеризации
Это важный процесс, который тесно связан с самим циклом MVC, особенно с частью контроллера. Работа происходит внутри диспетчера контроллера. Файлы контроллеров читаются, загружаются и создаются экземпляры. Затем выполняются необходимые действия. Если действие перенаправляет поток в другой контроллер/действие, диспетчер контроллера запускается заново. Для лучшей иллюстрации этого пример ниже демонстрирует приблизительный процесс, выполняемый в Phalcon\Mvc\Dispatcher:
//Dispatch loop
while (!$finished) {
$finished = true;
$controllerClass = $controllerName . "Controller";
//Instantiating the controller class via autoloaders
$controller = new $controllerClass();
// Execute the action
call_user_func_array(array($controller, $actionName . "Action"), $params);
// '$finished' should be reloaded to check if the flow
// was forwarded to another controller
$finished = true;
}
Приведённый выше код не содержит валидаций, фильтров и дополнительных проверок, но он демонстрирует обычный поток работы в диспетчере.
События цикла диспетчеризации
Phalcon\Mvc\Dispatcher может отправлять события в EventsManager, если он присутствует. События вызываются с типом «dispatch». Некоторые события, возвращающие false, могут остановить активную операцию. Поддерживаются следующие события:
| Имя события | Вызвано | Может остановить операцию? | Вызывается на |
|---|---|---|---|
| beforeDispatchLoop | Вызывается перед входом в цикл диспетчеризации. На этом этапе диспетчер не знает, существуют ли контроллер или действия для выполнения. Диспетчер знает только информацию, переданную маршрутизатором. | Да | Слушатели |
| beforeDispatch | Вызывается после входа в цикл диспетчеризации. На этом этапе диспетчер не знает, существуют ли контроллер или действия для выполнения. Диспетчер знает только информацию, переданную маршрутизатором. | Да | Слушатели |
| beforeExecuteRoute | Вызывается перед выполнением метода контроллера/действия. На этом этапе диспетчер инициализировал контроллер и знает, существует ли действие. | Да | Слушатели/Контроллеры |
| initialize | Позволяет глобально инициализировать контроллер в запросе | Нет | Контроллеры |
| afterExecuteRoute | Вызывается после выполнения метода контроллера/действия. Поскольку операция не может быть остановлена, используйте это событие только для завершения работы после выполнения действия. | Нет | Слушатели/Контроллеры |
| beforeNotFoundAction | Вызывается, когда действие не найдено в контроллере | Да | Слушатели |
| beforeException | Вызывается перед тем, как диспетчер выбросит исключение | Да | Слушатели |
| afterDispatch | Вызывается после выполнения метода контроллера/действия. Поскольку операция не может быть остановлена, используйте это событие только для завершения работы после выполнения действия. | Да | Слушатели |
| afterDispatchLoop | Вызывается после выхода из цикла диспетчеризации | Нет | Слушатели |
В руководстве INVO показано, как использовать события диспетчеризации, реализуя фильтр безопасности с Acl
Следующий пример демонстрирует, как прикрепить слушателей к этому компоненту:
use Phalcon\Mvc\Dispatcher as MvcDispatcher,
Phalcon\Events\Manager as EventsManager;
$di->set('dispatcher', function(){
//Create an event manager
$eventsManager = new EventsManager();
//Attach a listener for type "dispatch"
$eventsManager->attach("dispatch", function($event, $dispatcher) {
//...
});
$dispatcher = new MvcDispatcher();
//Bind the eventsManager to the view component
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
}, true);
Созданный экземпляр контроллера автоматически действует как слушатель событий диспетчеризации, поэтому вы можете реализовать методы в качестве обратных вызовов:
class PostsController extends \Phalcon\Mvc\Controller
{
public function beforeExecuteRoute($dispatcher)
{
// Executed before every found action
}
public function afterExecuteRoute($dispatcher)
{
// Executed after every found action
}
}
Перенаправление на другие действия
Цикл диспетчеризации позволяет нам перенаправить поток выполнения на другой контроллер/действие. Это очень полезно для проверки доступа пользователя к определённым параметрам, перенаправления пользователей на другие страницы или просто повторного использования кода.
class PostsController extends \Phalcon\Mvc\Controller
{
public function indexAction()
{
}
public function saveAction($year, $postTitle)
{
// .. store some product and forward the user
// Forward flow to the index action
$this->dispatcher->forward(array(
"controller" => "post",
"action" => "index"
));
}
}
Помните, что «перенаправление» не то же самое, что HTTP-перенаправление. Хотя они, по-видимому, приводят к одному результату. «Перенаправление» не перезагружает текущую страницу, всё перенаправление происходит в рамках одного запроса, в то время как HTTP-перенаправление требует двух запросов для завершения процесса.
Дополнительные примеры перенаправления:
// Forward flow to another action in the current controller
$this->dispatcher->forward(array(
"action" => "search"
));
// Forward flow to another action in the current controller
// passing parameters
$this->dispatcher->forward(array(
"action" => "search",
"params" => array(1, 2, 3)
));
Действие перенаправления принимает следующие параметры:
| Параметр | Описание |
|---|---|
| controller | Имя действительного контроллера для перенаправления. |
| action | Имя действительного действия для перенаправления. |
| params | Массив параметров для действия |
| namespace | Действительное имя пространства имён, к которому принадлежит контроллер |
Подготовка параметров
Благодаря точкам подключения, предоставленным Phalcon\Mvc\Dispatcher, вы можете легко адаптировать своё приложение к любой схеме URL:
Например, вы хотите, чтобы ваши URL выглядели так: http://example.com/controller/key1/value1/key2/value
Параметры по умолчанию передаются в действия так, как они поступают в URL. Вы можете преобразовать их в желаемую схему:
use Phalcon\Dispatcher,
Phalcon\Mvc\Dispatcher as MvcDispatcher,
Phalcon\Events\Manager as EventsManager;
$di->set('dispatcher', function() {
//Create an EventsManager
$eventsManager = new EventsManager();
//Attach a listener
$eventsManager->attach("dispatch:beforeDispatchLoop", function($event, $dispatcher) {
$keyParams = array();
$params = $dispatcher->getParams();
//Use odd parameters as keys and even as values
foreach ($params as $number => $value) {
if ($number & 1) {
$keyParams[$params[$number - 1]] = $value;
}
}
//Override parameters
$dispatcher->setParams($keyParams);
});
$dispatcher = new MvcDispatcher();
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
});
Если желаемая схема: http://example.com/controller/key1:value1/key2:value, необходим следующий код:
use Phalcon\Dispatcher,
Phalcon\Mvc\Dispatcher as MvcDispatcher,
Phalcon\Events\Manager as EventsManager;
$di->set('dispatcher', function() {
//Create an EventsManager
$eventsManager = new EventsManager();
//Attach a listener
$eventsManager->attach("dispatch:beforeDispatchLoop", function($event, $dispatcher) {
$keyParams = array();
$params = $dispatcher->getParams();
//Explode each parameter as key,value pairs
foreach ($params as $number => $value) {
$parts = explode(':', $value);
$keyParams[$parts[0]] = $parts[1];
}
//Override parameters
$dispatcher->setParams($keyParams);
});
$dispatcher = new MvcDispatcher();
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
});
Получение параметров
Когда маршрут предоставляет именованные параметры, вы можете получить их в контроллере, представлении или любом другом компоненте, который расширяет Phalcon\DI\Injectable.
class PostsController extends \Phalcon\Mvc\Controller
{
public function indexAction()
{
}
public function saveAction()
{
// Get the post's title passed in the URL as parameter
// or prepared in an event
$title = $this->dispatcher->getParam("title");
// Get the post's year passed in the URL as parameter
// or prepared in an event also filtering it
$year = $this->dispatcher->getParam("year", "int");
}
}
Подготовка действий
Вы также можете определить произвольную схему для действий до их диспетчеризации.
Приведение имён действий к верблюжью нотации
Если исходный URL: http://example.com/admin/products/show-latest-products, и, например, вы хотите привести «show-latest-products» к «ShowLatestProducts», необходим следующий код:
use Phalcon\Text,
Phalcon\Mvc\Dispatcher as MvcDispatcher,
Phalcon\Events\Manager as EventsManager;
$di->set('dispatcher', function() {
//Create an EventsManager
$eventsManager = new EventsManager();
//Camelize actions
$eventsManager->attach("dispatch:beforeDispatchLoop", function($event, $dispatcher) {
$dispatcher->setActionName(Text::camelize($dispatcher->getActionName()));
});
$dispatcher = new MvcDispatcher();
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
});
Удаление устаревших расширений
Если исходный URL всегда содержит расширение «.php»:
http://example.com/admin/products/show-latest-products.php http://example.com/admin/products/index.php
Вы можете удалить его перед диспетчеризацией комбинации контроллер/действие:
use Phalcon\Mvc\Dispatcher as MvcDispatcher,
Phalcon\Events\Manager as EventsManager;
$di->set('dispatcher', function() {
//Create an EventsManager
$eventsManager = new EventsManager();
//Remove extension before dispatch
$eventsManager->attach("dispatch:beforeDispatchLoop", function($event, $dispatcher) {
//Remove extension
$action = preg_replace('/\.php$/', '', $dispatcher->getActionName());
//Override action
$dispatcher->setActionName($action);
});
$dispatcher = new MvcDispatcher();
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
});
Ввод экземпляров моделей
В этом примере разработчик хочет проверить параметры, которые получит действие, чтобы динамически вводить экземпляры моделей.
Контроллер выглядит следующим образом:
class PostsController extends \Phalcon\Mvc\Controller
{
/**
* Shows posts
*
* @param \Posts $post
*/
public function showAction(Posts $post)
{
$this->view->post = $post;
}
}
Метод «showAction» получает экземпляр модели Posts. Разработчик может проверить это перед диспетчеризацией действия, подготовив параметр соответственно:
use Phalcon\Text,
Phalcon\Mvc\Dispatcher as MvcDispatcher,
Phalcon\Events\Manager as EventsManager;
$di->set('dispatcher', function() {
//Create an EventsManager
$eventsManager = new EventsManager();
$eventsManager->attach("dispatch:beforeDispatchLoop", function($event, $dispatcher) {
//Possible controller class name
$controllerName = Text::camelize($dispatcher->getControllerName()) . 'Controller';
//Possible method name
$actionName = $dispatcher->getActionName() . 'Action';
try {
//Get the reflection for the method to be executed
$reflection = new \ReflectionMethod($controllerName, $actionName);
//Check parameters
foreach ($reflection->getParameters() as $parameter) {
//Get the expected model name
$className = $parameter->getClass()->name;
//Check if the parameter expects a model instance
if (is_subclass_of($className, 'Phalcon\Mvc\Model')) {
$model = $className::findFirstById($dispatcher->getParams()[0]);
//Override the parameters by the model instance
$dispatcher->setParams(array($model));
}
}
} catch (\Exception $e) {
//An exception has occurred, maybe the class or action does not exist?
}
});
$dispatcher = new MvcDispatcher();
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
});
Приведенный выше пример упрощён для учебных целей. Разработчик может улучшить его, чтобы вводить любые типы зависимостей или моделей в действия перед их выполнением.
Обработка исключений "Не найдено"
Используя EventsManager, можно вставить точку подключения перед тем, как диспетчер выбросит исключение, когда комбинация контроллер/действие не найдена:
use Phalcon\Dispatcher,
Phalcon\Mvc\Dispatcher as MvcDispatcher,
Phalcon\Events\Manager as EventsManager,
Phalcon\Mvc\Dispatcher\Exception as DispatchException;
$di->set('dispatcher', function() {
//Create an EventsManager
$eventsManager = new EventsManager();
//Attach a listener
$eventsManager->attach("dispatch:beforeException", function($event, $dispatcher, $exception) {
//Handle 404 exceptions
if ($exception instanceof DispatchException) {
$dispatcher->forward(array(
'controller' => 'index',
'action' => 'show404'
));
return false;
}
//Alternative way, controller or action doesn't exist
if ($event->getType() == 'beforeException') {
switch ($exception->getCode()) {
case \Phalcon\Dispatcher::EXCEPTION_HANDLER_NOT_FOUND:
case \Phalcon\Dispatcher::EXCEPTION_ACTION_NOT_FOUND:
$dispatcher->forward(array(
'controller' => 'index',
'action' => 'show404'
));
return false;
}
}
});
$dispatcher = new \Phalcon\Mvc\Dispatcher();
//Bind the EventsManager to the dispatcher
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
}, true);
Конечно, этот метод можно перенести в отдельные классы плагинов, что позволит более чем одному классу принимать действия при возникновении исключения в цикле диспетчеризации:
use Phalcon\Mvc\Dispatcher,
Phalcon\Events\Event,
Phalcon\Mvc\Dispatcher\Exception as DispatchException;
class ExceptionsPlugin
{
public function beforeException(Event $event, Dispatcher $dispatcher, $exception)
{
//Handle 404 exceptions
if ($exception instanceof DispatchException) {
$dispatcher->forward(array(
'controller' => 'index',
'action' => 'show404'
));
return false;
}
//Handle other exceptions
$dispatcher->forward(array(
'controller' => 'index',
'action' => 'show503'
));
return false;
}
}
Только исключения, созданные диспетчером, и исключения, созданные в выполняемом действии, уведомляются о событиях «beforeException». Исключение, созданные в слушателях или событиях контроллеров, перенаправляются в последнюю конструкцию try/catch.
Реализация собственного диспетчера
Интерфейс Phalcon\Mvc\DispatcherInterface должен быть реализован для создания собственного диспетчера, заменяющего тот, что предоставлен Phalcon.
© 2011–2016 Phalcon Framework Team
Licensed under the Creative Commons Attribution License 3.0.
https://docs.phalconphp.com/en/2.0.0/reference/dispatching.html