Yii DI 容器


README

Yii

Yii 依赖注入


Latest Stable Version Total Downloads Build status Code coverage Mutation testing badge static analysis type-coverage

PSR-11 兼容的依赖注入容器,能够实例化和配置类,解决依赖关系。

特性

  • PSR-11 兼容。
  • 支持属性注入、构造函数注入和方法注入。
  • 检测循环引用。
  • 接受数组定义。你可以使用它与可合并的配置一起使用。
  • 为没有显式定义的类提供可选的自动加载回退。
  • 允许委托查找,并具有复合容器。
  • 支持别名。
  • 支持服务提供者。
  • 具有适用于长时间运行的工作者(如 RoadRunner 或 Swoole)的状态重置器。
  • 支持容器委托。

要求

  • PHP 8.1 或更高版本。
  • 多字节字符串 PHP 扩展。

安装

你可以使用 composer 安装此包

composer require yiisoft/di

使用容器

DI 容器的使用很简单:首先使用一组 定义 初始化它。数组的键通常是接口名称。然后,每当应用程序请求该类型时,它将使用这些定义创建一个对象。例如,当从应用程序中的某个位置直接从容器中检索类型时,就会发生这种情况。但是,如果定义依赖于另一个定义,则会隐式创建对象。

通常一个应用程序使用一个容器。它通常配置在入口脚本(如 index.php)或配置文件中

use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withDefinitions($definitions);

$container = new Container($config);

你可以在返回数组的 .php 文件中存储定义

return [
    EngineInterface::class => EngineMarkOne::class,
    'full_definition' => [
        'class' => EngineMarkOne::class,
        '__construct()' => [42], 
        '$propertyName' => 'value',
        'setX()' => [42],
    ],
    'closure' => fn (SomeFactory $factory) => $factory->create('args'),
    'static_call_preferred' => fn () => MyFactory::create('args'),
    'static_call_supported' => [MyFactory::class, 'create'],
    'object' => new MyClass(),
];

你可以以多种方式定义一个对象

  • 在简单情况下,接口定义将一个 id 映射到特定的类。
  • 完整的定义更详细地描述了如何实例化一个类
    • class 具有要实例化的类的名称。
    • __construct() 包含构造函数参数的数组。
    • 配置的其余部分是属性值(以 $ 为前缀)和方法调用(以 () 结尾)。它们按数组中的顺序设置/调用。
  • 闭包在实例化复杂且可以在代码中更好地完成时很有用。当使用这些时,参数会自动按类型连接。可以使用 ContainerInterface 来获取当前容器实例。
  • 如果更加复杂,将此类代码移动到工厂并作为静态调用引用是一个好主意。
  • 虽然通常不是好主意,但你也可以将已实例化的对象设置为容器中的对象。

有关更多信息,请参阅 yiisoft/definitions

配置容器后,您可以通过 get() 获取服务。

/** @var \Yiisoft\Di\Container $container */
$object = $container->get('interface_name');

然而,请注意,直接使用容器是不好的做法。最好依赖由 yiisoft/injector 包提供的自动绑定。

使用别名

DI 容器通过 Yiisoft\Definitions\Reference 类支持别名。这样您可以使用更方便的名称来检索对象。

use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withDefinitions([
        EngineInterface::class => EngineMarkOne::class,
        'engine_one' => EngineInterface::class,
    ]);

$container = new Container($config);
$object = $container->get('engine_one');

复合容器

复合容器将多个容器组合成一个容器。在采用此方法时,您应该仅从复合容器中获取对象。

use Yiisoft\Di\CompositeContainer;
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$composite = new CompositeContainer();

$carConfig = ContainerConfig::create()
    ->withDefinitions([
        EngineInterface::class => EngineMarkOne::class,
        CarInterface::class => Car::class
    ]);
$carContainer = new Container($carConfig);

$bikeConfig = ContainerConfig::create()
    ->withDefinitions([
        BikeInterface::class => Bike::class
    ]);

