mouf / container-installer
此包包含一个PHP依赖注入容器检测和聚合器。想法是每个composer包可能有潜在的一个DI容器,并将所有这些容器聚合到一个“根”容器中。
Requires
- php: >=5.4
- composer-plugin-api: 1.0.0
- container-interop/container-interop: ~1.0
This package is auto-updated.
Last update: 2024-09-15 04:36:41 UTC
README
此项目是在开发ContainerInterop项目期间作为概念验证开发的测试项目。
总体目标
最终目标是允许应用开发者轻松创建一个“根容器”,该容器可以自动检测并添加其他包中包含的容器,形成一个可由应用使用的全局复合容器。
与传统思考方式相比,这是一个范式转变。
在“经典”应用中,添加到应用中的包可能会向主且唯一的DI容器添加新的实例。这是SF2包、ZF2模块或Mouf2包所做的。
使用此方法,每个包都提供了一个包含实例的DI容器。DI容器被添加到一个全局容器中,并对其进行查询。
关于此包
此包的目标是简单地为应用开发者提供一种检测在使用的Composer包中可能声明的DI容器的方法。
此项目在Composer“安装”步骤中添加了一个额外的步骤,在Composer将自动加载器导出之后进行。Container-Installer将扫描所有composer.json文件,并查找类似以下部分的内容:
{ "extra": { "container-interop": { "container-factory": "My\\ContainerFactory::getContainer" } } }
此部分实际上声明了可以打包在包中的容器工厂。
“container-factory”参数必须指向一个函数或静态方法,该方法返回容器。
以下是一个示例实现
class ContainerFactory { private static $container; /** * This method is returning a configured container * * @param ContainerInterface $rootContainer * @return ContainerInterface */ public static function getContainer(ContainerInterface $rootContainer) { if (!$this->container) { // Delegate dependencies fetching to the root container. $this->container = new Picotainer([ "hello" => function(ContainerInterface $container) { return array('hello' => $container->get('world')); } ], $rootContainer); } return $this->container; } }
关于此代码的简要说明:我们提供了一个Picotainer容器。Picotainer是一个与ContainerInterop项目完全兼容的简约容器。
重要:工厂需要一个强制参数:$rootContainer。如果您的容器中包含外部依赖(不属于容器的依赖),则您的容器需要能够将依赖项获取委托给$rootContainer。例如,如果将另一个容器作为构造函数的第一个参数传递,则PimpleInterop
可以委托依赖项获取。
注意:您的包不必要求mouf/container-installer
包。这很棒,因为如果其他容器聚合器遵循相同的约定(在composer.json
的额外部分中引用工厂代码),则可以轻松实现许多不同的根容器实现(可能每个框架一个)。
如何在项目中使用根容器?
此包将在项目根目录下创建一个containers.php
文件。此containers.php
文件将包含以下形式的容器列表
<?php return [ [ 'name' => '__root___0', 'description' => 'Container number 0 for package __root__', 'factory' => My\ContainerFactory::getContainer, 'enable' => true ], ];
请注意,开发者可以使用“enable”属性手动启用或禁用包。
之后,使用该文件的权限就交给了应用开发者。
使用Acclimate的CompositeContainer,使用方式可能如下
use Acclimate\Container\CompositeContainer; // Let's create a composite container $rootContainer = new CompositeContainer(); // Let's get the containers list $container_descriptors = require 'containers.php'; // Let's add containers to the root container. foreach ($container_descriptors as $descriptor) { if (descriptor['enable']) { $container = $descriptor['factory']($rootContainer); $rootContainer->addContainer($container); } } $myEntry = $rootContainer->get('myEntry');
允许的语法
以下语法都适用于在 composer.json 中声明容器工厂。
通过 回调 声明容器工厂。
{ "extra": { "container-interop": { "container-factory": "My\\ContainerFactory::getContainer" } } }
通过回调声明 容器工厂数组。
{ "extra": { "container-interop": { "container-factory": [ "My\\ContainerFactory1::getContainer", "My\\ContainerFactory2::getContainer", "My\\ContainerFactory3::getContainer" ] } } }
声明容器工厂 描述符(它包含有关工厂的附加数据)。
{ "extra": { "container-interop": { "container-factory": { "name": "a unique name for the factory", "description": "a description of what the factory does", "factory": "My\\ContainerFactory::getContainer" } } } }
注意:描述符的所有参数都是可选的,除了“工厂”部分。
声明 容器工厂描述符数组。
{ "extra": { "container-interop": { "container-factory": [{ "name": "a unique name for the factory", "description": "a description of what the factory does", "factory": "My\\ContainerFactory::getContainer" }, { "name": "a unique name for another factory", "description": "a description of what the factory does", "factory": "My\\ContainerFactory2::getContainer" }] } } }
优点
每个包提供其容器。包不依赖于应用中使用的 DI 容器。这样,我们可以提供无框架依赖的包。
缺点
复合控制器的经典实现可能会导致性能下降。我们需要考虑提高复合容器(可能通过条目映射、将条目映射到其相关容器)性能的方法。
关于其他项目
这不是唯一一个在“每个包一个容器”范式上工作的项目。@mnapoli 的 FrameworkInterop 项目 也在走相同的路线(尽管其范围更广)。
关于性能
当前 RootContainer 的实现依赖于 Acclimate 的 CompositeContainer。这是一个概念验证,没有在性能方面做出努力。应用中的容器越多,性能应该越低(线性)。
但这并不意味着性能无法提高。有许多可能的策略可以改善性能,例如创建所有条目及其相应容器的映射。这超出了当前项目的范围。