04 Диспетчеризация
Контроллеры диспетчеризации
Phalcon\Mvc\Dispatcher является компонентом, ответственным за создание экземпляров контроллеров и выполнением необходимых действий над ними в приложении MVC. Понимание его работы и возможностей помогает нам получить больше от услуг, предоставляемых инфраструктурой.
Цикл диспетчеризации
Это важный процесс, который имеет много общего с самим потоком MVC, особенно с частью контроллера. Работа происходит внутри диспетчера контроллера. Файлы контроллера считываются, загружаются и создаются экземпляры. Затем выполняются необходимые действия. Если действие перенаправляет поток на другой контроллер / действие, диспетчер контроллера запускается снова. Чтобы лучше проиллюстрировать это, в следующем примере показан приблизительно процесс, выполняемый в Phalcon\Mvc\Dispatcher:
<?php // Цикл диспетчеризации while (!$finished) { $finished = true; $controllerClass = $controllerName . 'Controller'; // Создание экземпляра класса контроллера с помощью автозагрузчиков $controller = new $controllerClass(); // Выполнить действие call_user_func_array( [ $controller, $actionName . 'Action' ], $params ); // '$finished' должна быть перезагружена, чтобы проверить, был ли поток передан другому контроллеру $finished = true; }
В приведенном выше коде отсутствуют валидации, фильтры и дополнительные проверки, но он демонстрирует нормальный ход работы диспетчера.
События Цикла Диспетчеризации
Phalcon\Mvc\Dispatcher может отправлять события EventsManager , если он присутствует. События запускаются с помощью типа dispatch
. Некоторые события при возврате boolean false
могут остановить активную операцию. Поддерживаются следующие события:
Имя события | Срабатывает | Может остановить работу? | Запускает |
---|---|---|---|
beforeDispatchLoop | Срабатывает перед входом в цикл диспетчеризации. На этом этапе диспетчер не знает, существует ли контроллер или выполняемые действия. Диспетчер знает только информацию, передаваемую маршрутизатором. | да | Слушатели |
beforeDispatch | Срабатывает после входа в цикл диспетчеризации. На этом этапе диспетчер не знает, существует ли контроллер или выполняемые действия. Диспетчер знает только информацию, передаваемую маршрутизатором. | да | Слушатели |
beforeExecuteRoute | Срабатывает перед выполнением контроллера / метода действия. На этом этапе диспетчер инициализирует контроллер и знает, существует ли действие. | да | Слушатели / Контроллеры |
initialize | Разрешить глобально инициализировать контроллер в запросе | нет | Контроллеры |
afterExecuteRoute | Срабатывает после выполнения контроллера / метода действия. Поскольку операция не может быть остановлена, используйте это событие только для очистки после выполнения действия | нет | Слушатели / Контроллеры |
beforeNotFoundAction | Срабатывает, когда действие не найдено в контроллере | да | Слушатели |
beforeException | Срабатывает до того, как диспетчер выдаст любое исключение | да | Слушатели |
afterDispatch | Срабатывает после выполнения контроллера / метода действия. Поскольку операция не может быть остановлена, используйте это событие только для очистки после выполнения действия | да | Слушатели |
afterDispatchLoop | Срабатывает после выхода из цикла отправки | нет | Слушатели |
afterBinding | Срабатывает после привязки моделей, но перед выполнением маршрута | да | Слушатели / Контроллеры |
Учебник INVO показывает, как использовать преимущества диспетчеризации событий, реализующих фильтр безопасности с Acl.
В следующем примере показано, как присоединить прослушиватели к этому компоненту:
<?php use Phalcon\Mvc\Dispatcher as MvcDispatcher; use Phalcon\Events\Event; use Phalcon\Events\Manager as EventsManager; $di->set( 'dispatcher', function () { // Создание event-менеджер $eventsManager = new EventsManager(); // Присоединение прослушивателя для типа 'dispatch' $eventsManager->attach( 'dispatch', function (Event $event, $dispatcher) { // ... } ); $dispatcher = new MvcDispatcher(); // Привязать eventManager к компоненту вида $dispatcher->setEventsManager($eventsManager); return $dispatcher; }, true );
Созданный экземпляр контроллера автоматически выступает в качестве прослушивателя событий dispatch, поэтому можно реализовать методы в качестве обратных вызовов:
<?php use Phalcon\Mvc\Controller; use Phalcon\Mvc\Dispatcher; class PostsController extends Controller { public function beforeExecuteRoute(Dispatcher $dispatcher) { // Выполняется перед каждым найденным действием } public function afterExecuteRoute(Dispatcher $dispatcher) { // Выполняется после каждого действия } }
Переадресация на другие действия
Цикл диспетчеризации позволяет перенаправить поток выполнения на другой контроллер / действие. Это очень полезно, чтобы проверить, если пользователь может получить доступ к определенной функции, перенаправлять пользователей на другие экраны или просто повторно использовать код.
<?php use Phalcon\Mvc\Controller; class PostsController extends Controller { public function indexAction() { } public function saveAction($year, $postTitle) { // ... Сохранить некоторый продукт и перенаправьтте пользователя // Прямой поток к действию индекса $this->dispatcher->forward( [ 'controller' => 'posts', 'action' => 'index', ] ); } }
Имейте в виду, что создание forward
(дальше) -это не то же самое, что перенаправление HTTP. Хотя они, видимо, получили тот же результат. forward
не перезагружает текущую страницу, все перенаправление происходит в одном запросе, в то время как перенаправление HTTP требует двух запросов для завершения процесса.
Дополнительные примеры пересылки:
<?php // Прямой поток к другому действию в текущем контроллере $this->dispatcher->forward( [ 'action' => 'search' ] ); // Прямой поток к другому действию в текущем контроллере // передача параметров $this->dispatcher->forward( [ 'action' => 'search', 'params' => [1, 2, 3] ] );
Действие forward
принимает следующие параметры:
Параметр | Описание |
---|---|
controller |
Действительное имя контроллера для пересылки. |
action |
Действительное имя действия для пересылки. |
params |
Массив параметров для действия. |
namespace |
Действительное имя пространства имен, в котором находится контроллер. |
Использование событий диспетчера
Вы можете использовать событие диспетчера dispatcher::beforeForward
, чтобы изменить модули и перенаправить их проще и «чище»:
<?php use Phalcon\Di; use Phalcon\Events\Manager; use Phalcon\Mvc\Dispatcher; use Phalcon\Events\Event; $di = new Di(); $modules = [ 'backend' => [ 'className' => 'App\Backend\Bootstrap', 'path' => '/app/Modules/Backend/Bootstrap.php', 'metadata' => [ 'controllersNamespace' => 'App\Backend\Controllers', ], ], ]; $manager = new Manager(); $manager->attach( 'dispatch:beforeForward', function (Event $event, Dispatcher $dispatcher, array $forward) use ($modules) { $metadata = $modules[$forward['module']]['metadata']; $dispatcher->setModuleName($forward['module']); $dispatcher->setNamespaceName($metadata['controllersNamespace']); } ); $dispatcher = new Dispatcher(); $dispatcher->setDI($di); $dispatcher->setEventsManager($manager); $di->set('dispatcher', $dispatcher); $dispatcher->forward( [ 'module' => 'backend', 'controller' => 'posts', 'action' => 'index', ] ); echo $dispatcher->getModuleName(); // будет отображаться правильно 'backend'
Подготовка параметров
Благодаря точкам перехвата, предоставленным Phalcon\Mvc\Dispatcherr, вы можете легко адаптировать приложение к любой схеме URL; то есть вы можете захотеть, чтобы ваши URL-адреса выглядели следующим образом: http://example.com/controller/key1/value1/key2/value
. Поскольку параметры передаются с порядком их определения в URL-адресе для действий, вы можете преобразовать их, чтобы принять желаемую схему:
<?php use Phalcon\Dispatcher; use Phalcon\Mvc\Dispatcher as MvcDispatcher; use Phalcon\Events\Event; use Phalcon\Events\Manager as EventsManager; $di->set( 'dispatcher', function () { // Создать EventManager $eventsManager = new EventsManager(); // Прикрепите слушателя $eventsManager->attach( 'dispatch:beforeDispatchLoop', function (Event $event, $dispatcher) { $params = $dispatcher->getParams(); $keyParams = []; // Используйте нечетные параметры как ключи и даже значения foreach ($params as $i => $value) { if ($i & 1) { // Previous param $key = $params[$i - 1]; $keyParams[$key] = $value; } } // Параметры переопределения $dispatcher->setParams($keyParams); } ); $dispatcher = new MvcDispatcher(); $dispatcher->setEventsManager($eventsManager); return $dispatcher; } );
Если желаемая схема: http://example.com/controller/key1:value1/key2:value
, необходим следующий код:
<?php use Phalcon\Dispatcher; use Phalcon\Mvc\Dispatcher as MvcDispatcher; use Phalcon\Events\Event; use Phalcon\Events\Manager as EventsManager; $di->set( 'dispatcher', function () { // Создать EventManager $eventsManager = new EventsManager(); // Прикрепить слушателя $eventsManager->attach( 'dispatch:beforeDispatchLoop', function (Event $event, $dispatcher) { $params = $dispatcher->getParams(); $keyParams = []; // Разнесение каждого параметра по парам ключ, значение foreach ($params as $number => $value) { $parts = explode(':', $value); $keyParams[$parts[0]] = $parts[1]; } // Переопределение параметров $dispatcher->setParams($keyParams); } ); $dispatcher = new MvcDispatcher(); $dispatcher->setEventsManager($eventsManager); return $dispatcher; } );
Получение Параметров
Когда маршрут предоставляет именованные параметры, вы можете получить их в контроллере, представлении или любом другом компоненте, который расширяет Phalcon\Di\Injectable.
<?php use Phalcon\Mvc\Controller; class PostsController extends Controller { public function indexAction() { } public function saveAction() { // Получить заголовок записи, переданный в URL // как параметр или подготовленный в событии $title = $this->dispatcher->getParam('title'); // Получить год записи, переданный в URL как параметр // или подготовленный в событии, также отфильтровав его $year = $this->dispatcher->getParam('year', 'int'); // ... } }
Подготовка действий
Вы также можете определить произвольную схему действий перед before
циклом отправки.
Имена действий в верблюжем стиле.
Если исходный URL-адрес: http://example.com/admin/products/show-latest-products
, и, например, вы хотите, параметры его отбражались в верблюжем стиле show-latest-products
как ShowLatestProducts
, то необходим следующий код:
<?php use Phalcon\Text; use Phalcon\Mvc\Dispatcher as MvcDispatcher; use Phalcon\Events\Event; use Phalcon\Events\Manager as EventsManager; $di->set( 'dispatcher', function () { // Создать EventManager $eventsManager = new EventsManager(); // действие Camelize $eventsManager->attach( 'dispatch:beforeDispatchLoop', function (Event $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
Вы можете удалить его перед отправкой контроллера / комбинации действий:
<?php use Phalcon\Mvc\Dispatcher as MvcDispatcher; use Phalcon\Events\Event; use Phalcon\Events\Manager as EventsManager; $di->set( 'dispatcher', function () { // Создать EventManager $eventsManager = new EventsManager(); // Удалите расширение перед отправкой $eventsManager->attach( 'dispatch:beforeDispatchLoop', function (Event $event, $dispatcher) { $action = $dispatcher->getActionName(); // Удалить расширение $action = preg_replace('/\.php$/', '', $action); // Заменить действие $dispatcher->setActionName($action); } ); $dispatcher = new MvcDispatcher(); $dispatcher->setEventsManager($eventsManager); return $dispatcher; } );
Внедрение экземпляров модели
В этом примере разработчик хочет проверить параметры, которые получит действие, чтобы динамически внедрить экземпляры модели.
Контроллер выглядит так:
<?php use Phalcon\Mvc\Controller; class PostsController extends Controller { /** * Shows posts * * @param \Posts $post */ public function showAction(Posts $post) { $this->view->post = $post; } }
Метод showAction
получает экземпляр модели \Posts
, разработчик может проверить это перед отправкой действия, подготавливающего параметр соответствующим образом:
<?php use \Exception; use Phalcon\Mvc\Model; use Phalcon\Mvc\Dispatcher as MvcDispatcher; use Phalcon\Events\Event; use Phalcon\Events\Manager as EventsManager; use \ReflectionMethod; $di->set( 'dispatcher', function () { // Создание EventsManager $eventsManager = new EventsManager(); $eventsManager->attach( 'dispatch:beforeDispatchLoop', function (Event $event, $dispatcher) { // Возможное имя класса контроллера $controllerName = $dispatcher->getControllerClass(); // Possible method name $actionName = $dispatcher->getActiveMethod(); try { // Получить отражение для метода, который будет выполняться $reflection = new ReflectionMethod($controllerName, $actionName); $parameters = $reflection->getParameters(); // Проверить параметры foreach ($parameters as $parameter) { // Получить ожидаемое имя модели $className = $parameter->getClass()->name; // Проверить, ожидает ли параметр экземпляр модели if (is_subclass_of($className, Model::class)) { $model = $className::findFirstById($dispatcher->getParams()[0]); // Переопределение параметров экземпляром модели $dispatcher->setParams([$model]); } } } catch (Exception $e) { // Произошло исключение, возможно, класс или действие не существует? } } ); $dispatcher = new MvcDispatcher(); $dispatcher->setEventsManager($eventsManager); return $dispatcher; } );
Приведенный выше пример был упрощен. Разработчик может улучшить его, чтобы внедрить любой тип зависимости или модели в действия перед выполнением.
Начиная с версии 3.1.x диспетчер также имеет возможность обрабатывать это внутренне для всех моделей, переданных в действие контроллера, используя Phalcon\Mvc\Model\Binder.
use Phalcon\Mvc\Dispatcher; use Phalcon\Mvc\Model\Binder; $dispatcher = new Dispatcher(); $dispatcher->setModelBinder(new Binder()); return $dispatcher;
Он также вводит новый интерфейс Phalcon\Mvc\Model\Binder\BindableInterface, который позволяет вам определять модели, связанные с контроллерами, для привязки моделей к базовым контроллерам.
Например, у вас есть базовый CrudController
, из которого выходит ваш PostsController
. Ваш CrudController
выглядит примерно так:
use Phalcon\Mvc\Controller; use Phalcon\Mvc\Model; class CrudController extends Controller { /** * Show action * * @param Model $model */ public function showAction(Model $model) { $this->view->model = $model; } }
В вашем PostsController вам необходимо определить, с какой моделью связан контроллер. Это делается путем реализации Phalcon\Mvc\Model\Binder\BindableInterface , который добавит метод getModelName()
, из которого вы можете вернуть имя модели. Он может возвращать строку только с одним именем модели или ассоциативным массивом, где ключ - имя параметра.
use Phalcon\Mvc\Model\Binder\BindableInterface; use Models\Posts; class PostsController extends CrudController implements BindableInterface { public static function getModelName() { return Posts::class; } }
Объявив модель, связанную с PostsController
, связыватель может проверить контроллер для метода getModelName()
перед передачей определенной модели в родительское действие show
.
Если в структуре проекта не используется Родительский контроллер, можно, конечно, привязать модель непосредственно к действию контроллера:
use Phalcon\Mvc\Controller; use Models\Posts; class PostsController extends Controller { /** * Показывает сообщения * * @param Posts $post */ public function showAction(Posts $post) { $this->view->post = $post; } }
Обработка Не Найденных Исключений
С помощью EventsManager можно вставить точку подключения до того, как диспетчер выдаст исключение, если комбинация контроллер / действие не найдена:
<?php use Exception; use Phalcon\Dispatcher; use Phalcon\Mvc\Dispatcher as MvcDispatcher; use Phalcon\Events\Event; use Phalcon\Events\Manager as EventsManager; use Phalcon\Mvc\Dispatcher\Exception as DispatchException; $di->setShared( 'dispatcher', function () { // Создание EventsManager $eventsManager = new EventsManager(); // Прикрепить слушатель $eventsManager->attach( 'dispatch:beforeException', function (Event $event, $dispatcher, Exception $exception) { // Обрабатывать 404 исключения if ($exception instanceof DispatchException) { $dispatcher->forward( [ 'controller' => 'index', 'action' => 'show404', ] ); return false; } // Альтернативного способа, контроллера или действия не существует switch ($exception->getCode()) { case Dispatcher::EXCEPTION_HANDLER_NOT_FOUND: case Dispatcher::EXCEPTION_ACTION_NOT_FOUND: $dispatcher->forward( [ 'controller' => 'index', 'action' => 'show404', ] ); return false; } } ); $dispatcher = new MvcDispatcher(); // Привязка EventsManager к диспетчеру $dispatcher->setEventsManager($eventsManager); return $dispatcher; } );
Конечно, этот метод может быть перемещен на независимые классы плагина, позволяя более чем одному классу выполнять действия, когда исключение создается в цикле диспетчеризации:
<?php use Exception; use Phalcon\Events\Event; use Phalcon\Mvc\Dispatcher; use Phalcon\Mvc\Dispatcher\Exception as DispatchException; class ExceptionsPlugin { public function beforeException(Event $event, Dispatcher $dispatcher, Exception $exception) { // Действие ошибка по умолчанию $action = 'show503'; // Обрабатывать 404 исключения if ($exception instanceof DispatchException) { $action = 'show404'; } $dispatcher->forward( [ 'controller' => 'index', 'action' => $action, ] ); return false; } }
Реализация собственного диспетчера
Интерфейс Phalcon\Mvc\DispatcherInterface должен быть реализован для создания вашего собственного диспетчера, заменяющего тот, который предоставляется Phalcon.