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 | |
---|---|
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
|
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 | |
---|---|
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 \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
, orApi
). - 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
, orApi
). - 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
, orApi
). The third argument is the view type which corresponds to theformat
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
, orApi
).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.