$bikeContainer = new Container($bikeConfig);
$composite->attach($carContainer);
$composite->attach($bikeContainer);

// Returns an instance of a `Car` class.
$car = $composite->get(CarInterface::class);
// Returns an instance of a `Bike` class.
$bike = $composite->get(BikeInterface::class);

注意,先附加的容器将覆盖后附加的容器的依赖项。

use Yiisoft\Di\CompositeContainer;
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$carConfig = ContainerConfig::create()
    ->withDefinitions([
        EngineInterface::class => EngineMarkOne::class,
        CarInterface::class => Car::class
    ]);

$carContainer = new Container($carConfig);

$composite = new CompositeContainer();
$composite->attach($carContainer);

// Returns an instance of a `Car` class.
$car = $composite->get(CarInterface::class);
// Returns an instance of a `EngineMarkOne` class.
$engine = $car->getEngine();

$engineConfig = ContainerConfig::create()
    ->withDefinitions([
        EngineInterface::class => EngineMarkTwo::class,
    ]);

$engineContainer = new Container($engineConfig);

$composite = new CompositeContainer();
$composite->attach($engineContainer);
$composite->attach($carContainer);

// Returns an instance of a `Car` class.
$car = $composite->get(CarInterface::class);
// Returns an instance of a `EngineMarkTwo` class.
$engine = $composite->get(EngineInterface::class);

使用服务提供者

服务提供者是一个特殊类,负责为容器和现有服务的扩展提供复杂的服务或依赖组。

提供者应扩展自 Yiisoft\Di\ServiceProviderInterface 并必须包含 getDefinitions()getExtensions() 方法。它应只为容器提供服务,因此应只包含与此任务相关的代码。它 永远 不应实现任何业务逻辑或其他功能,如环境启动或对数据库进行更改。

一个典型的服务提供者可能如下所示

use Yiisoft\Di\Container;
use Yiisoft\Di\ServiceProviderInterface;

class CarFactoryProvider extends ServiceProviderInterface
{
    public function getDefinitions(): array
    {
        return [
            CarFactory::class => [
                'class' => CarFactory::class,
                '$color' => 'red',
            ], 
            EngineInterface::class => SolarEngine::class,
            WheelInterface::class => [
                'class' => Wheel::class,
                '$color' => 'black',
            ],
            CarInterface::class => [
                'class' => BMW::class,
                '$model' => 'X5',
            ],
        ];    
    }
     
    public function getExtensions(): array
    {
        return [
            // Note that Garage should already be defined in a container 
            Garage::class => function(ContainerInterface $container, Garage $garage) {
                $car = $container
                    ->get(CarFactory::class)
                    ->create();
                $garage->setCar($car);
                
                return $garage;
            }
        ];
    } 
}

在这里,您创建了一个负责启动带有所有依赖关系的汽车工厂的服务提供者。

扩展是可以调用的,它返回一个修改后的服务对象。在这种情况下,您通过调用 setCar() 方法将现有 Garage 服务放入车库。因此,在应用此提供者之前,您的车库是空的,借助扩展,您填充了它。

要将此服务提供者添加到容器中,您可以通过额外配置传递其类或配置数组。

use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withProviders([CarFactoryProvider::class]);

$container = new Container($config);

添加服务提供者时,DI 会立即调用其 getDefinitions()getExtensions() 方法,并且服务和它们的扩展都注册到容器中。

容器标签

您可以使用以下方式对服务进行标记

use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withDefinitions([  
        BlueCarService::class => [
            'class' => BlueCarService::class,
            'tags' => ['car'], 
        ],
        RedCarService::class => [
            'definition' => fn () => new RedCarService(),
            'tags' => ['car'],
        ],
    ]);

$container = new Container($config);

现在您可以按以下方式从容器中获取标记服务

$container->get(\Yiisoft\Di\Reference\TagReference::to('car'));

