Учимся использовать Dependency Injection Containers (DI контейнеры) в Joomla 5

от автора

У меня есть библиотека для работы с АПИ Сдэка. Для работы с ней надо создать экземпляр класса WishboxCdekSDK2\CdekClientV2 и вызвать нужный метод.

$apiClient = new CdekClientV2($account, $secure, $timeout); $apiClient->getCities();

Цель данной статьи получать АПИ-клиент в основном компоненте и плагинах следующим образом:

$apiClient = $this->getCdekClientV2();

или в любом другом расширении:

$apiClient = Joomla\CMS\Factory::getApplication()   ->bootComponent('com_wishboxcdek')->getCdekClientV2(); $apiClient = Joomla\CMS\Factory::getApplication()   ->bootPlugin('wishboxcdek', 'console')->getCdekClientV2();

И в этом нам помогут DI контейнеры.

Давайте пойдём от обратного.

Шаг 1: трейт WishboxCdekSDK2\Trait\CdekClientV2ServiceTrait

Трейт — реализует методы getCdekClientV2 и setCdekClientV2.

Root/libraries/wishboxcdek/src/Trait/CdekClientV2ServiceTrait.php:

<?php /**  * @copyright   (c) 2013-2025 Nekrasov Vitaliy <nekrasov_vitaliy@list.ru>  * @license     GNU General Public License version 2 or later;  */ namespace WishboxCdekSDK2\Trait;  use UnexpectedValueException; use WishboxCdekSDK2\CdekClientV2; use WishboxCdekSDK2\CdekClientV2Interface; use function defined;  // phpcs:disable PSR1.Files.SideEffects defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects  /**  * Defines the trait for the CdekClientV2 service class.  *  * @since  1.0.0  */ trait CdekClientV2ServiceTrait { /**  * The API client.  *  * @var CdekClientV2|null  *  * @since 1.0.0  */ private ?CdekClientV2Interface $cdekClientV2 = null;  /**  * Get the API client.  *  * @return  CdekClientV2Interface  *  * @since   1.0.0  */ public function getCdekClientV2(): CdekClientV2Interface { if (!$this->cdekClientV2) { throw new UnexpectedValueException('CdekClientV2 not set in ' . __CLASS__); }  return $this->cdekClientV2; }  /**  * The API client.  *  * @param   CdekClientV2Interface  $cdekClientV2  CdekClientV2  *  * @return  void  *  * @since  1.0.0  */ public function setCdekClientV2(CdekClientV2Interface $cdekClientV2): void { $this->cdekClientV2 = $cdekClientV2; } } 

Подключим его к классам компонента Root/administrator/components/com_wishboxcdek/src/Extension/WishboxcdekComponent.php и плагина Root/plugins/console/wishboxcdek/src/Extension/Wishboxcdek.php .

Шаг 2: класс сервис-провайдера WishboxCdekSDK2\Service\ProviderCdekClientV2

Сервис-провайдер — реализует интерфейс Joomla\DI\ServiceProviderInterface, в методе register которого в контейнере регистрируется функция, которая создаёт экземпляр АПИ-клиента.

В каждом компоненте или плагине есть файл provider.php с анонимным классом, реализующим интерфейс Joomla\DI\ServiceProviderInterface, используемый для регистрации сервис-провайдеров.

Значит нам нужен сервис-провайдер для нашего класса WishboxCdekSDK2\CdekClientV2.

По аналогии с сервис-провайдерами классов ядра Joomla назовём класс точно так же как называется класс который он будет регистрировать — WishboxCdekSDK2\Service\ProviderCdekClientV2.

Root/libraries/wishboxcdek/src/Service/Provider/CdekClientV2.php

<?php /**  * @copyright   (c) 2013-2025 Nekrasov Vitaliy <nekrasov_vitaliy@list.ru>  * @license     GNU General Public License version 2 or later  */ namespace WishboxCdekSDK2\Service\Provider;  use Joomla\DI\Container; use Joomla\DI\ServiceProviderInterface; use function defined;  // phpcs:disable PSR1.Files.SideEffects defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects  /**  * Service provider for the service MVC factory.  *  * @since  1.0.0  */ class CdekClientV2 implements ServiceProviderInterface { /**  * Аккаунт сервиса интеграции.  *  * @var string  *  * @since 1.0.0  */ private string $account;  /**  * Секретный пароль сервиса интеграции.  *  * @var string  *  * @since 1.0.0  */ private string $secure;  /**  * Timeout  *  * @var float|null  *  * @since 1.0.0  */ private ?float $timeout;  /**  * @param   string       $account  Account  * @param   string|null  $secure   Secure  * @param   float|null   $timeout  Timeout  *  * @since   1.0.0  */ public function __construct(string $account, ?string $secure = null, ?float $timeout = 10.0) { $this->account = $account; $this->secure = $secure; $this->timeout = $timeout; }  /**  * Registers the service provider with a DI container.  *  * @param   Container  $container  The DI container.  *  * @return  void  *  * @since   1.0.0  *  * @noinspection PhpUnusedParameterInspection  */ public function register(Container $container): void { $container->set( \WishboxCdekSDK2\CdekClientV2Interface::class, function (Container $container) { return new \WishboxCdekSDK2\CdekClientV2( $this->account, $this->secure, $this->timeout ); } ); } } 

