You may have noticed that in the sample code above I got the
database driver object using the
Factory::getContainer
static method. This is
not ideal. It's even worse if I'd need to get access to MVC objects such
as Models and Tables — having to boot the entire component just to get
its MVCFactory is something to use only if there is no other way, not
something to do by default.
Fortunately, Joomla gives us the option to push any services we need — such as the database driver object and the component's MVCFactory — into the router. Unfortunately, it's a bit non-obvious.
To inject services into the Router service object they need to be injected into the object after it is created by the Router Factory object. The Router Factory object needs to have access to these services to inject them. This means injecting these services to the Router Factory object from the Router Factory Service Provider. The Router Factory Service Provider can get any of the dependencies (services) it needs since it has access to the component's service provider.
Therefore we need to create the two missing pieces of the puzzle (Router Factory and its service provider) and register the latter with our component's service provider.
![]() | Important |
---|---|
The component's service provider
( Yup. It sounds backwards. You can definitely create them in the frontend but, if you do, you and everyone else reading your code might get confused. Don't over-think it, just do what I tell you to do. There's method in this madness, I promise. |
First, let's make our router MVCFactory-aware (it's already database-aware).
<?php
namespace Acme\Component\Example\Site\Service;
use Joomla\CMS\Component\Router\RouterView;
use Joomla\CMS\MVC\Factory\MVCFactoryAwareTrait;
class Router extends RouterView
{
use MVCFactoryAwareTrait;
// ... the rest of the router implementation goes here ...
}
Now, let's create a Router Factory
(\Acme\Component\Example\Administrator\Service\RouterFactory
).
The default Joomla implementation is already database-aware. I am just
extending it to also know about the MVCFactory so we can inject it to
our router.
<?php namespace Acme\Component\Example\Administrator\Service; use Joomla\CMS\Application\CMSApplicationInterface; use Joomla\CMS\Component\Router\RouterInterface; use Joomla\CMS\Menu\AbstractMenu; use Joomla\CMS\MVC\Factory\MVCFactoryAwareTrait; class RouterFactory extends \Joomla\CMS\Component\Router\RouterFactory { use MVCFactoryAwareTrait; public function createRouter(CMSApplicationInterface $application, AbstractMenu $menu): RouterInterface { $router = parent::createRouter($application, $menu); $router->setMVCFactory($this->getMVCFactory()); return $router; } }
Now, we need to create a RouterFactory service provider. Unfortunately, Joomla has a bad habit of registering factory objects in the DI container even though they are only going to return exactly one object with no initialisation, like a router. This complicates thing because we cannot extend the DI container definition. We have to do something stupid: copy Joomla's default implementation of the Router Factory Service Provider just so we can change the returned object type and register dependencies on it. Well... I guess it could be worse?
Anyway. Let's create our router factory service provider
\Acme\Component\Example\Administrator\Service\Provider\RouterFactoryProvider
.
<?php namespace \Acme\Component\Example\Administrator\Service\Provider; use Acme\Component\Example\Administrator\Service\RouterFactory; use Joomla\CMS\Categories\CategoryFactoryInterface; use Joomla\CMS\Component\Router\RouterFactoryInterface; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\Database\DatabaseInterface; use Joomla\DI\Container; use Joomla\DI\ServiceProviderInterface; class RouterFactoryProvider implements ServiceProviderInterface { /** * The module namespace * * @since 4.0.0 * @var string * */ private $namespace; /** * DispatcherFactory constructor. * * @param string $namespace The namespace * * @since 4.0.0 */ public function __construct(string $namespace) { $this->namespace = $namespace; } /** * Registers the service provider with a DI container. * * @param Container $container The DI container. * * @return void * * @since 4.0.0 */ public function register(Container $container) { $container->set( RouterFactoryInterface::class, function (Container $container) { $categoryFactory = null; if ($container->has(CategoryFactoryInterface::class)) { $categoryFactory = $container->get(CategoryFactoryInterface::class); } $routerFactory = new RouterFactory( $this->namespace, $categoryFactory, $container->get(DatabaseInterface::class) ); $routerFactory->setMVCFactory($container->get(MVCFactoryInterface::class)); return $routerFactory; } ); } }
Our changes to the core code in Joomla are highlighted in bold type.
Finally, we need to register this router factory service provider
in our component's services/provider.php
file.
$container->registerServiceProvider( new \Acme\Component\Example\Administrator\Service\Provider\RouterFactoryProvider( '\\Acme\\Component\\Example' ) );