Spec-Zone.ru › Phalcon 2

Диспетчеризация контроллеров

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

Spec-Zone.ru

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