Dependency Injection (DI) sounds complicated and the synonym introducing dependencies does not really sound positive. At first glance, program code should be as flexible as possible. In other words, not dependent on anything else. Nobody likes to be dependent. The word has a negative touch.
Complicated and negative? On closer inspection, neither applies. With the help of a practical example, the advantages will become clear.
The explanations in this chapter are an excursus. In other words, the code described here is not included in the final version of the boilerplate.
Dependency Injection in Joomla
Step 1 - Add the function displayDirection()
The initial situation: Imagine you want to make the directions for each item in your component individually describable.
For impatient people: View the changed program code in the Diff View[^codeberg.org/astrid/j4examplecode/compare/t27..t27a1].
administrator/components/com_foos/src/Extension/FoosComponent.php
So that every direction can be managed from one place, you start the call in the file administrator/components/com_foos/src/Extension/FoosComponent.php
. This file uses a container, or rather the interface ContainerInterface
.
administrator/components/com_foos/src/Extension/FoosComponent.php
use Joomla\CMS\HTML\HTMLRegistryAwareTrait;
use FooNamespace\Component\Foos\Administrator\Service\HTML\AdministratorService;
use FooNamespace\Component\Foos\Administrator\Service\HTML\Icon;
use FooNamespace\Component\Foos\Administrator\Service\HTML\Direction;
use Psr\Container\ContainerInterface;
use Joomla\CMS\Helper\ContentHelper;
use Joomla\CMS\Component\Router\RouterServiceInterface;
public function boot(ContainerInterface $container)
{
$this->getRegistry()->register('foosadministrator', new AdministratorService);
$this->getRegistry()->register('fooicon', new Icon($container->get(SiteApplication::class)));
$this->getRegistry()->register('foodirection', new Direction());
}
/**
administrator/components/com_foos/src/Service/HTML/Direction.php
We print the directions as text using the displayDirection
method of the Direction
class.
administrator/components/com_foos/src/Service/HTML/Direction.php
<?php
/**
* @package Joomla.Site
* @subpackage com_foos
*
* @copyright Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace FooNamespace\Component\Foos\Administrator\Service\HTML;
\defined('_JEXEC') or die;
/**
* Directions Helper
*
* @since __DEPLOY_VERSION__
*/
class Direction
{
/**
* Service constructor
*
* @since __DEPLOY_VERSION__
*/
public function __construct()
{
}
/**
* Method to generate a routing direction
*
* @return string The HTML markup for the create item link
*
* @since __DEPLOY_VERSION__
*/
public function displayDirection()
{
return "The route description";
}
}
components/com_foos/tmpl/foo/default.php
The template default.php
in the directory components/com_foos/tmpl/foo/
is responsible for the actual output.
components/com_foos/tmpl/ foo/default.php
</div>
<?php endif; ?>
<hr>
<?php echo HTMLHelper::_('foodirection.displayDirection', $this->item, $tparams); ?>
<hr>
<?php
echo $this->item->event->afterDisplayTitle;
echo $this->item->event->beforeDisplayContent;
When you call up an item in the frontend, the text you prepared to describe the directions appears.
Now the thing is that there are different ways to describe it:
- There are digital maps that even offer routing functions.
- The description via text is possible
- You can describe the route with the help of a picture.
For some items you have a descriptive graphic that shows the location. For another item, there is no graphic. Instead, the address can be easily found via geocoding services and displayed on a digital map. For other items, the position can only be described by text because insider knowledge is required. We will work on this problem in step 2.
Schritt 2 - Die Funktion displayDirection() für mehrere Möglichkeiten verwenden
For impatient people: View the changed program code in the Diff View[^codeberg.org/astrid/j4examplecode/compare/t27a1..t27a2].
administrator/components/com_foos/src/Service/HTML/Direction.php
First, we prepare a class for each description type. Each class can prepare the text for the directions separately and therefore well arranged. In this step, we next display the description for each type.
administrator/components/com_foos/src/Service/HTML/Direction.php
\defined('_JEXEC') or die;
use FooNamespace\Component\Foos\Administrator\Service\HTML\Directions\Image;
use FooNamespace\Component\Foos\Administrator\Service\HTML\Directions\Map;
use FooNamespace\Component\Foos\Administrator\Service\HTML\Directions\Text;
/**
* Directions Helper
*
*/
class Direction
{
protected $directionTool1;
protected $directionTool2;
protected $directionTool3;
/**
* Service constructor
*
class Direction
*/
public function __construct()
{
$this->directionTool1 = new Image;
$this->directionTool2 = new Map;
$this->directionTool3 = new Text;
}
/**
public function __construct()
*/
public function displayDirection()
{
return "The route description";
return
$this->directionTool1->findDirection() . "<br>" .
$this->directionTool2->findDirection() . "<br>" .
$this->directionTool3->findDirection();
}
}
administrator/components/com_foos/src/Service/HTML/Directions/Image.php
Below you see the class that is responsible for displaying the image.
administrator/components/com_foos/src/Service/HTML/Directions/Image.php
<?php
/**
* @package Joomla.Site
* @subpackage com_foos
*
* @copyright Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace FooNamespace\Component\Foos\Administrator\Service\HTML\Directions;
\defined('_JEXEC') or die;
/**
* Content Component HTML Helper
*
* @since __DEPLOY_VERSION__
*/
class Image
{
/**
* Service constructor
*
* @param CMSApplication $application The application
*
* @since __DEPLOY_VERSION__
*/
public function __construct()
{
}
/**
* Method to generate a link to the create item page for the given category
*
* @param object $category The category information
* @param Registry $params The item parameters
* @param array $attribs Optional attributes for the link
*
* @return string The HTML markup for the create item link
*
* @since __DEPLOY_VERSION__
*/
public static function findDirection()
{
return "Find direction on Image.";
}
}
administrator/components/com_foos/src/Service/HTML/Directions/Map.php
The most complex is probably the creation of the route via the digital map, which is the task of the class 'Map'.
administrator/components/com_foos/src/Service/HTML/Directions/Map.php
<?php
/**
* @package Joomla.Site
* @subpackage com_foos
*
* @copyright Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace FooNamespace\Component\Foos\Administrator\Service\HTML\Directions;
\defined('_JEXEC') or die;
/**
* Content Component HTML Helper
*
* @since __DEPLOY_VERSION__
*/
class Map
{
/**
* Service constructor
*
* @param CMSApplication $application The application
*
* @since __DEPLOY_VERSION__
*/
public function __construct()
{
}
/**
* Method to generate a link to the create item page for the given category
*
* @param object $category The category information
* @param Registry $params The item parameters
* @param array $attribs Optional attributes for the link
*
* @return string The HTML markup for the create item link
*
* @since __DEPLOY_VERSION__
*/
public static function findDirection()
{
return "Find direction with a Map.";
}
}
administrator/components/com_foos/src/Service/HTML/Directions/Text.php
The class named 'Text' prepares the textual description of the route.
administrator/components/com_foos/src/Service/HTML/Directions/Text.php
<?php
/**
* @package Joomla.Site
* @subpackage com_foos
*
* @copyright Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace FooNamespace\Component\Foos\Administrator\Service\HTML\Directions;
\defined('_JEXEC') or die;
/**
* Content Component HTML Helper
*
* @since __DEPLOY_VERSION__
*/
class Text
{
/**
* Service constructor
*
* @param CMSApplication $application The application
*
* @since __DEPLOY_VERSION__
*/
public function __construct()
{
}
/**
* Method to generate a link to the create item page for the given category
*
* @param object $category The category information
* @param Registry $params The item parameters
* @param array $attribs Optional attributes for the link
*
* @return string The HTML markup for the create item link
*
* @since __DEPLOY_VERSION__
*/
public static function findDirection()
{
return "Find direction via Text Explanation.";
}
}
When you call up an item in the frontend, the text you prepared to describe the directions appears.
The problem: At the moment, all types of directions are displayed and it is not ensured that this description exists. Often not every type is available. Sometimes it is also the case that you want to specify which type is displayed. Instead of making all types available, it would be better if the optimal directions could be defined. This way it would also be possible to add or remove new types without having to make changes to the existing code. We will look at how this is possible in step 3.
Step 3 - Select displayDirection() variable and type-safe
For each item, the description of the direction is possible by text, by image or by digital map. It would be nice if the three types were equally applicable next to each other and if it was ensured that there is at least one description. Let us look at 'interfaces' and 'traits' in this context.
An interface is a contract between the implementing class and the calling class. The contract ensures that each class meets some criteria that the interface implements. We have three options. For each, we create an interface. An interface is something like a contract that ensures minimum requirements are met. We then implement these interfaces in the classes. By using a "trait", we ensure that we don't have to rewrite the contract each time. We use standards. This way, our service works as agreed!
More information about traits [^php.net/manual/en/language.oop5.traits.php] and interfaces [^php.net/manual/en/language.oop5.interfaces.php].
For impatient people: View the changed program code in the Diff View[^codeberg.org/astrid/j4examplecode/compare/t27a2..t27a3].
administrator/components/com_foos/src/Extension/FoosComponent.php
In the component class we add everything necessary for the service Direction
.
administrator/components/com_foos/src/Extension/FoosComponent.php
use Joomla\CMS\Helper\ContentHelper;
use Joomla\CMS\Component\Router\RouterServiceInterface;
use Joomla\CMS\Component\Router\RouterServiceTrait;
use FooNamespace\Component\Foos\Administrator\Service\Direction\DirectionServiceInterface;
use FooNamespace\Component\Foos\Administrator\Service\Direction\DirectionServiceTrait;
/**
* Component class for com_foos
*
* @since __BUMP_VERSION__
*/
class FoosComponent extends MVCComponent implements BootableExtensionInterface, CategoryServiceInterface, AssociationServiceInterface, RouterServiceInterface
class FoosComponent extends MVCComponent implements BootableExtensionInterface, CategoryServiceInterface, AssociationServiceInterface, RouterServiceInterface, DirectionServiceInterface
{
use CategoryServiceTrait;
use AssociationServiceTrait;
use HTMLRegistryAwareTrait;
use RouterServiceTrait;
use DirectionServiceTrait;
/**
* Booting the extension. This is the function to set up the environment of the extension like
public function boot(ContainerInterface $container)
{
$this->getRegistry()->register('foosadministrator', new AdministratorService);
$this->getRegistry()->register('fooicon', new Icon($container->get(SiteApplication::class)));
$this->getRegistry()->register('foodirection', new Direction());
}
/**
administrator/components/com_foos/src/Service/Direction/DirectionExtensionInterface.php
The file DirectionExtensionInterface.php
contains the interface DirectionExtensionInterface
which ensures that the function findDirection()
is available to all implementing classes. Put simply, the contract is: if a class implements the interface, then it provides a solution for the containing functions.
administrator/components/com_foos/src/Service/Direction/DirectionExtensionInterface.php
<?php
/**
* @package Joomla.Site
* @subpackage com_foos
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace FooNamespace\Component\Foos\Administrator\Service\Direction;
\defined('JPATH_PLATFORM') or die;
/**
* Direction Extension Interface for the helper classes
*
* @since __DEPLOY_VERSION__
*/
interface DirectionExtensionInterface
{
/**
* Method to get the direction for a given item.
*
* @return string Direction
*
* @since __DEPLOY_VERSION__
*/
public static function findDirection();
}
administrator/components/com_foos/src/Service/Direction/DirectionServiceInterface.php
DirectionServiceInterface' is another interface. It defines which interface the service supports and how it can be accessed. Specifically, we use DirectionExtensionInterface
, which we discussed in the previous section. We can retrieve this via getDirectionExtension
.We will do the latter in a concrete example below.
administrator/components/com_foos/src/Service/Direction/DirectionServiceInterface.php
<?php
/**
* @package Joomla.Site
* @subpackage com_foos
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace FooNamespace\Component\Foos\Administrator\Service\Direction;
\defined('JPATH_PLATFORM') or die;
/**
* The Direction service.
*
* @since __DEPLOY_VERSION__
*/
interface DirectionServiceInterface
{
/**
* Returns the Directions extension helper class.
*
* @return DirectionExtensionInterface
*
* @since __DEPLOY_VERSION__
*/
public function getDirectionExtension(): DirectionExtensionInterface;
}
administrator/components/com_foos/src/Service/Direction/DirectionServiceTrait.php
The trait DirectionServiceTrait
provides a standard implementation of the functions getDirectionExtension
and setDirectionExtension
, so that our contract is for sure fulfilled.
administrator/components/com_foos/src/Service/Direction/DirectionServiceTrait.php
<?php
/**
* @package Joomla.Site
* @subpackage com_foos
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace FooNamespace\Component\Foos\Administrator\Service\Direction;
\defined('JPATH_PLATFORM') or die;
/**
* Trait to implement DirectionServiceInterface
*
* @since __DEPLOY_VERSION__
*/
trait DirectionServiceTrait
{
/**
* The direction extension.
*
* @var DirectionExtensionInterface
*
* @since __DEPLOY_VERSION__
*/
private $directionExtension = null;
/**
* Returns the directions extension helper class.
*
* @return DirectionExtensionInterface
*
* @since __DEPLOY_VERSION__
*/
public function getDirectionExtension(): DirectionExtensionInterface
{
return $this->directionExtension;
}
/**
* The direction extension.
*
* @param DirectionExtensionInterface $directionExtension The extension
*
* @return void
*
* @since __DEPLOY_VERSION__
*/
public function setDirectionExtension(DirectionExtensionInterface $directionExtension)
{
$this->directionExtension = $directionExtension;
}
}
administrator/components/com_foos/src/Service/HTML/Directions/Image.php
The class Image
should in any case provide the function findDirection
. We achieve this by implementing the interface DirectionExtensionInterface
.
administrator/components/com_foos/src/Service/HTML/Directions/Image.php
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace FooNamespace\Component\Foos\Administrator\Service\HTML\Directions;
namespace FooNamespace\Component\Foos\Administrator\Service\Direction;
\defined('_JEXEC') or die;
*
* @since __DEPLOY_VERSION__
*/
class Image
class Image implements DirectionExtensionInterface
{
/**
administrator/components/com_foos/src/Service/HTML/Directions/Map.php
The class Map
should also offer the function findDirection
. We achieve this by also implementing the interface DirectionExtensionInterface
.
administrator/components/com_foos/src/Service/HTML/Directions/Map.php
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace FooNamespace\Component\Foos\Administrator\Service\HTML\Directions;
namespace FooNamespace\Component\Foos\Administrator\Service\Direction;
\defined('_JEXEC') or die;
*
* @since __DEPLOY_VERSION__
*/
class Map
class Map implements DirectionExtensionInterface
{
/**
administrator/components/com_foos/src/Service/HTML/Directions/Text.php
Last but not least, Map
shall provide the function findDirection
. Therefore, this also implements the interface DirectionExtensionInterface
.
administrator/components/com_foos/src/Service/HTML/Directions/Text.php
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace FooNamespace\Component\Foos\Administrator\Service\HTML\Directions;
namespace FooNamespace\Component\Foos\Administrator\Service\Direction;
\defined('_JEXEC') or die;
*
* @since __DEPLOY_VERSION__
*/
class Text
class Text implements DirectionExtensionInterface
{
/**
administrator/components/com_foos/src/Service/HTML/Direction.php
We do not need the class administrator/components/com_foos/src/Service/HTML/Direction.php
any further. We delete it.
administrator/components/com_foos/src/Service/HTML/Direction.php
<?php
/**
* @package Joomla.Site
* @subpackage com_foos
*
* @copyright Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace FooNamespace\Component\Foos\Administrator\Service\HTML;
\defined('_JEXEC') or die;
use FooNamespace\Component\Foos\Administrator\Service\HTML\Directions\Image;
use FooNamespace\Component\Foos\Administrator\Service\HTML\Directions\Map;
use FooNamespace\Component\Foos\Administrator\Service\HTML\Directions\Text;
/**
* Directions Helper
*
* @since __DEPLOY_VERSION__
*/
class Direction
{
protected $directionTool1;
protected $directionTool2;
protected $directionTool3;
/**
* Service constructor
*
* @since __DEPLOY_VERSION__
*/
public function __construct()
{
$this->directionTool1 = new Image;
$this->directionTool2 = new Map;
$this->directionTool3 = new Text;
}
/**
* Method to generate a routing direction
*
* @return string The HTML markup for the direction
*
* @since __DEPLOY_VERSION__
*/
public function displayDirection()
{
return
$this->directionTool1->findDirection() . "<br>" .
$this->directionTool2->findDirection() . "<br>" .
$this->directionTool3->findDirection();
}
}
components/com_foos/tmpl/foo/default.php
When displaying in the frontend, we can load the component class via $fooComponent = Factory::getApplication()->bootComponent('com_foos')
and dynamically re-set the interface $fooComponent->setDirectionExtension(new DirectionMap)
during runtime. This way it is possible to use different implementations for the output findDirection()
. To ensure that the method findDirection()
is always available, we have implemented the interface DirectionExtensionInterface
in the possible DirectionExtensions DirectionExtension
.
components/com_foos/tmpl/foo/default.php
use Joomla\CMS\Helper\ContentHelper;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use FooNamespace\Component\Foos\Administrator\Service\Direction\Map as DirectionMap;
use FooNamespace\Component\Foos\Administrator\Service\Direction\Text as DirectionText;
use FooNamespace\Component\Foos\Administrator\Service\Direction\Image as DirectionImage;
$canDo = ContentHelper::getActions('com_foos', 'category', $this->item->catid);
$canEdit = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_by == Factory::getUser()->id);
</div>
<?php endif; ?>
<?php
$fooComponent = Factory::getApplication()->bootComponent('com_foos');
?>
<hr>
<?php
$fooComponent->setDirectionExtension(new DirectionMap);
echo $fooComponent->getDirectionExtension()->findDirection();
?>
<hr>
<?php echo HTMLHelper::_('foodirection.displayDirection', $this->item, $tparams); ?>
<?php
$fooComponent->setDirectionExtension(new DirectionText);
echo $fooComponent->getDirectionExtension()->findDirection();
?>
<hr>
<?php
$fooComponent->setDirectionExtension(new DirectionImage);
echo $fooComponent->getDirectionExtension()->findDirection();
?>
<hr>
<?php
When you call up an item in the frontend, the text you prepared to describe the directions appears. In the example, for demonstration purposes, I still display all possible directions. In my opinion, however, it becomes clear how uncomplicated it is to change the output at runtime or how easy it is to manipulate it with the help of parameters. Parameters are also the subject of a chapter in this tutorial.
Why Dependency Injection?
In this section you see a simple example. It contains the basics of DI. Passing the requirements for a class to the class via a set
method, where the set
method typically corresponds to the name of the property. In our case, this is DirectionExtension
: we want to set the extension that outputs the Direction
.
Why containers?
An Inversion of Control (IoC) container can help manage all parts of the application. Instead of creating a new DirectionExtension
each time, it is much easier to remember how to prepare a DirectionExtension
. Since the DirectionExtension
in our example does not have many dependencies, the advantages of a container are hard to see. But imagine that every time you create a DirectionExtension
you have to remember to pass the dependencies like impement the interface DirectionExtensionInterface
and provide the method findDirection
. With a container, it is possible to set up everything as if it were a template and leave the creation to the application. This is even more handy when the dependencies we inject have dependencies within their dependencies. This can all become very complex. Examples you can find in the ../services/provider.php
files, for example in /administrator/components/com_foos/services/provider.php
.
Links
Dependency injection via Wikipedia[^en.wikipedia.org/wiki/dependency_injection]
JAB18: Services in Joomla 4[^joomla.digital-peak.com/images/blog/jab18_services_in_joomla_4.pdf]
Implementation of the services in the component class on Github[^github.com/joomla/joomla-cms/pull/20217]
Why dependency injection in Joomla 4[^github.com/joomla-framework/di/blob/2.0-dev/docs/why-dependency-injection.md]
Webmentions