Work In Progress

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

The MVCFactory

In Joomla 3 you had to make a static call to the base MVC class included in Joomla itself to get an instance of an MVC object. For example, getting your ExampleModelItem model object you had to do something like the following.

$model = \Joomla\CMS\MVC\Model\BaseDatabaseModel::getInstance('Item', 'ExampleModel');

If you were to do that in the frontend of the site you'd get the frontend ExampleModelItem class stored in components/com_example/models/item.php. If you were to do that in the backend of the site you'd get the backend ExampleModelItem class stored in administrator/components/com_example/models/item.php. If you did that from outside the com_example component, e.g. in a module or plugin, it would fail unless either the class was already loaded beforehand OR you had done something like

\Joomla\CMS\MVC\Model\BaseDatabaseModel::addIncludePath(
  'components/com_example/models', 'ExampleModel'
);

Having to call a static method to get an object is bad architecture since the superclass where the static method lives in becomes a God Object — it knows way too much about how everything works in the entire CMS! Moreover, having the ability to auto-load classes only under certain circumstances, and having the object returned depend on both the application running under and magic configuration supplied by static method calls made it impossible to know what will be returned every time. The fact that backend and frontend classes had the exact same Fully Qualified Name made it impossible to double-check something didn't go awry. This loopy behaviour was a constant source of bugs.

Joomla 4 addressed this source of endless frustration by introducing the MVCFactory service in the component's DI container. The MVCFactory object implements the Factory method pattern which means it can create instances of our MVC objects: controllers, models, views and tables.

[Important]Important

Each MVCFactory object instance can only create MVC objects for a specific component! This is in stark contrast with the static calls in Joomla 3 which could create an MVC object for just about any component.

This is actually a good thing! It is implementing the fundamental computer science principle called Separation of Concerns. Put another way, if I get the MVCFactory object for com_example I know that it can create any MVC object I need for com_example and only that components. It won't be “polluted” by any other component. Every time I call an MVCFactory method I AM 100% CONFIDENT I am getting the object I asked for and expected, not something potentially random I have no way of checking. Therefore, the MVCFactory object solves the single biggest source of frustration in Joomla component development. Hallelujah!

When you are writing code in a Controller or Model class you can get your own component's MVCFactory instance using the $this->getMVCFactory() method.

If you are outside a component — a module, plugin, template or a different component — you can still get the MVCFactory of any installed and enabled component on the site as we saw in the lifetime of a component:

$comContentMVCFactory = \Joomla\CMS\Factory::getApplication()
  ->bootComponent('com_content')
  ->getMVCFactory();
[Tip]Tip

Before trying to boot a component you are supposed to check the component is installed and enabled. You can do that very simply, e.g. for com_content, with this code

\Joomla\CMS\Component\ComponentHelper::isEnabled('com_content')

The MVCFactory has four public methods corresponding to the MVC objects we can create:

createController

Loads and creates a controller object.

$myController = $this->getMVCFactory()
                  ->createController('Item', 'Administrator');

The first argument is the name of the controller. The second argument is the application type (Site, Administrator, or Api).

createModel

Loads and creates a model object.

$myModel = $this->getMVCFactory()
             ->createModel('Item', 'Administrator');

The first argument is the name of the model. The second argument is the application type (Site, Administrator, or Api).

createView

Loads and creates a view object.

$myView = $this->getMVCFactory()
            ->createView('Item', 'Administrator', 'Html');

The first argument is the name of the view. The second argument is the application type (Site, Administrator, or Api). The third argument is the view type which corresponds to the format URL parameter.

createTable

Loads and creates a table object.

$myTable = $this->getMVCFactory()
             ->createTable('Item', 'Administrator');

The first argument is the name of the table. The second argument is the application type (Site, Administrator, or Api).

[Warning]Warning

Joomla table classes are only defined in the backend (Administrator) of your component. Therefore your second argument MUST always be 'Administrator'.

While you could conceivably define table classes in the site or api parts of your component it's not recommended and you should not expect Joomla's core code to be able to find them. Think of that second argument as a “forwards compatibility” provision, in case Joomla ever fully supports creating tables in the site or api parts of your component.

MVCFactory and Dependency Injection

The MVCFactory also performs a modicum of dependency injection. It inspects each created MVC object and checks which interfaces it implements. Depending on which interfaces are implemented some basic objects (form factory, dispatcher, router, cache controller, database object) are taken from the corresponding services in the component's DI container and injected into the MVC object.

If you want to inject additional services you will have to extend the MVCFactory service in your component's service provider using a custom MVCFactory wrapper. When registering the service extension in your component's service provider you can push your custom service to the MVCFactory wrapper and use it in the overridden methods of the wrapper. We will see an example of that pushing a custom service into a Model later in this book.

While this sounds like a bit of a chore, it is actually a very clean architecture. If you decide to make a change in your service's initialisation — or swap it out with a completely different implementation altogether — you can do that trivially by changing one line of code in your service provider. No more hunting down all over your codebase for that elusive reference to the service you are using. This is just another way Dependency Injection helps you write more sustainable code.