In Joomla 3 and earlier versions the module helper was a class with static methods. You would call these static methods in the module's entry point file, or a module view template, directly. This is the concept of pulling information.
The big limitation of this concept is that static method calls are inflexible. If you are trying to write any kind of tests for your view templates, you will quickly find that the static calls make your code untestable unless you go to great lengths (create a mock class, create a stream wrapper, load the PHP code, replace the calls to the helper with calls to the mock class, and test the temporary file you created in the stream wrapper). This is why this concept, while easy to use by novice developers, is no longer considered a good practice — and that applies in general in programming, not just Joomla! modules.
As noted earlier, Joomla 4 introduced a different concept, one where you push information to the view template through the Dispatcher. This isolates the business logic from the presentation. When you, or a site integrator, create a view template or view template override for a module you no longer need to care how you will get the data you need to display. From your point of view, the display data is magically there. From a developer's point of view, the isolation of business logic and presentation means that both are perfectly testable; the former with Unit Tests, the latter by passing a set of hard-coded data and inspecting the generated output.
In practical terms, you no longer have static methods. You have regular methods. Your class is no longer abstract; it's just a regular class.
The simplest helper class you can have is like this:
<?php namespace Acme\Module\Example\Site\Helper; defined('_JEXEC') || die; class ExampleHelper { public function getStuff(): string { return "Hello, world."; } }
In your Dispatcher, you'd call this as
$this->getHelperFactory()->getHelper('ExampleHelper')->getStuff();
.
In practice, you will need access to some database date. In the past
you'd get Joomla's database object by doing something like $db =
\Joomla\CMS\Factory::getDbo();
. This is no longer the case. You
will just implement Joomla's
\Joomla\Database\DatabaseAwareInterface
in
your class and use Joomla's
\Joomla\Database\DatabaseAwareTrait
trait. You then
get the database object with $db =
$this->getDatabase();
.
<?php namespace Acme\Module\Example\Site\Helper; defined('_JEXEC') || die; use Joomla\Database\DatabaseAwareInterface; use Joomla\Database\DatabaseAwareTrait; class ExampleHelper implements DatabaseAwareInterface { use DatabaseAwareTrait; public function getStuff(): string { $db = $this->getDatabase(); // Do some database stuff here. return "Hello, world."; } }
Note | |
---|---|
“How does this sorcery work?” you wonder. When you instantiate the
helper object through the helper factory, it checks if your class
implements the DatabaseAwareInterface. If it does, it pushes the Joomla!
database object to the helper object. That's why you can retrieve it
with |
If you need to push any other object, including the module parameters, you have to do it with method parameters. We showed that when talking about the Dispatcher. So let's see how we can pass the module parameters and the application object to the helper:
<?php namespace Acme\Module\Example\Site\Helper; defined('_JEXEC') || die; use Joomla\CMS\Application\CMSApplicationInterface; use Joomla\CMS\Application\SiteApplication; use Joomla\Database\DatabaseAwareInterface; use Joomla\Database\DatabaseAwareTrait; use Joomla\Registry\Registry; class ExampleHelper implements DatabaseAwareInterface { use DatabaseAwareTrait; public function getStuff(Registry $config, CMSApplicationInterface $app): string { if (!$app instanceof SiteApplication) { return ''; } $db = $this->getDatabase(); // Do some database stuff here. return sprintf("Hello, %s.", $config->get('name', 'world')); } }
As a reminder, here's how this was called from the Dispatcher:
$data['stuff'] = $this->getHelperFactory() ->getHelper('ExampleHelper') ->getStuff($data['params'], $this->getApplication());
As you can see, it is fairly easy, as long as you remember to push information to the helper instead of the old way of having it pull information from static methods.