Pull to refresh

Использование «фабрики» в своих расширениях для Joomla

Level of difficultyMedium
Reading time10 min
Views48

Мы будем использовать фабрику для создания объектов и установки в них зависимостей.

Какие интерфейсы фабрик есть в 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();

Tags:
Hubs:
-1
Comments0

Articles