Мы будем использовать фабрику для создания объектов и установки в них зависимостей.
Какие интерфейсы фабрик есть в Joomla 5 и какие задачи они выполняют?
Joomla\CMS\Cache\CacheControllerFactoryInterface
Создание кэш-контроллера;
Joomla\CMS\Categories\CategoryFactoryInterface
Создание категории;
Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface
Создание диспечера;
Joomla\CMS\Dispatcher\ModuleDispatcherFactoryInterface
Создание диспечер модуля;
Joomla\CMS\Document\FactoryInterface
Создание документа;
Создание рендерера документа;
Joomla\CMS\Form\FormFactoryInterface
Создание формы;
Joomla\CMS\Helper\HelperFactoryInterface
Получение хелпера;
Joomla\CMS\Language\LanguageFactoryInterface
Создание языка;
Joomla\CMS\Mail\MailerFactoryInterface
Создание почтового сервиса;
Joomla\CMS\Menu\MenuFactoryInterface
Создание меню;
Joomla\CMS\MVC\Factory\MVCFactoryInterface
Создание контроллера;
Создание модели;
Создание представления;
Создание таблицы;
Joomla\CMS\Toolbar\ToolbarFactoryInterface
Создание кнопки;
Создание тулбара;
Joomla\CMS\User\UserFactoryInterface
Загружает пользователя по Id;
Загружает пользователя по имени;
Joomla\CMS\Component\Router\RouterFactoryInterface
Создание роутера;
Joomla\Plugin\Authentication\Ldap\Factory\LdapFactory
Создание Ldap клиента.
Это не полный список фабрик, но из него видим что задача фабрик - «создавать».
Как сделать свою фабрику для своего типа объектов?
Рассмотрим пример создания класса-фабрики для АПИ-клиента службы доставки Сдэк — CdekClientV2Factory
. В результате, в моделях компонента и плагинах мы сможем получать АПИ-клиент следующим способом:
$apiClient = $this->getCdekClientV2Factory()->createDefaultClient();
Для этого нам потребуется создать несколько классов:
Интерфейс фабрики:
Root/libraries/wishboxcdek/src/Factory/CdekClientV2FactoryInterface.php
Описывает единственный метод, предназначенный для создания экземпляра АПИ-клиента с параметрами по-умолчанию. При необходимости можно добавить другие методы.
namespace WishboxCdekSDK2\Factory;
use WishboxCdekSDK2\CdekClientV2Interface;
use function defined;
defined('_JEXEC') or die;
interface CdekClientV2FactoryInterface
{
public function createDefaultClient(): CdekClientV2Interface;
}
Класс фабрики
Root/libraries/wishboxcdek/src/Factory/CdekClientV2Factory.php
Реализует метод для создания АПИ-клиента.
namespace WishboxCdekSDK2\Factory;
use Joomla\CMS\Cache\CacheControllerFactoryAwareTrait;
use Joomla\CMS\Component\ComponentHelper;
use WishboxCdekSDK2\CdekClientV2;
use WishboxCdekSDK2\CdekClientV2Interface;
class CdekClientV2Factory implements CdekClientV2FactoryInterface
{
use CacheControllerFactoryAwareTrait;
public function createDefaultClient(): CdekClientV2Interface
{
$componentParams = ComponentHelper::getParams('com_wishboxcdek');
$client = new CdekClientV2(
$componentParams->get('account', ''),
$componentParams->get('secure', ''),
60.0
);
$client->setCacheControllerFactory($this->getCacheControllerFactory());
return $client;
}
}
Класс сервис-провайдера для класса фабрики:
Root/libraries/wishboxcdek/src/Service/Provider/CdekClientV2Factory.php
Регистрирует в контейнере метод для получения экземпляра фабрики.
namespace WishboxCdekSDK2\Service\Provider;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use WishboxCdekSDK2\Factory\CdekClientV2FactoryInterface;
use function defined;
defined('_JEXEC') or die;
class CdekClientV2Factory implements ServiceProviderInterface
{
public function register(Container $container): void
{
$container->set(
CdekClientV2FactoryInterface::class,
function (Container $container)
{
return new \WishboxCdekSDK2\Factory\CdekClientV2Factory;
}
);
}
}
Aware-интерфейс фабрики
Root/libraries/wishboxcdek/src/Factory/CdekClientV2FactoryAwareInterface.php
Описывает геттер и сеттер для установки экземпляра фабрики в экземпляр какого-либо объекта. Этот интерфейс должны будут реализовать классы, которые будут работать с нашей фабрикой.
namespace WishboxCdekSDK2\Factory;
use function defined;
defined('_JEXEC') or die;
interface CdekClientV2FactoryAwareInterface
{
public function setCdekClientV2Factory(CdekClientV2FactoryInterface $cdekClientV2Factory): CdekClientV2FactoryAwareInterface;
public function getCdekClientV2Factory(): CdekClientV2FactoryInterface;
}
Aware-трейт фабрики
Root/libraries/wishboxcdek/src/Factory/CdekClientV2FactoryAwareInterface.php
Реализует геттер и сеттер для установки экземпляра фабрики в экземпляр какого-либо объекта. Будет использоваться вместе с Aware-интерфейсом.
namespace WishboxCdekSDK2\Factory;
use UnexpectedValueException;
use function defined;
defined('_JEXEC') or die;
trait CdekClientV2FactoryAwareTrait
{
private ?CdekClientV2FactoryInterface $cdekClientV2Factory = null;
public function getCdekClientV2Factory(): CdekClientV2FactoryInterface
{
if (!$this->cdekClientV2Factory)
{
throw new UnexpectedValueException('CdekClientV2Factory not set in ' . __CLASS__);
}
return $this->cdekClientV2Factory;
}
public function setCdekClientV2Factory(CdekClientV2FactoryInterface $cdekClientV2Factory): CdekClientV2FactoryAwareInterface
{
$this->cdekClientV2Factory = $cdekClientV2Factory;
return $this;
}
}
Как подготовить расширения для использования своей фабрики
Плагины:
Подключаем к классу плагина Aware-трейт фабрики.
В сервис-провайдере плагина provider.php
регистрируем сервис-провайдер фабрики: строка: 17
и устанавливаем экземпляр фабрики в экземпляр плагина: строка: 28
.
use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use Joomla\Plugin\RadicalMart\WishboxCdekPackageMultiple\Extension\WishboxCdekPackageMultiple;
use WishboxCdekSDK2\Factory\CdekClientV2FactoryInterface;
use WishboxCdekSDK2\Service\Provider\CdekClientV2Factory;
defined('_JEXEC') or die;
return new class implements ServiceProviderInterface
{
public function register(Container $container): void
{
$container->registerServiceProvider(new CdekClientV2Factory);
$container->set(
PluginInterface::class,
function (Container $container)
{
$dispatcher = $container->get(DispatcherInterface::class);
$config = (array) PluginHelper::getPlugin('radicalmart', 'wishboxcdekpackagemultiple');
$plugin = new WishboxCdekPackageMultiple($dispatcher, $config);
$plugin->setApplication(Factory::getApplication());
$plugin->setCdekClientV2Factory($container->get(CdekClientV2FactoryInterface::class));
return $plugin;
}
);
}
};
Теперь в классе плагина доступен экземпляр фабрики $this->getCdekClientV2Factory()
.
В компоненте
В сервис-провайдере компонента provider.php
и классе-расширения компонента надо выполнить тоже самое что и в плагине.
В MVC-фабрике компонента
Экземпляры моделей компонента обычно создаются с помощью MVC-фабрики, нам потребуется её переопределить, о том как это сделать я рассказывал в посте.
Для того что-бы MVC-фабрика могла устанавливать экземпляр нашей фабрики в модели, она сама должна им владеть.
Добавим в сервис-провайдер MVC-фабрики регистрацию сервис-провайдера нашей фабрики в контейнере строка: 35
и установим в MVC-фабрику экземпляр нашей фабрики строка: 58
.
namespace Joomla\Component\WishboxCdek\Administrator\Extension\Service\Provider;
use Joomla\CMS\Cache\CacheControllerFactoryInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormFactoryInterface;
use Joomla\CMS\Mail\MailerFactoryInterface;
use Joomla\CMS\MVC\Factory\ApiMVCFactory;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\CMS\Router\SiteRouter;
use Joomla\CMS\User\UserFactoryInterface;
use Joomla\Component\WishboxCdek\Administrator\Factory\RequestFactoryInterface;
use Joomla\Database\DatabaseInterface;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use WishboxCdekSDK2\Factory\CdekClientV2FactoryInterface;
use WishboxCdekSDK2\Service\Provider\CdekClientV2Factory;
use function defined;
defined('_JEXEC') or die;
class MVCFactory extends \Joomla\CMS\Extension\Service\Provider\MVCFactory implements ServiceProviderInterface
{
private $namespace;
public function __construct(string $namespace)
{
parent::__construct($namespace);
$this->namespace = $namespace;
}
public function register(Container $container): void
{
$container->registerServiceProvider(new CdekClientV2Factory);
$container->registerServiceProvider(new RequestFactory);
$container->set(
MVCFactoryInterface::class,
function (Container $container)
{
if (Factory::getApplication()->isClient('api'))
{
$factory = new ApiMVCFactory($this->namespace);
}
else
{
$factory = new \Joomla\Component\WishboxCdek\Administrator\MVC\Factory\MVCFactory($this->namespace);
}
$factory->setFormFactory($container->get(FormFactoryInterface::class));
$factory->setDispatcher($container->get(DispatcherInterface::class));
$factory->setDatabase($container->get(DatabaseInterface::class));
$factory->setSiteRouter($container->get(SiteRouter::class));
$factory->setCacheControllerFactory($container->get(CacheControllerFactoryInterface::class));
$factory->setUserFactory($container->get(UserFactoryInterface::class));
$factory->setMailerFactory($container->get(MailerFactoryInterface::class));
$factory->setCdekClientV2Factory($container->get(CdekClientV2FactoryInterface::class));
$factory->setRequestFactory($container->get(RequestFactoryInterface::class));
return $factory;
}
);
}
}
Теперь перейдём в методу createModel
MVC-фабрики. Перед возвращением экземпляра модели MVC-фабрика выполняет серию проверок и установку зависимостей. Добавим проверку на реализацию моделью aware-интерефейса нашей фабрики CdekClientV2FactoryAwareInterface
и установку экземпляра строки:190-200
.
namespace Joomla\Component\WishboxCdek\Administrator\MVC\Factory;
use Exception;
use Joomla\CMS\Cache\CacheControllerFactoryAwareInterface;
use Joomla\CMS\Cache\CacheControllerFactoryAwareTrait;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormFactoryAwareInterface;
use Joomla\CMS\Form\FormFactoryAwareTrait;
use Joomla\CMS\Mail\MailerFactoryAwareInterface;
use Joomla\CMS\Mail\MailerFactoryAwareTrait;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\CMS\MVC\Model\ModelInterface;
use Joomla\CMS\Router\SiteRouterAwareInterface;
use Joomla\CMS\Router\SiteRouterAwareTrait;
use Joomla\CMS\User\UserFactoryAwareInterface;
use Joomla\CMS\User\UserFactoryAwareTrait;
use Joomla\Component\WishboxCdek\Administrator\Factory\RequestFactoryAwareInterface;
use Joomla\Component\WishboxCdek\Administrator\Factory\RequestFactoryAwareTrait;
use Joomla\Database\DatabaseAwareInterface;
use Joomla\Database\DatabaseAwareTrait;
use Joomla\Database\DatabaseInterface;
use Joomla\Database\Exception\DatabaseNotFoundException;
use Joomla\Event\DispatcherAwareInterface;
use Joomla\Event\DispatcherAwareTrait;
use UnexpectedValueException;
use WishboxCdekSDK2\Factory\CdekClientV2FactoryAwareInterface;
use WishboxCdekSDK2\Factory\CdekClientV2FactoryAwareTrait;
use function defined;
use function sprintf;
defined('_JEXEC') or die;
class MVCFactory extends \Joomla\CMS\MVC\Factory\MVCFactory implements MVCFactoryInterface,
FormFactoryAwareInterface, SiteRouterAwareInterface, UserFactoryAwareInterface, MailerFactoryAwareInterface, CdekClientV2FactoryAwareInterface
{
use FormFactoryAwareTrait;
use DispatcherAwareTrait;
use DatabaseAwareTrait;
use SiteRouterAwareTrait;
use CacheControllerFactoryAwareTrait;
use UserFactoryAwareTrait;
use MailerFactoryAwareTrait;
use CdekClientV2FactoryAwareTrait;
use RequestFactoryAwareTrait;
public function createModel($name, $prefix = '', array $config = [])
{
// Clean the parameters
$name = preg_replace('/[^A-Z0-9_]/i', '', $name);
$prefix = preg_replace('/[^A-Z0-9_\\\\]/i', '', $prefix);
if (!$prefix)
{
@trigger_error(
sprintf(
'Calling %s() without a prefix is deprecated.',
__METHOD__
),
E_USER_DEPRECATED
);
$prefix = Factory::getApplication()->getName();
}
if (!mb_strpos($prefix, "\\"))
{
$className = $this->getClassName('Model\\' . ucfirst($name) . 'Model', $prefix);
}
else
{
$className = $this->getClassName(ucfirst($name) . 'Model', $prefix);
}
if (!$className)
{
return null;
}
$model = new $className($config, $this);
if ($model instanceof FormFactoryAwareInterface)
{
try
{
$model->setFormFactory($this->getFormFactory());
}
catch (UnexpectedValueException $e)
{
// Ignore it
}
}
if ($model instanceof DispatcherAwareInterface)
{
try
{
$model->setDispatcher($this->getDispatcher());
}
catch (UnexpectedValueException $e)
{
// Ignore it
}
}
if ($model instanceof DispatcherAwareInterface)
{
try
{
$model->setDispatcher($this->getDispatcher());
}
catch (UnexpectedValueException $e)
{
// Ignore it
}
}
if ($model instanceof SiteRouterAwareInterface)
{
try
{
$model->setSiteRouter($this->getSiteRouter());
}
catch (UnexpectedValueException $e)
{
// Ignore it
}
}
if ($model instanceof CacheControllerFactoryAwareInterface)
{
try
{
$model->setCacheControllerFactory($this->getCacheControllerFactory());
}
catch (UnexpectedValueException $e)
{
// Ignore it
}
}
if ($model instanceof CacheControllerFactoryAwareInterface)
{
try
{
$model->setCacheControllerFactory($this->getCacheControllerFactory());
}
catch (UnexpectedValueException $e)
{
// Ignore it
}
}
if ($model instanceof UserFactoryAwareInterface)
{
try
{
$model->setUserFactory($this->getUserFactory());
}
catch (UnexpectedValueException $e)
{
// Ignore it
}
}
if ($model instanceof MailerFactoryAwareInterface)
{
try
{
$model->setMailerFactory($this->getMailerFactory());
}
catch (UnexpectedValueException $e)
{
// Ignore it
}
}
if ($model instanceof DatabaseAwareInterface)
{
try
{
$model->setDatabase($this->getDatabase());
}
catch (DatabaseNotFoundException $e)
{
@trigger_error('Database must be set, this will not be caught anymore in 5.0.', E_USER_DEPRECATED);
$model->setDatabase(Factory::getContainer()->get(DatabaseInterface::class));
}
}
if ($model instanceof CdekClientV2FactoryAwareInterface)
{
try
{
$model->setCdekClientV2Factory($this->getCdekClientV2Factory());
}
catch (UnexpectedValueException $e)
{
// Ignore it
}
}
if ($model instanceof RequestFactoryAwareInterface)
{
try
{
$model->setRequestFactory($this->getRequestFactory());
}
catch (UnexpectedValueException $e)
{
// Ignore it
}
}
return $model;
}
}
В моделях компонента
Классам модели теперь надо добавить CdekClientV2FactoryAwareInterface
и CdekClientV2FactoryAwareTrait
.
Готово! Теперь мы можем получать объект фабрики и создавать экземпляр АПИ-клмента с параметрами по-умолчанию: $apiCient = $this->getCdekClientV2Factory()->createDefaultClient();