Как стать автором
Обновить

Dependency Injection Containers (DI контейнеры) в Joomla 4

Время на прочтение5 мин
Количество просмотров1.5K
Автор оригинала: Manual Joomla org

Этот текст - перевод статьи из нового портала документации для разработчиков 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.

От переводчика

Замечания и пожелания по переводу приветствуются. Так же полезные ресурсы.

Ресурсы сообщества:

Telegram:

Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
+1
Комментарии0

Публикации

Изменить настройки темы

Истории

Работа

PHP программист
162 вакансии

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн