Work In Progress

This book is currently work in progress. Some sections are not yet written. Thank you for your understanding!

Pushing services to the Model: an alternative

While Joomla 4.3 and later allows you to push services into the Model using an MVCFactory wrapper which extends the MVC service, necessary parts of this architecture did not exist in previous Joomla versions. We will have to use a slightly less “architecturally clean” solution.

We will pull the custom service from the component's DIC.

First, you need to make sure that you register a service provider in your component's service provider implementation:

$container->set(
  \Acme\Component\Example\Administrator\Service\FacebookPublish::class,
  function (Container $container) {
    return new \Acme\Component\Example\Administrator\Service\FacebookPublish();
  }
);

Then, you need to modify your component's extension class section as we saw on that section's “Getting access to the component's DIC anytime, anywhere” to be able to statically return the component's DI Container anytime, anywhere:

protected static $dic;

public function boot(ContainerInterface $container)
{
  self::$dic = $container;
}

public static function getContainer()
{
  if (empty(self::$dic))
  {
    Factory::getApplication()
      ->bootComponent('com_example');
  }

  return self::$dic;
}

Now your Model can simply pull the service from the Component's DIC in its constructor:

<?php
namespace Acme\Component\Example\Administrator\Model;

use Acme\Component\Example\Administrator\Extension\ExampleComponent;
use Acme\Component\Example\Administrator\Service\FacebookPublish;
use Joomla\CMS\Form\FormFactoryInterface;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\CMS\MVC\Model\AdminModel;

class ItemModel extends AdminModel
{
  private $fbPublish;

  public function __construct($config = [], MVCFactoryInterface $factory = null,
    FormFactoryInterface $formFactory = null)
  {
    parent::__construct($config, $factory, $formFactory);

    $exampleDIC      = ExampleComponent::getContainer();
    $this->fbPublish = $exampleDIC->get(FacebookPublish::class);
  }

  protected function getFacebookPublish(): FacebookPublish
  {
    return $this->fbPublish;
  }

  // The rest of your model's code goes here…
}

For completeness' sake, here are there are two (minor) downsides to this approach:

  • The Model now has a direct hard dependency on the component's extension class. For real world use inside Joomla this is not a big deal; the only recommended way to create instances of a component's Models is through its MVCFactory object which necessarily goes through the component's extension object. If you are writing Unit Tests, though, you can no longer isolate the Model. You will need to inject a dependency injection container with your custom service in the component's extension class.

  • The extension class has a static method to fetch the DIC. If this is not already set up it will try to go through the global application object to boot the component. If you are writing Unit Tests this is a problem, hence why I said that you need to inject a DIC to your component's extension class.