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.