Spec-Zone.ru › Phalcon 3

Управление контроллерами

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(
        [
            $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”. Некоторые события при возвращении значения boolean false могут остановить активную операцию. Поддерживаются следующие события:

В руководстве INVO показано, как использовать события обработки запроса для реализации фильтра безопасности с помощью Acl

Следующий пример демонстрирует, как прикрепить слушатели к этому компоненту:

use Phalcon\Mvc\Dispatcher as MvcDispatcher;
use Phalcon\Events\Event;
use 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 $event, $dispatcher) {
                // ...
            }
        );

        $dispatcher = new MvcDispatcher();

        // Bind the eventsManager to the view component
        $dispatcher->setEventsManager($eventsManager);

        return $dispatcher;
    },
    true
);

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

use Phalcon\Mvc\Controller;
use Phalcon\Mvc\Dispatcher;

class PostsController extends Controller
{
    public function beforeExecuteRoute(Dispatcher $dispatcher)
    {
        // Executed before every found action
    }

    public function afterExecuteRoute(Dispatcher $dispatcher)
    {
        // Executed after every found action
    }
}

Примечание

Методы слушателей событий принимают объект Phalcon\Events\Event в качестве первого параметра — методы в контроллерах этого не делают.

Перенаправление на другие действия

Цикл обработки запроса позволяет перенаправлять поток выполнения в другой контроллер/действие. Это очень полезно для проверки доступа пользователя к определённым опциям, перенаправления пользователей на другие страницы или просто повторного использования кода.

use Phalcon\Mvc\Controller;

class PostsController extends 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(
            [
                "controller" => "posts",
                "action"     => "index",
            ]
        );
    }
}

Обратите внимание, что «перенаправление» не то же самое, что HTTP-перенаправление. Хотя они, по-видимому, дают одинаковый результат. «Перенаправление» не перезагружает текущую страницу, всё перенаправление происходит в рамках одного запроса, в то время как HTTP-перенаправление требует двух запросов для завершения процесса.

Дополнительные примеры перенаправлений:

// Forward flow to another action in the current controller
$this->dispatcher->forward(
    [
        "action" => "search"
    ]
);

// Forward flow to another action in the current controller
// passing parameters
$this->dispatcher->forward(
    [
        "action" => "search",
        "params" => [1, 2, 3]
    ]
);

Действие перенаправления принимает следующие параметры:

Параметр Описание
controller Действительное имя контроллера для перенаправления.
action Действительное имя действия для перенаправления.
params Массив параметров для действия
namespace Действительное имя пространства имён, к которому относится контроллер.

Подготовка параметров

Благодаря точкам подключения, предоставляемым Phalcon\Mvc\Dispatcher, вы можете легко адаптировать своё приложение к любой схеме URL:

Например, вы хотите, чтобы ваши URL выглядели так: http://example.com/controller/key1/value1/key2/value

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

use Phalcon\Dispatcher;
use Phalcon\Mvc\Dispatcher as MvcDispatcher;
use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;

