Work In Progress

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

Controllers

The bulk of the implementation logic for Joomla 4 MVC Controllers is the same as in Joomla 3 MVC.

The logic is that most views are displayed using the component's DisplayController which extends from Joomla's Joomla\CMS\MVC\Controller\BaseController. You only write custom controllers when there's an action needed or when you need access to a Form object.

[Tip]Tip

The kind of MVC practiced by Joomla and described above is a bit “dirty”; all views share a common DisplayController for displaying. This is fine with Joomla's one-trick-pony components, only able to display one kind of information.

When you have something more complex this may not be at all convenient. For example, a document management system might need separate views to display nested categories, available items (including the actual downloading part!) and uploading items, as well as viewing, adding and managing comments per document. In these cases it's typically much more efficient to have a separate controller per view. In components like that each view consists of a Controller, Model and View class (an MVC triad) which is more in line with how the normative MVC coding pattern is meant to work. This is perfectly possible in Joomla as long as you have a custom Dispatcher. You can see how I am doing that in the Dispatcher of Akeeba Release System, the software downloads management software used by Joomla's official Downloads site.

The Controller classes extend from one of the base Joomla MVC Controller super-classes:

Joomla\CMS\MVC\Controller\BaseController

The most basic controller you can get. It is mainly used to power the DisplayController of the component or whenever you want to create custom controllers to display stuff and perform non-administrative actions (e.g. in the frontend).

[Warning]Warning

Heads up! In Joomla 3 the default display controller was in the root of the component as the file controller.php e.g. administrator/components/com_example/controller.php. This is no longer the case in Joomla 4 and later. All controllers are under the src/Controller folder and the default controller is named DisplayController which means that in the above example it would be located in administrator/components/com_example/srs/Controller/DisplayController.php.

In Joomla 4 and later the DisplayController is mostly empty except for a line like this:

protected $default_view = 'foobar';

This is the name of the “default view”. That's the view which will be displayed if the view URL parameter is not provided in the request.

\Joomla\CMS\MVC\Controller\AdminController

This is an extension to BaseController. It is meant for displaying list views in the backend of your site, e.g. a list of articles.

[Tip]Tip

This is the controller you will be typically using for controllers whose name is plural such as \Acme\Component\Example\Administrator\Controller\ItemsController. Since this is the Items controller and “items” is plural it gives us a hint that it will be displaying multiple items which, in the Joomla backend, is very likely to be a list.

A mnemonic way to remember this is PASiFIC (yes, with the misspelling and all) "Plural Admin Singular Form Identifies the Controller".

It also handles some of the admin tasks you will have to perform on records in a Joomla list view: publish, unpulish, archive, trash, report, orderup, orderdown, delete, reorder, saveorder, checkin, checkout, saveOrderAjax and runTransition. Everything else has to be handled by a singular named Controller which extends FormController. Yes, that's confusing and yes, it's the source of many bugs — but that's the same as Joomla 3 and earlier so at least it's consistently confusing.

\Joomla\CMS\MVC\Controller\FormController

This is an extension to the BaseController which additionally implements Form handling. This is meant to be used in views which add or edit a database record. Additionally, it will be used in views which handle an XML Form e.g. a configuration page.

[Tip]Tip

This is the controller you will be typically using for controllers whose name is singular such as \Acme\Component\Example\Administrator\Controller\ItemController. Since this is the Item controller and “item” is singular it gives us a hint that it will be displaying a single item which, in the Joomla backend, is very likely to be an add / edit record page.

In the frontend of the site you may have two controllers for the same data type. One DisplayController extending from BaseController to display the item and one singular name controller extending from FormController to add or edit an item. It is possible to have a single controller, extending from FormController. You see, FormController itself extends from BaseController which has the display method which is used to display stuff, i.e. it already contains the functionality of the typical DisplayController.

\Joomla\CMS\MVC\Controller\ApiController

This is a very special kind of controller which is only used in the JSON API application part of your component e.g. the code under api/components/com_example.

This type of controller does not have a direct equivalent in the other controllers. It can do everything. Produce a list of records, return a single record, create a new record and modify or delete an existing record.

Basic services in your Controller

A Controller object created by Joomla has a few basic services. In the olden days before Joomla 4 we had to use static methods in \Joomla\CMS\Factory (formerly JFactory) or the base MVC objects themselves to get access to these services. You are no longer supposed to do that, you are supposed to use the services provided in the Controller object itself.

Application

In Joomla 3 and earlier we would get the currently active application object through \Joomla\CMS\Factory::getApplication(). Do NOT do that.

In Joomla 4 and later you can get the currently active application object through $this->app.

Please remember that the application you are getting is not necessarily a SiteApplication or AdministratorApplication. It may very well be an ApiApplication (JSON API), a ConsoleApplication (Joomla CLI app) or even a custom application extending from \Joomla\CMS\Application\CMSApplication.

Dispatcher

In Joomla 3 and earlier you'd run plugin events by doing something like this:

$results = \Joomla\CMS\Factory::getApplication()
                ->triggerEvent('onSomething', [$param1, $param2]);

In Joomla 4 and later there are two ways to call plugin events.

The first method is the legacy method, going through the application's triggerEvent method, which is discouraged and will eventually go away.

$results = $this->app->triggerEvent('onSomething', [$param1, $param2]);

The second and recommended method is going through the Joomla events dispatcher and using real events.

$event = new \Joomla\Event\Event('onSomething', [$param1, $param2]);
$this->getDispatcher()->dispatch($event->getName(), $event);
$results = $event->getArgument('result', []);

While this looks a bit more complicated it has some benefits.

