Joomla Developer Manual
Manual Index
Dependency Injection in Joomla 4
Joomla! 4.0
Joomla 4 introduces the practice of Dependency Injection Containers (DIC's) into Joomla. This article aims to explain why we are introducing them and how to use them in Joomla.
Introduction¶
DIC's have been around in the PHP ecosystem for a long time now to support the goals of dependency injection. For example Symfony introduced the concept in 2009.
There's a variety of reasons why now is the right time to introduce these into Joomla 4:
- Testing - one of the themes of Joomla 3 has been buggy releases. We need to be able to test classes and components in an easier fashion. Dependency injection allows significantly easier injection of Mock classes hopefully allowing us to reduce the amount of bugs.
- Reduce the amount of magic in Joomla - Joomla has a large number of magic files you need to guess the names of. This increases the amount of time people new to Joomla have to take researching these conventions. Exposing a concrete class in extensions allows us to easily test extensions compatibility with other extensions (for example categories and associations).
The global container¶
The Global Dependency injection is very loosely a replacement for the JFactory class. However it shouldn't be mistaken for a direct replacement.
Upgrade Note
As a majority rule you shouldn't directly replace JFactory calls to the container. The majority of the time you should either get the object out the application OR use dependency injection into your class.
So for example in your Controllers in the CMS instead of substituting
\Joomla\CMS\Factory::getDocument()
consider using
$this->app->getDocument()
. This uses the injected application and
therefore allows for easier testing.
Creating an object in a container¶
To place something in the Global DIC the most simple way is to pass in an anonymous function. An example for a logger is below
// Assuming we have an instance of a Joomla Container
$container->share(
LoggerInterface::class,
function (Container $container)
{
return \Joomla\CMS\Log\Log::createDelegatedLogger();
},
true
);
The share function takes two compulsory parameters and an optional third parameter.
- A name for the service is nearly always the class name that you're creating
- An anonymous function takes a single parameter - the container instance (this allows you to retrieve any dependencies out the container). The return is the service that you want to place into the container
- (optional) This boolean controls whether the service is protected (i.e. whether anyone else is allowed to override it in the container). Generally for Joomla core services, such as session objects, this is true.
Let's now look at a more complicated example:
$container->alias('AmazingApiRouter', Joomla\CMS\Router\ApiRouter::class)
->share(
\Joomla\CMS\Router\ApiRouter::class,
function (Container $container)
{
return new \Joomla\CMS\Router\ApiRouter($container->get(\Joomla\CMS\Application\ApiApplication::class));
},
true
);
Here you can see we've done two extra things - we've started using
dependencies (the api router gets the api application out the container)
and we've also created an alias for the ApiRouter. That means whilst the
container recognises that if it needs to build an ApiRouter instance it
can do that. But in our code to keep things simple we can also run
Factory::getContainer()->get('AmazingApiRouter')
to retrieve our
router.
Whilst in Joomla our providers can look more complicated than this because the logic to create objects inside the anonymous function is more complicated - all of them follow this base idea.
Providers¶
Providers in Joomla are a way of registering a dependency into a service
container. To do this create a class that implements
Joomla\DI\ServiceProviderInterface
. This gives you a register method
which contains the container. You can then use the share method again to
add any number of objects into the container. You can then register this
into the container with the
`\Joomla\DI\Container::registerServiceProvider` method in the
container. You can see where we register all the core service providers
here in the
\Joomla\CMS\Factory::createContainer method
Component Containers¶
Every component also has its own container (which is located in the
administrator section of Joomla). However this container is not exposed.
It's just there to get the system dependencies and allow a class to
represent your extension. This class is the Extension class and at a
minimum must implement the relevant extensions type interface. For
example a component must implement the
\Joomla\CMS\Extension\ComponentInterface
(found on here on GitHub).
For full information on implementing this in your extension, we
recommend reading Developing an MVC
Component
Using a component container in another extension¶
You can easily grab the container of another extension through the CMSApplication object. For example
Factory::getApplication()->bootComponent('com_content')->getMVCFactory()->createModel('Articles', 'Site');
Will get the com_content container, get the MVC Factory and get the ArticlesModel from the frontend of Joomla. And this will work in any extension in frontend, backend or the API of Joomla (unlike the old LegacyModel::getInstance() method)
Read More¶
There's a great example in the Joomla Framework docs on why Dependency Injection is good for your Application and how DIC's help structure it. Read it here