$di->set(
    "dispatcher",
    function () {
        // Create an EventsManager
        $eventsManager = new EventsManager();

        // Attach a listener
        $eventsManager->attach(
            "dispatch:beforeDispatchLoop",
            function (Event $event, $dispatcher) {
                $params = $dispatcher->getParams();

                $keyParams = [];

                // Use odd parameters as keys and even as values
                foreach ($params as $i => $value) {
                    if ($i & 1) {
                        // Previous param
                        $key = $params[$i - 1];

                        $keyParams[$key] = $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;
use Phalcon\Mvc\Dispatcher as MvcDispatcher;
use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;

$di->set(
    "dispatcher",
    function () {
        // Create an EventsManager
        $eventsManager = new EventsManager();

        // Attach a listener
        $eventsManager->attach(
            "dispatch:beforeDispatchLoop",
            function (Event $event, $dispatcher) {
                $params = $dispatcher->getParams();

                $keyParams = [];

                // 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.

use Phalcon\Mvc\Controller;

class PostsController extends 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");

        // ...
    }
}

Подготовка действий

Вы также можете определить произвольную схему для действий перед их обработкой.

Преобразование имён действий в camelCase

Если исходный URL: http://example.com/admin/products/show-latest-products, и, например, вы хотите преобразовать ‘show-latest-products’ в ‘ShowLatestProducts’, необходим следующий код:

use Phalcon\Text;
use Phalcon\Mvc\Dispatcher as MvcDispatcher;
use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;

$di->set(
    "dispatcher",
    function () {
        // Create an EventsManager
        $eventsManager = new EventsManager();

        // Camelize actions
        $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

Вы можете удалить его перед обработкой сочетания контроллер/действие:

use Phalcon\Mvc\Dispatcher as MvcDispatcher;
use Phalcon\Events\Event;
use 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 $event, $dispatcher) {
                $action = $dispatcher->getActionName();

                // Remove extension
                $action = preg_replace("/\.php$/", "", $action);

                // Override action
                $dispatcher->setActionName($action);
            }
        );

        $dispatcher = new MvcDispatcher();

        $dispatcher->setEventsManager($eventsManager);

        return $dispatcher;
    }
);

Вставка экземпляров моделей

В этом примере разработчик хочет проверить параметры, которые получит действие, чтобы динамически вставить экземпляры моделей.

Контроллер выглядит так:

use Phalcon\Mvc\Controller;

class PostsController extends Controller
{
    /**
     * Shows posts
     *
     * @param \Posts $post
     */
    public function showAction(Posts $post)
    {
        $this->view->post = $post;
    }
}

Метод «showAction» получает экземпляр модели Posts, разработчик мог проверить это перед обработкой действия, подготовив параметр соответственно:

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 () {
        // Create an EventsManager
        $eventsManager = new EventsManager();

        $eventsManager->attach(
            "dispatch:beforeDispatchLoop",
            function (Event $event, $dispatcher) {
                // Possible controller class name
                $controllerName = $dispatcher->getControllerClass();

                // Possible method name
                $actionName = $dispatcher->getActiveMethod();

                try {
                    // Get the reflection for the method to be executed
                    $reflection = new ReflectionMethod($controllerName, $actionName);

                    $parameters = $reflection->getParameters();

                    // Check parameters
                    foreach ($parameters as $parameter) {
                        // Get the expected model name
                        $className = $parameter->getClass()->name;

                        // Check if the parameter expects a model instance
                        if (is_subclass_of($className, Model::class)) {
                            $model = $className::findFirstById($dispatcher->getParams()[0]);

                            // Override the parameters by the model instance
                            $dispatcher->setParams([$model]);
                        }
                    }
                } catch (Exception $e) {
                    // An exception has occurred, maybe the class or action does not exist?
                }
            }
        );

        $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;
Поскольку объект Binder использует внутренне API рефлексии, который может быть ресурсоёмким, существует возможность задать кэш. Это можно сделать, используя второй аргумент в setModelBinder(), который также может принимать имя сервиса или просто передав экземпляр кэша в конструктор Binder.

Он также вводит новый интерфейс 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
{
    /**
     * Shows posts
     *
     * @param Posts $post
     */
    public function showAction(Posts $post)
    {
        $this->view->post = $post;
    }
}
В настоящее время диспетчер будет использовать только первичный ключ моделей для выполнения findFirst(). Пример маршрута для вышеуказанного был бы /posts/show/{1}

Обработка исключений "Не найдено"

С помощью EventsManager можно вставить точку подключения перед тем, как диспетчер выбросит исключение, когда сочетание контроллер/действие не найдено:

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 () {
        // Create an EventsManager
        $eventsManager = new EventsManager();

        // Attach a listener
        $eventsManager->attach(
            "dispatch:beforeException",
            function (Event $event, $dispatcher, Exception $exception) {
                // Handle 404 exceptions
                if ($exception instanceof DispatchException) {
                    $dispatcher->forward(
                        [
                            "controller" => "index",
                            "action"     => "show404",
                        ]
                    );

                    return false;
                }

                // Alternative way, controller or action doesn't exist
                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();

        // Bind the EventsManager to the dispatcher
        $dispatcher->setEventsManager($eventsManager);

        return $dispatcher;
    }
);

Конечно, этот метод может быть перемещён в независимые классы плагинов, позволяя нескольким классам принимать действия, когда в цикле обработки запроса возникает исключение:

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)
    {
        // Default error action
        $action = "show503";

        // Handle 404 exceptions
        if ($exception instanceof DispatchException) {
            $action = "show404";
        }

        $dispatcher->forward(
            [
                "controller" => "index",
                "action"     => $action,
            ]
        );

        return false;
    }
}
Оповещения о событиях ‘beforeException’ отправляются только для исключений, созданных диспетчером, и исключений, созданных в исполняемом действии. Исключения, созданные в слушателях или событиях контроллера, перенаправляются в последнюю структуру try/catch.

Реализация собственного диспетчера

Интерфейс Phalcon\Mvc\DispatcherInterface необходимо реализовать для создания собственного диспетчера, заменяя предоставляемый Phalcon.

© 2011–2017 Phalcon Framework Team
Licensed under the Creative Commons Attribution License 3.0.
https://docs.phalconphp.com/en/latest/reference/dispatching.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API