Dispatcher Component


    Phalcon\Mvc\Dispatcher is the component responsible for instantiating controllers and executing the required actions on them in an MVC application. Understanding its operation and capabilities helps us get more out of the services provided by the framework.

    The Dispatch Loop

    This is an important process that has much to do with the MVC flow itself, especially with the controller part. The work occurs within the controller dispatcher. The controller files are read, loaded, and instantiated. Then the required actions are executed. If an action forwards the flow to another controller/action, the controller dispatcher starts again. To better illustrate this, the following example shows approximately the process performed within Phalcon\Mvc\Dispatcher:

    The code above lacks validations, filters and additional checks, but it demonstrates the normal flow of operation in the dispatcher.

    is able to send events to an EventsManager if it is present. Events are triggered using the type . Some events when returning boolean false could stop the active operation. The following events are supported:

    The tutorial shows how to take advantage of dispatching events implementing a security filter with Acl

    The following example demonstrates how to attach listeners to this component:

    1. <?php
    2. use Phalcon\Mvc\Dispatcher as MvcDispatcher;
    3. use Phalcon\Events\Event;
    4. use Phalcon\Events\Manager as EventsManager;
    5. $di->set(
    6. 'dispatcher',
    7. function () {
    8. // Create an event manager
    9. $eventsManager = new EventsManager();
    10. // Attach a listener for type 'dispatch'
    11. $eventsManager->attach(
    12. 'dispatch',
    13. function (Event $event, $dispatcher) {
    14. // ...
    15. }
    16. );
    17. $dispatcher = new MvcDispatcher();
    18. // Bind the eventsManager to the view component
    19. $dispatcher->setEventsManager($eventsManager);
    20. return $dispatcher;
    21. },
    22. true
    23. );

    An instantiated controller automatically acts as a listener for dispatch events, so you can implement methods as callbacks:

    1. <?php
    2. use Phalcon\Mvc\Controller;
    3. use Phalcon\Mvc\Dispatcher;
    4. class PostsController extends Controller
    5. {
    6. public function beforeExecuteRoute(Dispatcher $dispatcher)
    7. {
    8. // Executed before every found action
    9. }
    10. public function afterExecuteRoute(Dispatcher $dispatcher)
    11. {
    12. // Executed after every found action
    13. }
    14. }

    The dispatch loop allows us to forward the execution flow to another controller/action. This is very useful to check if the user can access to certain options, redirect users to other screens or simply reuse code.

    1. <?php
    2. use Phalcon\Mvc\Controller;
    3. class PostsController extends Controller
    4. {
    5. public function indexAction()
    6. {
    7. }
    8. public function saveAction($year, $postTitle)
    9. {
    10. // ... Store some product and forward the user
    11. // Forward flow to the index action
    12. $this->dispatcher->forward(
    13. [
    14. 'controller' => 'posts',
    15. 'action' => 'index',
    16. ]
    17. );
    18. }
    19. }

    Keep in mind that making a forward is not the same as making a HTTP redirect. Although they apparently got the same result. The forward doesn’t reload the current page, all the redirection occurs in a single request, while the HTTP redirect needs two requests to complete the process.

    More forwarding examples:

    1. <?php
    2. // Forward flow to another action in the current controller
    3. $this->dispatcher->forward(
    4. [
    5. 'action' => 'search',
    6. ]
    7. );
    8. // Forward flow to another action in the current controller
    9. // passing parameters
    10. $this->dispatcher->forward(
    11. [
    12. 'action' => 'search',
    13. 'params' => [1, 2, 3],
    14. ]
    15. );
    ParameterDescription
    controllerA valid controller name to forward to.
    actionA valid action name to forward to.
    paramsAn array of parameters for the action.
    namespaceA valid namespace name where the controller is part of.

    You can use the dispatcher::beforeForward event to change modules and redirect easier and “cleaner”:

    1. <?php
    2. use Phalcon\Di;
    3. use Phalcon\Events\Manager;
    4. use Phalcon\Mvc\Dispatcher;
    5. use Phalcon\Events\Event;
    6. $di = new Di();
    7. $modules = [
    8. 'backend' => [
    9. 'className' => \App\Backend\Bootstrap::class,
    10. 'path' => '/app/Modules/Backend/Bootstrap.php',
    11. 'metadata' => [
    12. 'controllersNamespace' => 'App\Backend\Controllers',
    13. ],
    14. ],
    15. ];
    16. $manager = new Manager();
    17. $manager->attach(
    18. 'dispatch:beforeForward',
    19. function (Event $event, Dispatcher $dispatcher, array $forward) use ($modules) {
    20. $moduleName = $forward['module'];
    21. $metadata = $modules[$moduleName]['metadata'];
    22. $dispatcher->setModuleName($moduleName);
    23. $dispatcher->setNamespaceName(
    24. $metadata['controllersNamespace']
    25. );
    26. }
    27. );
    28. $dispatcher = new Dispatcher();
    29. $dispatcher->setDI($di);
    30. $di->set('dispatcher', $dispatcher);
    31. $dispatcher->forward(
    32. [
    33. 'module' => 'backend',
    34. 'controller' => 'posts',
    35. 'action' => 'index',
    36. ]
    37. );
    38. echo $dispatcher->getModuleName(); // will display properly 'backend'

    Preparing Parameters

    Thanks to the hook points provided by Phalcon\Mvc\Dispatcher you can easily adapt your application to any URL schema; i.e. you might want your URLs look like: . Since parameters are passed with the order that they are defined in the URL to actions, you can transform them to adopt the desired schema:

    If the desired schema is: https://example.com/controller/key1:value1/key2:value, the following code is required:

    1. <?php
    2. use Phalcon\Dispatcher;
    3. use Phalcon\Events\Event;
    4. use Phalcon\Events\Manager as EventsManager;
    5. use Phalcon\Mvc\Dispatcher as MvcDispatcher;
    6. $di->set(
    7. 'dispatcher',
    8. function () {
    9. // Create an EventsManager
    10. $eventsManager = new EventsManager();
    11. // Attach a listener
    12. $eventsManager->attach(
    13. 'dispatch:beforeDispatchLoop',
    14. function (Event $event, $dispatcher) {
    15. $params = $dispatcher->getParams();
    16. $keyParams = [];
    17. // Explode each parameter as key,value pairs
    18. foreach ($params as $param) {
    19. $parts = explode(':', $param);
    20. $key = $parts[0];
    21. $value = $parts[1];
    22. $keyParams[$key] = $value;
    23. }
    24. // Override parameters
    25. $dispatcher->setParams($keyParams);
    26. }
    27. );
    28. $dispatcher = new MvcDispatcher();
    29. $dispatcher->setEventsManager($eventsManager);
    30. return $dispatcher;
    31. }
    32. );

    When a route provides named parameters you can receive them in a controller, a view or any other component that extends .

    1. <?php
    2. use Phalcon\Mvc\Controller;
    3. class PostsController extends Controller
    4. {
    5. public function indexAction()
    6. {
    7. }
    8. public function saveAction()
    9. {
    10. // Get the post's title passed in the URL as parameter
    11. // or prepared in an event
    12. $title = $this->dispatcher->getParam('title');
    13. // Get the post's year passed in the URL as parameter
    14. // or prepared in an event also filtering it
    15. $year = $this->dispatcher->getParam('year', 'int');
    16. // ...
    17. }
    18. }

    Preparing actions

    You can also define an arbitrary schema for actions before in the dispatch loop.

    If the original URL is: , and for example you want to camelize show-latest-products to ShowLatestProducts, the following code is required:

    1. <?php
    2. use Phalcon\Text;
    3. use Phalcon\Mvc\Dispatcher as MvcDispatcher;
    4. use Phalcon\Events\Event;
    5. use Phalcon\Events\Manager as EventsManager;
    6. $di->set(
    7. 'dispatcher',
    8. function () {
    9. // Create an EventsManager
    10. $eventsManager = new EventsManager();
    11. // Camelize actions
    12. $eventsManager->attach(
    13. 'dispatch:beforeDispatchLoop',
    14. function (Event $event, $dispatcher) {
    15. $dispatcher->setActionName(
    16. Text::camelize(
    17. $dispatcher->getActionName()
    18. )
    19. );
    20. }
    21. );
    22. $dispatcher = new MvcDispatcher();
    23. $dispatcher->setEventsManager($eventsManager);
    24. return $dispatcher;
    25. }
    26. );

    If the original URL always contains a .php extension:

    1. https://example.com/admin/products/show-latest-products.php
    2. https://example.com/admin/products/index.php

    You can remove it before dispatch the controller/action combination:

    1. <?php
    2. use Phalcon\Mvc\Dispatcher as MvcDispatcher;
    3. use Phalcon\Events\Event;
    4. use Phalcon\Events\Manager as EventsManager;
    5. $di->set(
    6. 'dispatcher',
    7. function () {
    8. // Create an EventsManager
    9. $eventsManager = new EventsManager();
    10. // Remove extension before dispatch
    11. $eventsManager->attach(
    12. 'dispatch:beforeDispatchLoop',
    13. function (Event $event, $dispatcher) {
    14. $action = $dispatcher->getActionName();
    15. // Remove extension
    16. $action = preg_replace('/\.php$/', '', $action);
    17. // Override action
    18. $dispatcher->setActionName($action);
    19. }
    20. );
    21. $dispatcher = new MvcDispatcher();
    22. $dispatcher->setEventsManager($eventsManager);
    23. return $dispatcher;
    24. }

    In this example, the developer wants to inspect the parameters that an action will receive in order to dynamically inject model instances.

    The controller looks like:

    Method showAction receives an instance of the model \Posts, the developer could inspect this before dispatch the action preparing the parameter accordingly:

    1. <?php
    2. use \Exception;
    3. use Phalcon\Mvc\Model;
    4. use Phalcon\Mvc\Dispatcher as MvcDispatcher;
    5. use Phalcon\Events\Event;
    6. use Phalcon\Events\Manager as EventsManager;
    7. use \ReflectionMethod;
    8. $di->set(
    9. 'dispatcher',
    10. function () {
    11. // Create an EventsManager
    12. $eventsManager = new EventsManager();
    13. $eventsManager->attach(
    14. 'dispatch:beforeDispatchLoop',
    15. function (Event $event, $dispatcher) {
    16. // Possible controller class name
    17. $controllerName = $dispatcher->getControllerClass();
    18. // Possible method name
    19. $actionName = $dispatcher->getActiveMethod();
    20. try {
    21. // Get the reflection for the method to be executed
    22. $reflection = new ReflectionMethod($controllerName, $actionName);
    23. $parameters = $reflection->getParameters();
    24. // Check parameters
    25. foreach ($parameters as $parameter) {
    26. // Get the expected model name
    27. $className = $parameter->getClass()->name;
    28. // Check if the parameter expects a model instance
    29. if (is_subclass_of($className, Model::class)) {
    30. $model = $className::findFirstById(
    31. $dispatcher->getParams()[0]
    32. );
    33. // Override the parameters by the model instance
    34. $dispatcher->setParams(
    35. [
    36. $model,
    37. ]
    38. );
    39. }
    40. }
    41. } catch (Exception $e) {
    42. // An exception has occurred, maybe the class or action does not exist?
    43. }
    44. }
    45. );
    46. $dispatcher = new MvcDispatcher();
    47. $dispatcher->setEventsManager($eventsManager);
    48. return $dispatcher;
    49. }
    50. );

    From 3.1.x onwards the dispatcher also comes with an option to handle this internally for all models passed into a controller action by using Phalcon\Mvc\Model\Binder.

    1. use Phalcon\Mvc\Dispatcher;
    2. use Phalcon\Mvc\Model\Binder;
    3. $dispatcher = new Dispatcher();
    4. $dispatcher->setModelBinder(
    5. new Binder()
    6. );
    7. return $dispatcher;

    It also introduces a new interface which allows you to define the controllers associated models to allow models binding in base controllers.

    For example, you have a base CrudController which your PostsController extends from. Your CrudController looks something like this:

    1. use Phalcon\Mvc\Controller;
    2. use Phalcon\Mvc\Model;
    3. class CrudController extends Controller
    4. {
    5. /**
    6. * Show action
    7. */
    8. public function showAction(Model $model)
    9. {
    10. $this->view->model = $model;
    11. }
    12. }

    In your PostsController you need to define which model the controller is associated with. This is done by implementing the Phalcon\Mvc\Model\Binder\BindableInterface which will add the getModelName() method from which you can return the model name. It can return string with just one model name or associative array where key is parameter name.

    1. use Phalcon\Mvc\Model\Binder\BindableInterface;
    2. use Models\Posts;
    3. class PostsController extends CrudController implements BindableInterface
    4. {
    5. public static function getModelName()
    6. {
    7. return Posts::class;
    8. }
    9. }

    By declaring the model associated with the PostsController the binder can check the controller for the getModelName() method before passing the defined model into the parent show action.

    If your project structure does not use any parent controller you can of course still bind the model directly into the controller action:

    1. use Phalcon\Mvc\Controller;
    2. use Models\Posts;
    3. class PostsController extends Controller
    4. {
    5. /**
    6. * Shows posts
    7. */
    8. public function showAction(Posts $post)
    9. {
    10. $this->view->post = $post;
    11. }
    12. }

    Using the it’s possible to insert a hook point before the dispatcher throws an exception when the controller/action combination wasn’t found:

    Of course, this method can be moved onto independent plugin classes, allowing more than one class take actions when an exception is produced in the dispatch loop:

    1. <?php
    2. use Exception;
    3. use Phalcon\Events\Event;
    4. use Phalcon\Mvc\Dispatcher;
    5. use Phalcon\Mvc\Dispatcher\Exception as DispatchException;
    6. class ExceptionsPlugin
    7. {
    8. public function beforeException(Event $event, Dispatcher $dispatcher, Exception $exception)
    9. {
    10. // Default error action
    11. $action = 'show503';
    12. // Handle 404 exceptions
    13. if ($exception instanceof DispatchException) {
    14. $action = 'show404';
    15. }
    16. $dispatcher->forward(
    17. [
    18. 'controller' => 'index',
    19. 'action' => $action,
    20. ]
    21. );
    22. return false;
    23. }

    Implementing your own Dispatcher