Work In Progress

This book is currently work in progress. Some sections are not yet written. Thank you for your understanding!

Dispatcher

As we said when comparing the Joomla 3 to the Joomla 4 MVC, the Dispatcher takes the place of the front controller (controller.php) in the legacy Joomla 3 MVC. In some ways it also takes the place of the entry point file (e.g. example.php for a component named com_example) as it's the first code which is executed when our component is loaded with the explicit intent of rendering output.

Most components do not need a custom Dispatcher. The default Dispatcher provided by Joomla is just fine.

If you do decide to create a custom Dispatcher its class name MUST be \My\Component\ComponentName\Administrator\Dispatcher\Dispatcher (backend), \My\Component\ComponentName\Site\Dispatcher\Dispatcher (frontend) or \My\Component\ComponentName\Api\Dispatcher\Dispatcher (API application) where \My\Component\ComponentName is the namespace prefix for your component, for instance \Acme\Component\Example. This custom Dispatcher class must extend from \Joomla\CMS\Dispatcher\ComponentDispatcher.

Typically, there are two methods you might need to customise. One is loadLanguage which loads your component's language files. You may have to customise that if, for example, you need to load the backend files in the frontend or vice versa, or if you need to load some core component's language files on top of yours.

The other method may be the dispatch() method which figures out the view, task and controller from the input and executes the component.

Usually, I override the dispatch() method to add a minimum PHP version check and fix up the request variables so I always have a view and task. The latter helps when writing Routers for components which have record add / edit views in the frontend. Such a Dispatcher would look like the following code example.

<?php
namespace Acme\Component\Example\Site\Dispatcher;

defined('_JEXEC') or die;

class Dispatcher extends \Joomla\CMS\Dispatcher\ComponentDispatcher
{
  /**
   * The default controller (and view), if none is specified in the request.
   *
   * @var   string
   */
  protected $defaultController = 'items';

  /** @inheritdoc */
  public function dispatch()
  {
    $minPHPVersion = '7.4.0';

    if (version_compare(PHP_VERSION, $minPHPVersion, 'lt'))
    {
      throw new \RuntimeException(
        sprintf(
          'This component requires PHP %s or later.',
          $minPHPVersion
        )
      );
    }

    $this->applyViewAndController();

    parent::dispatch();
  }

  /**
   * Applies the view and controller to the input object communicated to the MVC objects.
   *
   * If we have a controller without view or just a task=controllerName.taskName we populate the view to make things
   * easier and more consistent for us to handle.
   *
   * @return  void
   */
  protected function applyViewAndController(): void
  {
    $controller = $this->input->getCmd('controller', null);
    $view       = $this->input->getCmd('view', null);
    $task       = $this->input->getCmd('task', 'default');

    if (strpos($task, '.') !== false)
    {
      // Explode the controller.task command.
      [$controller, $task] = explode('.', $task);
      $view = null;
    }

    if (empty($controller) && empty($view))
    {
      $controller = $this->defaultController;
      $view       = $this->defaultController;
    }
    elseif (empty($controller) && !empty($view))
    {
      $controller = $view;
    }
    elseif (!empty($controller) && empty($view))
    {
      $view = $controller;
    }

    $controller = strtolower($controller);
    $view       = strtolower($view);

    $this->input->set('view', $view);
    $this->input->set('controller', $controller);
    $this->input->set('task', $task);
  }
}