Using Events to access plugins means that you get access to a concrete event object. All core Joomla events will correspond to concrete event objects in Joomla 5.0, meaning that you will be able to add typehinting and have your IDE (e.g. phpStorm, Visual Studio Code, NetBeans, Eclipse, ...) auto-complete the argument names when manipulating an event.

The real Events implemented in Joomla 4 and later, unlike plain old plugin handlers we had in Joomla 1.0 to 3.10, can prevent some of their arguments to be modified, prescribe exactly what can be modified and even allow plugins to stop the processing of an event when we reach a point that further processing is unnecessary. If you slowly move from just using custom event names to concrete event classes you can implement far more complex features in your code with much less code — believe me, I've been there and done that! Event handling is one of those fundamental things which you are really upset it's changed but once you start getting the idea of how the new system works you start wondering how you could have ever written software without it.

Input

Back in the olden days we'd get the user's input by doing one of these (the more of these you remember the older you are — some date back to the early 00s!):

// Joomla 1.0
$foo = \JRequest::getCmd('foo');
// Joomla 1.5
$input = new \Joomla\CMS\Input\Input();
$foo = $input->getCmd('foo');
// Joomla 1.6
$foo = Joomla\CMS\Factory::getApplication()->input->getCmd('foo');

This had always been a bad idea because our Controller had to know about the global application object and its request variables.

Since Joomla 3.0 the Controller object has an input property which holds its own \Joomla\CMS\Input\Input object. In Joomla 3 this was typically just the application's input object unless you were manually constructing a controller (with a lot of effort).

In Joomla 4.0 and later the input property contains its own \Joomla\CMS\Input\Input object which comes from the DI Container of the component. Remember that when booting a component we get access to its component extension object which has access to the component's DI Container. This of course means that we can add a method to that object to access the DI Container and set a custom input object, thereby implementing HMVC very easily — something that Joomla core maintainers didn't think was possible with the core architecture a few years ago! This means that whenever we want to create a module which displays the same information an existing view of our component already does, albeit in a slightly different format, all we need to do is create a new view template or layout and use HMVC, without having to rewrite our business logic in the module. We can write less code to do more. That's awesome!

That is (one of the many reasons) why you should always be accessing the input as $this->input in your controllers. Now you know.

MVCFactory

Back in Joomla 1.0 to 3.10 creating an MVC object such as a Model, View, Table, or even another Controller from inside our Controller required calling static methods in the base MVC objects or proxy methods in the controller, like so:

$someController = \Joomla\CMS\MVC\Controller\BaseController::getInstance('Some');
$someModel = \Joomla\CMS\MVC\Model\BaseDatabaseModel::getInstance('Some', 'ExampleModel');
// or
$someModel = $this->createModel('Some', 'ExampleModel');
$someView = $this->createView('Some', 'ExampleView', 'Html');
$someTable = \Joomla\CMS\Table\Table::getInstance('Some', 'ExampleTable');

There are two things which strike us as suboptimal:

  • There is no consistency in how you create MVC objects. Some are static calls, some are method calls to proxy functions.

  • There is no consistency in the arguments you provide. Creating a controller only needs its name (and requires the magic configuration array key base_path if you want to get a controller from a component other than the current one), creating anything else requires us to give it the prefix of the MVC class name (not just the component name).

This is what we'd call “sanity came here to die”.

Joomla 3.10 introduced the MVCFactory for creating models, views and tables and Joomla 4.0 extended it by also letting it create controllers. See how easier and more consistent everything is now:

// Only on Joomla 4.0 and later
$someController = $this->getMVCFactory()->createController('Some', 'Administrator');
// Joomla 3.10 and later
$someModel = $this->getMVCFactory()->createModel('Some', 'Administrator');
$someView = $this->getMVCFactory()->createView('Some', 'Administrator', 'Html');
$someTable = $this->getMVCFactory()->createTable('Some', 'Administrator');
// Inside the Controller I can still do this (4.0 and later):
$someModel = $this->getModel('Some', 'Administrator');
$someView = $this->getView('Some', 'Administrator', 'Html');

First of all, we see that creating MVC objects is now very consistent and easier.

Second, we notice that the $prefix argument is no longer something component-specific (such as ExampleModel) but simply the side of the application (Site, Administrator or Api) that we want to get the MVC object from.

[Note]Note

Tables are only available in the Administrator side of the application. While you can theoretically define different Table classes in the frontend (Site) or JSON API (Api) side it's not a good idea as you will very likely run into issues where the “wrong” table class is being used. As a result, it's advisable to always call createTable with two parameters and set the second parameter to Administrator).

Legacy Joomla 3 components still work despite the apparent backwards compatibility (b/c) break in the getModel and getView methods. Why is that? Well, it's because Joomla 4 uses the \Joomla\CMS\MVC\Factory\LegacyFactory instead of \Joomla\CMS\MVC\Factory\MVCFactory for these components. The LegacyFactory is aware of the b/c break and acts accordingly so you, the extensions developer, do not suffer. This is possible because each component has its own DI Container. So that's another way how using a DI Container allowed Joomla to create an updated, richer MVC API without breaking backwards compatibility with Joomla 3 — at least until Joomla 6.0. Neat, huh?

As a developer, you should remember this when you are inside a Controller writing code:

  • If you want to get a Model or View use the controller's getModel and getView methods.

  • If you want to get another Controller or a Table do not instantiate them directly! Always go through the MVCFactory.

  • The second argument to all these methods is the application side you are getting an object from. Leave it empty to use the current application side or pass an explicit value. If you leave it empty keep in mind that, for example, your backend controller may be running in the frontend. In this case, do you really want to get the frontend model?