05 - Регистрация Зависимостей
Последним аспектом понимания файлов поставщика услуг является регистрация зависимостей. Двухэтапный процесс загрузки расширений, который был частично описан в предыдущем разделе, на самом деле выглядит следующим образом:
- Класс расширения вводится в дочерний DIC, и все зависимости класса расширения регистрируются (т. е. вводятся в дочерний DIC).
- Класс расширения извлекается из дочернего DIC, создавая экземпляр класса. И любые подчиненные зависимости класса Extension извлекаются из DIC, при этом экземпляр Extension хранит указатели на эти зависимые объекты на случай, когда они ему понадобятся.
Минимальный размер файла services/provider.php
Давайте рассмотрим минимальный пример для com_example,с пространством имен Mycompany\Component\Example. В этом случае мы предположим, что com_example не нуждается в собственном классе Extension
и может просто использовать поставляемый Joomla класс MVCComponent
как его Extension
. Итак, этот файл services/provider.php настолько минималистичен, насколько это возможно для базового компонента.
use Joomla\CMS\Extension\ComponentInterface; use Joomla\CMS\Extension\MVCComponent; use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\DI\Container; use Joomla\DI\ServiceProviderInterface; return new class implements ServiceProviderInterface { public function register(Container $container): void { $container->registerServiceProvider(new Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory('\\Mycompany\\Component\\Example')); $container->registerServiceProvider(new Joomla\CMS\Extension\Service\Provider\MVCFactory('\\Mycompany\\Component\\Example')); $container->set( ComponentInterface::class, function (Container $container) { $component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class)); $component->setMVCFactory($container->get(MVCFactoryInterface::class)); return $component; } ); } };
Здесь мы включили несколько дополнительных строк в файл services/provider.php, которые относятся к классам, которые будут необходимы экземпляру расширения com_example Extension
- для создания экземпляра класса Dispatcher потребуется DispatcherFactory, а затем Joomla вызовет
dispatch()
на этом объекте диспетчера, в качестве следующего шага в запуске компонента - MVCFactory потребуется для создания экземпляров класса Controller, View, Model и Table от имени компонента.
Два вызова registerServiceProvider
приводят к загрузке двух зависимостей в дочерний DIC.
Затем при создании экземпляров компонент получает get
для получения экземпляров этих двух фабричных классов от дочернего DIC.
Предупреждение о дублирующем классе!
Внимание! Имейте в виду, что здесь есть имена классов, которые имеют одинаковую конечную часть FQN:
ComponentDispatcherFactory - Фабрика компонентов:
- может ссылаться на \Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory в libraries/src/Service/Provider/ComponentDispatcherFactory.php
- а также может ссылаться на \Joomla\CMS\Dispatcher\ComponentDispatcherFactory в libraries/src/Dispatcher/ComponentDispatcherFactory.php.
Фабрика MVCFactory:
- можете обратиться к \Joomla\CMS\Extension\Service\Provider\MVCFactory в libraries/src/Service/Provider/MVCFactory.php
- а также может ссылаться на \Joomla\CMS\MVC\Factory\MVCFactory в libraries/src/MVC/Factory/MVCFactory.php.
В каждом случае последний является истинным "заводским" классом, первый - классом поставщика услуг, который позволяет создавать экземпляры истинного заводского класса через DIC. Чтобы попытаться уменьшить путаницу, полные учетные данные классов поставщиков услуг используются выше.
Разрешение зависимости ComponentDispatcherFactory
На втором шаге, когда Joomla вызывает get(ComponentInterface::class)
создан экземпляр класса расширения (класс MVCComponent в этом примере) и разрешены зависимости.
Давайте сначала посмотрим на ComponentDispatcherFactory
$component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
Код $container->get(ComponentDispatcherFactoryInterface::class)
запускает функцию в дочернем DIC, связанную с ключом ComponentDispatcherFactoryInterface::class. Эта функция найдена в libraries/src/Extension/Service/Provider/ComponentDispatcherFactory.php, и возвращаемый объект:
new \Joomla\CMS\Dispatcher\ComponentDispatcherFactory($this->namespace, $container->get(MVCFactoryInterface::class))
передается в конструктор MVCComponent в качестве параметра. Класс компонента Joomla\CMS\Extension\MVCComponent наследуется от Joomla\CMS\Extension\Component, и в конструкторе последнего он хранит ссылку на переданный ComponentDispatcherFactory:
public function __construct(ComponentDispatcherFactoryInterface $dispatcherFactory) { $this->dispatcherFactory = $dispatcherFactory; }
И у него тоже есть функция getDispatcher
которая извлекает его и использует для создания экземпляра класса Dispatcher:
public function getDispatcher(CMSApplicationInterface $application): DispatcherInterface { return $this->dispatcherFactory->createDispatcher($application); }
Давайте еще раз взглянем на строку при создании экземпляра ComponentDispatcherFactory:
new \Joomla\CMS\Dispatcher\ComponentDispatcherFactory($this->namespace, $container->get(MVCFactoryInterface::class))
Здесь код также извлекает экземпляр MVCFactory из дочернего DIC; ComponentDispatcherFactory, в свою очередь, зависит от MVCFactory. Почему это? Как описано в документации диспетчера всякий раз, когда в диспетчере вызывается функция dispatch
, которая анализирует параметр task URL-адреса, чтобы решить, какой класс контроллера создать. Таким образом, он сохраняет объект MVCFactory, переданный в его конструкторе, чтобы позже использовать его для создания класса контроллера:
$controller = $this->mvcFactory->createController(...); $controller->execute($task);
Таким образом, ComponentDispatcherFactory извлекает MVCFactory из DIC, сохраняет ссылку на него, а затем передает ее классу Dispatcher при создании его экземпляра. Его код находится в libraries/src/Dispatcher/ComponentDispatcherFactory.php.
Разрешение зависимости MVCFactory
Это следует шаблону, аналогичному шаблону зависимости ComponentDispatcherFactory.
Зависимость регистрируется в строке:
$container->registerServiceProvider(new Joomla\CMS\Extension\Service\Provider\MVCFactory('\\Mycompany\\Component\\Example'));
который вызывает функцию register
в libraries/src/Extension/Service/Provider/MVCFactory.php для запуска на экземпляре этого класса. Если вы посмотрите на этот файл, вы увидите, что MVCFactory имеет несколько зависимостей, все из которых будут разрешены путем получения записей из родительского DIC.
Второй шаг включает в себя создание экземпляра класса расширения и разрешение его зависимостей:
$component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class)); $component->setMVCFactory($container->get(MVCFactoryInterface::class));
В первой строке создается экземпляр класса расширения (MVCComponent), при этом экземпляр ComponentDispatchFactory передается в конструктор (как мы видели выше).
Во второй строке приведенный код $container->get(MVCFactoryInterface::class
получает экземпляр MVCFactory из дочернего DIC, а затем ссылка на него сохраняется локально через передачу в setMVCFactory
. Эта функция доступна для MVCComponent
через признак Joomla\CMS\MVC\Factory\MVCFactoryServiceTrait, используемый в MVCComponent и который содержит строки:
public function setMVCFactory(MVCFactoryInterface $mvcFactory) { $this->mvcFactory = $mvcFactory; }
Параметры пространств имен
Вы, наверное, заметили, что пространство имен '\Mycompany\Component\Example' передается в качестве параметра в конструкторы нескольких классов. (Кстати, это точно такая же строка PHP, как и ее эквивалент, с двойной обратной косой чертой вместо одинарной обратной косой черты).
Причина этого заключается в том, что эти экземпляры заводского класса несут ответственность за создание различных классов от имени компонента:
- ComponentDispatcherFactory создаст экземпляр класса Dispatcher
- MVCFactory создаст экземпляры классов Controller, View, Model и Table
Например, в функции createDispatcher
ComponentDispatcherFactory проверяет, существует ли определенный класс - диспетчер для com_example, формируя имя класса:
$className = '\\' . trim($this->namespace, '\\') . '\\' . $name . '\\Dispatcher\\Dispatcher';
(где $name
является "Site" или "Administrator") и проверяет, существует ли этот класс. Если это не так, то создается экземпляр класса Joomla \ CMS \ Dispatcher \ComponentDispatcher по умолчанию.
Аналогично, MVCFactory использует пространство имен для определения полных имен классов com_example Controller, View, Model и Table, чтобы создать их экземпляры.
Это одна из причин, по которой необходим дочерний DIC. В процессе ответа на HTTP-запрос Joomla создаст экземпляры нескольких расширений, каждое со своими зависимостями. Поскольку Joomla использует общие ключи – например, ComponentInterface::class для компонентов – дочерние DIC необходимы для разделения записей для каждого расширения.