注册子依赖项
理解服务提供程序文件的最后一个方面是注册子依赖项。加载扩展的两步过程(在上一节中部分描述过)实际上如下所示
- 扩展类被输入到子 DIC 中,并且扩展类的任何依赖项都已注册(即输入到子 DIC 中)
- 从子 DIC 中检索扩展类,创建类的实例。并且从 DIC 中检索扩展类的任何子依赖项,扩展实例在需要时将存储指向这些依赖对象的指针。
最小的 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
实例将需要的类有关
- 将需要 DispatcherFactory 来创建 Dispatcher 类实例,然后 Joomla 将在此 Dispatcher 对象上调用
dispatch()
,作为运行组件的下一步 - 将需要 MVCFactory 代表组件创建 Controller、View、Model 和 Table 类实例。
这两个 registerServiceProvider
调用导致这两个依赖项加载到子 DIC 中。
然后在实例化组件时,会进行 get
调用以从子 DIC 中获取这两个 Factory 类的实例。
重复类警告!
警告!请注意,这里有一些类名具有相同的 FQN 后半部分
ComponentDispatcherFactory
- 可以指 libraries/src/Service/Provider/ComponentDispatcherFactory.php 中的 \Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory
- 也可以指 libraries/src/Dispatcher/ComponentDispatcherFactory.php 中的 \Joomla\CMS\Dispatcher\ComponentDispatcherFactory。
MVCFactory
- 可以指 libraries/src/Service/Provider/MVCFactory.php 中的 \Joomla\CMS\Extension\Service\Provider\MVCFactory
- 也可以指 libraries/src/MVC/Factory/MVCFactory.php 中的 \Joomla\CMS\MVC\Factory\MVCFactory。
在每种情况下,后者都是真正的“工厂”类,前者是服务提供程序类,它使真正的工厂类能够通过 DIC 实例化。为了尽量减少混淆,上面使用了服务提供程序类的 FQN。
解析 ComponentDispatcherFactory 依赖项
在第二步中,当 Joomla 调用 get(ComponentInterface::class)
时,将实例化扩展类(在此示例中为 MVCComponent 类)并解析依赖项。
让我们先看看 ComponentDispatcherFactory
$component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
代码 $container->get(ComponentDispatcherFactoryInterface::class)
运行与 ComponentDispatcherFactoryInterface::class 键关联的子 DIC 中的函数。此函数位于 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))
在这里,代码还从子 DIC 中获取 MVCFactory 实例;ComponentDispatcherFactory 反过来依赖于 MVCFactory。这是为什么?如调度程序文档中所述,每当调用 Dispatcher 的 dispatch
函数时,它都会分析 URL 的 task 参数,以决定要实例化哪个 Controller 类。因此,它存储在构造函数中传递的 MVCFactory 对象,以便以后可以使用它来创建 Controller 类
$controller = $this->mvcFactory->createController(...);
$controller->execute($task);
因此,ComponentDispatcherFactory 从 DIC 中获取 MVCFactory,存储对它的引用,然后在实例化它时将其传递给 Dispatcher 类。其代码位于 libraries/src/Dispatcher/ComponentDispatcherFactory.php 中。
解析 MVCFactory 依赖项
这遵循与 ComponentDispatcherFactory 依赖项类似的模式。
依赖项使用以下代码行注册
$container->registerServiceProvider(new Joomla\CMS\Extension\Service\Provider\MVCFactory('\\Mycompany\\Component\\Example'));
这会导致 libraries/src/Extension/Service/Provider/MVCFactory.php 中的 register
函数在该类的实例上运行。如果您查看此文件,您会发现 MVCFactory 有几个依赖项,所有这些依赖项都将通过从父 DIC 中获取条目来解析。
第二步涉及实例化扩展类并解析其依赖项
$component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
$component->setMVCFactory($container->get(MVCFactoryInterface::class));
在第一行中,扩展类(MVCComponent)被实例化,并将 ComponentDispatchFactory 实例传递到构造函数中(如上所述)。
在第二行中,代码 $container->get(MVCFactoryInterface::class
从子 DIC 中获取 MVCFactory 实例,然后通过将其传递给 setMVCFactory
在本地存储对它的引用。此函数可通过特征 Joomla\CMS\MVC\Factory\MVCFactoryServiceTrait 供 MVCComponent
使用,该特征用于 MVCComponent,其中包含以下代码行
public function setMVCFactory(MVCFactoryInterface $mvcFactory)
{
$this->mvcFactory = $mvcFactory;
}
命名空间参数
您可能已经注意到,命名空间 '\Mycompany\Component\Example' 作为参数传递到几个类的构造函数中。(顺便说一句,这与使用双反斜杠而不是单反斜杠的等效 PHP 字符串完全相同)。
这样做的原因是,这些 Factory 类实例负责代表组件实例化各种类
- ComponentDispatcherFactory 将实例化 Dispatcher 类
- MVCFactory 将实例化 Controller、View、Model 和 Table 类
例如,ComponentDispatcherFactory 的 createDispatcher
函数会查看是否存在 com_example 的特定 Dispatcher 类,方法是形成一个类名
$className = '\\' . trim($this->namespace, '\\') . '\\' . $name . '\\Dispatcher\\Dispatcher';
(其中 $name
为“站点”或“管理员”)并查看该类是否存在。如果不存在,则实例化默认的 Joomla\CMS\Dispatcher\ComponentDispatcher 类。
类似地,MVCFactory 使用命名空间来计算 com_example Controller、View、Model 和 Table 类的完全限定类名,以便实例化它们。
这是子 DIC 必要的其中一个原因。在响应 HTTP 请求的过程中,Joomla 将实例化多个扩展,每个扩展都有其依赖项。由于 Joomla 使用通用键(例如,组件的 ComponentInterface::class),因此子 DIC 对于分离每个扩展的条目是必要的。