Теперь перейдём в сервис-провайдер компонента строки 49-59 и 71.

Root/administrator/components/com_wishboxcdek/services/provider.php

<?php /**  * @copyright   (c) 2013-2025 Nekrasov Vitaliy <nekrasov_vitaliy@list.ru>  * @license     GNU General Public License version 2 or later;  */  use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Component\Router\RouterFactoryInterface; use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface; use Joomla\CMS\Extension\ComponentInterface; use Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory; use Joomla\CMS\Extension\Service\Provider\RouterFactory; use Joomla\CMS\HTML\Registry; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\Component\Wishboxcdek\Administrator\Extension\WishboxcdekComponent; use Joomla\Component\Wishboxcdek\Site\CMS\Extension\Service\Provider\MVCFactory; use Joomla\DI\Container; use Joomla\DI\ServiceProviderInterface; use WishboxCdekSDK2\Service\Provider\CdekClientV2;  // phpcs:disable PSR1.Files.SideEffects defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects  /**  * The Wishboxcdek service provider.  *  * @since  1.0.0  */ return new class implements ServiceProviderInterface { /**  * Registers the service provider with a DI container.  *  * @param   Container  $container  The DI container.  *  * @return  void  *  * @since   1.0.0  *  * @noinspection PhpMissingReturnTypeInspection  */ public function register(Container $container) { $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Wishboxcdek')); $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Wishboxcdek')); $container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Wishboxcdek'));          // Получаем параметры компонента $componentParams = ComponentHelper::getParams('com_wishboxcdek');          // Регистрируем класс сервис-провайдера АПИ клиента в контейнере $container->registerServiceProvider( new CdekClientV2( $componentParams->get('account', ''), $componentParams->get('secure', ''), 60.0 ) );  $container->set( ComponentInterface::class, function (Container $container) { $component = new WishboxcdekComponent($container->get(ComponentDispatcherFactoryInterface::class)); $component->setRegistry($container->get(Registry::class)); $component->setMVCFactory($container->get(MVCFactoryInterface::class)); $component->setRouterFactory($container->get(RouterFactoryInterface::class));                  // Устанавливаем АПИ-клиент в класс компонента                 $component->setCdekClientV2($container->get(\WishboxCdekSDK2\CdekClientV2Interface::class));  return $component; } ); } }; 

И аналогично зарегистрируем сервис-провайдер в плагине Root/plugins/console/wishboxcdek/services/provider.php.

Следующие способы получения АПИ-клиента уже должны работать.

$apiClient = Joomla\CMS\Factory::getApplication()  ->bootComponent('com_wishboxcdek')->getCdekClientV2();  $apiClient = Joomla\CMS\Factory::getApplication()  ->bootPlugin('wishboxcdek', 'console')->getCdekClientV2();

Но наша цель $apiClient = $this->getCdekClientV2(); в моделях компонента.

Шаг 3: Интерфейс WishboxCdekSDK2\Interface\CdekClientV2AwareInterface и трейт WishboxCdekSDK2\TraitCdekClientV2AwareTrait

Так же пойдём от обратного, напишем для модели интерфейс, который описывает метод для установки АПИ-клиента setCdekClientV2.

Root/libraries/wishboxcdek/src/Interface/CdekClientV2AwareInterface.php

<?php /**  * @copyright   (c) 2013-2025 Nekrasov Vitaliy <nekrasov_vitaliy@list.ru>  * @license     GNU General Public License version 2 or later  */ namespace WishboxCdekSDK2\Interface;  // phpcs:disable PSR1.Files.SideEffects use WishboxCdekSDK2\CdekClientV2Interface; use function defined;  defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects  /**  * Interface to be implemented by classes depending on a form factory.  *  * @since  1.0.0  */ interface CdekClientV2AwareInterface { /**  * Set the form factory to use.  *  * @param   CdekClientV2  $cdekClientV2  The API client to use.  *  * @return  CdekClientV2AwareInterface  This method is chainable.  *  * @since   1.0.0  */ public function setCdekClientV2(CdekClientV2Interface $cdekClientV2): CdekClientV2AwareInterface; } 