结果是包含两个实例的数组:BlueCarServiceRedCarService

标记服务的另一种方式是在容器构造函数中设置标签

use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withDefinitions([  
        BlueCarService::class => [
            'class' => BlueCarService::class,
        ],
        RedCarService::class => fn () => new RedCarService(),
    ])
    ->withTags([
        // "car" tag has references to both blue and red cars
        'car' => [BlueCarService::class, RedCarService::class]
    ]);

$container = new Container($config);

重置服务状态

尽管具有状态的服务不是一种很好的做法,但这些往往是不可避免的。当您使用像 SwooleRoadRunner 这样的工具构建长期运行的应用程序时,您应该在每个请求后重置此类服务的状态。为此,您可以使用带有重置回调的 StateResetter

$resetter = new StateResetter($container);
$resetter->setResetters([
    MyServiceInterface::class => function () {
        $this->reset(); // a method of MyServiceInterface
    },
]);

回调可以访问服务实例的私有和受保护属性,因此您可以在不创建新实例的情况下有效地设置服务的初始状态。

您应该在每个请求-响应周期后触发重置。对于 RoadRunner,它看起来如下所示

while ($request = $psr7->acceptRequest()) {
    $response = $application->handle($request);
    $psr7->respond($response);
    $application->afterEmit($response);
    $container
        ->get(\Yiisoft\Di\StateResetter::class)
        ->reset();
    gc_collect_cycles();
}

在定义中设置重置器

您可以通过以下方式提供“重置”回调来为每个服务定义重置状态

use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withDefinitions([
        EngineInterface::class => EngineMarkOne::class,
        EngineMarkOne::class => [
            'class' => EngineMarkOne::class,
            'setNumber()' => [42],
            'reset' => function () {
                $this->number = 42;
            },
        ],
    ]);

$container = new Container($config);

注意:如果未在定义或服务提供者中设置 StateResetter,则定义中的重置器才会工作。

手动配置 StateResetter

为了手动添加重置器,或者如果您使用的是不支持原生状态重置的第三方容器的 Yii DI 复合容器,您可以单独配置状态重置器。以下是一个 PHP-DI 的示例。

MyServiceInterface::class => function () {
    // ...
},
StateResetter::class => function (ContainerInterface $container) {
    $resetter = new StateResetter($container);
    $resetter->setResetters([
        MyServiceInterface::class => function () {
            $this->reset(); // a method of MyServiceInterface
        },
    ]);
    return $resetter;
}

为非数组定义指定元数据

为了指定某些元数据,例如在“重置服务状态”或“容器标签”的情况下,对于非数组定义,您可以使用以下语法

LogTarget::class => [
    'definition' => static function (LoggerInterface $logger) use ($params) {
        $target = ...
        return $target;
    },
    'reset' => function () use ($params) {
        ...
    },
],

现在您已显式地将定义本身移动到“定义”键。

委托

每个委托是一个可调用的,它返回一个用于在 DI 无法在主容器中找到服务时使用的容器实例。

function (ContainerInterface $container): ContainerInterface
{

}

要配置委托,请使用额外配置

use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withDelegates([
        function (ContainerInterface $container): ContainerInterface {
            // ...
        }
    ]);


$container = new Container($config);

针对生产进行调优

默认情况下,容器会在设置定义时立即验证它们。在生产环境中,关闭此功能是有意义的。

use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withValidate(false);

$container = new Container($config);

严格模式

容器可能处于严格模式,这意味着您应该在容器中明确定义一切。要启用它,请使用以下代码:

use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withStrictMode(true);

$container = new Container($config);

文档

如果您需要帮助或有问题,请访问Yii 论坛,这是一个很好的地方。您还可以查看其他Yii 社区资源

许可

Yii 依赖注入是免费软件。它根据BSD许可条款发布。有关更多信息,请参阅LICENSE

Yii 软件维护。

支持项目

Open Collective

关注更新

Official website Twitter Telegram Facebook Slack