Dependency Injection in Joomla 4/nl
From Joomla! Documentation
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.
# 'Verminder de hoeveelheid mysterie in Joomla' - Joomla heeft een groot aantal bestanden waarvan je de namen moet raden. Dit verhoogt de hoeveelheid tijd die mensen die nieuw zijn bij Joomla moeten nemen om deze conventies te onderzoeken. Door een concrete class in extensies bloot te stellen, kunnen we eenvoudig de compatibiliteit van extensies testen met andere extensies (bijvoorbeeld categorieën en associaties).
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.
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
);
De deelfunctie heeft twee verplichte parameters en een optionele derde parameter.
- Een naam voor de service is bijna altijd de class naam die u maakt
- Een anonieme functie heeft één parameter nodig - de containerinstantie (hiermee kunt u eventuele afhankelijkheden uit de container ophalen). De return is de service die je in de container wilt plaatsen
- (optioneel) Deze boolean bepaalt of de service beschermd is (d.w.z. of iemand anders deze in de container mag overschrijven). Over het algemeen geldt dit voor Joomla-kernservices, zoals sessieobjecten.
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