И трейт, который реализует методы setCdekClientV2 и getCdekClientV2.

Root/libraries/wishboxcdek/src/Trait/CdekClientV2AwareTrait.php

<?php /**  * @copyright   (c) 2013-2025 Nekrasov Vitaliy <nekrasov_vitaliy@list.ru>  * @license     GNU General Public License version 2 or later;  */ namespace WishboxCdekSDK2\Trait;  use UnexpectedValueException; use WishboxCdekSDK2\CdekClientV2; use WishboxCdekSDK2\Interface\CdekClientV2AwareInterface; use function defined;  // phpcs:disable PSR1.Files.SideEffects defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects  /**  * Defines the trait for the CdekClientV2 service class.  *  * @since  1.0.0  */ trait CdekClientV2AwareTrait { /**  * The API client.  *  * @var CdekClientV2|null  *  * @since 1.0.0  */ private ?CdekClientV2 $cdekClientV2 = null;  /**  * Get the API client.  *  * @return  CdekClientV2  *  * @throws  UnexpectedValueException May be thrown if the factory has not been set.  *  * @since   1.0.0  */ public function getCdekClientV2(): CdekClientV2Interface { if (!$this->cdekClientV2) { throw new UnexpectedValueException('CdekClientV2 not set in ' . __CLASS__); }  return $this->cdekClientV2; }  /**  * The API client.  *  * @param   CdekClientV2  $cdekClientV2  CdekClientV2  *  * @return  CdekClientV2AwareInterface  *  * @since  1.0.0  */ public function setCdekClientV2(CdekClientV2Interface $cdekClientV2): CdekClientV2AwareInterface { $this->cdekClientV2 = $cdekClientV2;  return $this; } } 

Подключаем интерфейс и трейт к классу модели, теперь она поддерживает методы setCdekClientV2 и getCdekClientV2.

Но кто устанавливает АПИ-клиент в модель?. — Правильно, MVC-фабрика. Давайте вспомним пост Использование своего класса MVC фабрики в компоненте Joomla 5​.

Открываем переопределённый класс MVC-фабрики, находим метод createModel и в конце добвляем следующие строки:

if ($model instanceof CdekClientV2AwareInterface) {     try     {         $model->setCdekClientV2($this->getCdekClientV2());     }     catch (UnexpectedValueException $e)     {         // Ignore it     } }

Мы проверяем наличие у модели интерфейса и если есть, устанавляваем свой АПИ-клиент.

Значит и сам класс MVC-фабрики должен использовать эти интерфейс и трейт.

А в фабрику экземпляр АПИ-клиента при создании устанавливает её сервис-провайдер.

Root/administrator/components/com_wishboxcdek/src/Extension/Service/Provider/MVCFactory.php

<?php /**  * @copyright   (c) 2013-2025 Nekrasov Vitaliy <nekrasov_vitaliy@list.ru>  * @license     GNU General Public License version 2 or later; see LICENSE.txt  */ 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\Database\DatabaseInterface; use Joomla\DI\Container; use Joomla\DI\ServiceProviderInterface; use Joomla\Event\DispatcherInterface; use WishboxCdekSDK2\CdekClientV2Interface; use function defined;  // phpcs:disable PSR1.Files.SideEffects defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects  /**  * Service provider for the service MVC factory.  *  * @since  1.0.0  */ class MVCFactory extends \Joomla\CMS\Extension\Service\Provider\MVCFactory implements ServiceProviderInterface { /**  * The extension namespace  *  * @var  string  *  * @since   1.0.0  */ private $namespace;  /**  * MVCFactory constructor.  *  * @param   string  $namespace  The namespace  *  * @since   1.0.0  */ public function __construct(string $namespace) { parent::__construct($namespace);  $this->namespace = $namespace; }  /**  * Registers the service provider with a DI container.  *  * @param   Container  $container  The DI container.  *  * @return  void  *  * @since   1.0.0  */ public function register(Container $container): void { $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->setCdekClientV2($container->get(CdekClientV2Interface::class));  return $factory; } ); } } 

Готово! Теперь в модели работает код $apiClient = $this->getCdekClientV2();.


ссылка на оригинал статьи https://habr.com/ru/articles/905258/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *