Этот текст - перевод статьи из нового портала документации для разработчиков Joomla, раздел "Основные концепции". Перевод в начале был опубликован на Хабре.

Введение

Joomla 4 внедряет практику контейнеров внедрения зависимостей (DI контейнеры, DIC) в Joomla. Эта статья призвана объяснить, почему мы внедряем их и как их использовать в Joomla.

DI контейнеры уже давно существуют в экосистеме PHP для поддержки целей внедрения зависимостей. Например, Symfony представила эту концепцию в 2009 году.

Есть несколько причин, по которым пришло время внедрить их в Joomla 4:

  1. Тестирование — одной из тем Joomla 3 были глючные релизы. Нам нужно иметь возможность тестировать классы и компоненты более простым способом. Внедрение зависимостей позволяет значительно упростить внедрение классов Mock, что, мы надеемся, позволит нам уменьшить количество ошибок.

  2. Нужно уменьшить количество магии в Joomla - Joomla имеет большое количество "волшебных" файлов, названия которых нужно угадывать. Это увеличивает количество времени, которое люди, плохо знакомые с Joomla, тратят на изучение соглашений по именованию файлов. Предоставление конкретного класса в расширениях позволяет нам легко тестировать совместимость расширений с другими расширениями (например, категориями и ассоциациями).

Глобальный контейнер

Внедрение глобального контейнера зависимостей очень слабо заменяет класс Factory (ex. JFactory). Однако его не следует путать с прямой заменой.

Так, например, в ваших контроллерах в CMS вместо

\Joomla\CMS\Factory::getDocument();

стоит использовать

$this->app->getDocument();

Это использует внедренное приложение и поэтому упрощает тестирование.

Ссылки по теме на Хабре:

Создание объекта в контейнере

Чтобы поместить что-то в глобальном DI-контейнере Joomla проще всего передать анонимную функцию. Пример для логгера ниже:

 

// Assuming we have an instance of a Joomla Container
$container->share(
    LoggerInterface::class,
    function (Container $container)
    {
        return \Joomla\CMS\Log\Log::createDelegatedLogger();
    },
    true
);

Функция share принимает два обязательных параметра и необязательный третий параметр:

  • $key - имя сервиса (dataStore key) - почти всегда является именем класса, который вы создаете.

  • $value - Анонимная функция принимает единственный параметр — экземпляр контейнера (это позволяет вам получать любые зависимости из контейнера). return — это сервис, который вы хотите поместить в контейнер.

  • $protected - (необязательный параметр) - это булев параметр, определяет, защищена ли служба от перезаписи (т. е. разрешено ли кому-либо еще переопределять ее в контейнере). Как правило, для основных служб Joomla, таких как объекты сессии (Session), это true.

Теперь рассмотрим более сложный пример:

$container->alias('AmazingApiRouter', Joomla\CMS\Router\ApiRouter::class)
    ->share(
    \Joomla\CMS\Router\ApiRouter::class,
    function (Container $container)
    {
        return new \Joomla\CMS\Router\ApiRouter($container->get(\Joomla\CMS\Application\ApiApplication::class));
    },
    true
);

Здесь видно, что мы добавили две вещи — начали использовать зависимости (роутер API получает приложение API из контейнера) и мы также создали алиас для ApiRouter (в Joomla 4 существует 5 типов приложений - Application - Site, Administrator, Cli, API и Installation, а также могут быть созданы свои типы - Т.С.). Это означает, что контейнер создает экземпляр ApiRouter тогда, когда распознает использование класса  Зато в нашем коде для простоты мы сможем запустить следующий вызов, чтобы получить наш роутер (That means whilst the container recognises that if it needs to build an ApiRouter instance it can do that. But in our code to keep things simple we can also run to retrieve our router). 

Factory::getContainer()->get('AmazingApiRouter');

В то время как в Joomla наши провайдеры могут выглядеть более сложными, потому что логика создания объектов внутри анонимной функции более сложна - все они следуют этой базовой идее.

Провайдеры

Провайдеры в Joomla — это способ регистрации зависимости в сервис-контейнере. Для этого создайте класс, реализующий Joomla\DI\ServiceProviderInterface.

Это дает вам метод регистрации, который содержит контейнер. Затем вы можете снова использовать метод share, чтобы добавить любое количество объектов в контейнер. Затем вы можете зарегистрировать их в контейнере с помощью \Joomla\DI\Container::registerServiceProvider . Вы можете посмотреть, как мы регистрируем все сервис-провайдеры, здесь, в методе \Joomla\CMS\Factory::createContainer.

// libraries/src/Factory.php
/**
     * Create a container object
     *
     * @return  Container
     *
     * @since   4.0.0
     */
    protected static function createContainer(): Container
    {
        $container = (new Container())
            ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Application())
            ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Authentication())
            ->registerServiceProvider(new \Joomla\CMS\Service\Provider\CacheController())
            ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Config())
            ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Console())
            ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Database())
            ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Dispatcher())
            ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Document())
            ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Form())
            ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Logger())
            ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Language())
            ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Menu())
            ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Pathway())
            ->registerServiceProvider(new \Joomla\CMS\Service\Provider\HTMLRegistry())
            ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Session())
            ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Toolbar())
            ->registerServiceProvider(new \Joomla\CMS\Service\Provider\WebAssetRegistry())
            ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Router())
            ->registerServiceProvider(new \Joomla\CMS\Service\Provider\User());

        return $container;
    }

Контейнер компонента

Каждый компонент также имеет свой собственный контейнер (который находится в разделе администратора (administrator section) Joomla). Однако этот контейнер не подвергается воздействию. Он нужен только для того, чтобы получить системные зависимости и позволить классу представлять ваше расширение. Этот класс является классом Extension и как минимум должен реализовывать интерфейс соответствующего типа расширения. Например, компонент должен реализовать \Joomla\CMS\Extension\ComponentInterface (libraries/src/Extension/ComponentInterface.php). Для получения полной информации о реализации в Вашем расширении мы рекомендуем обратиться к официальной документации Joomla «Разработка компонента MVC для Joomla 4».

Использование контейнера компонента в другом расширении

Вы можете легко получить контейнер другого расширения через объект CMSApplication. Например

Factory::getApplication()->bootComponent('com_content')->getMVCFactory()->createModel('Articles', 'Site');

Получите контейнер com_content, получите MVC Factory и получите ArticlesModel фронтенда Joomla. И это будет работать в любом расширении во фронтенде, бэкэнде или API Joomla (в отличие от старого метода LegacyModel::getInstance()).

Дополнительно

В документации Joomla Framework есть отличный пример того, почему внедрение зависимостей полезно для вашего приложения и как DIC помогает его структурировать. Читать на GitHub.

 

Толкачев Сергей Юрьевич
Толкачев Сергей Юрьевич

Joomla-разработчик. Контрибьютер ядра Joomla. Один из ведущих Telegram-канала русскоязычного Joomla-сообщества JoomlaFeed, один из модераторов чата русскоязычного Joomla-сообщества. Мои расширения в официальном маркетплейсе расширений Joomla - Joomla Extensions Directory. Имею публикации в официальном журнале международного Joomla-сообщества - Joomla Community Magazine.

Муж. Отец 3 детей.

Россия, Саратов.

Расширения Joomla WebTolk

85 Всего расширений
11 Категорий
354 Выпущено версий
348928 Всего скачиваний
Корзина
Корзина пуста