yiisoft / di
Yii DI 容器
Requires
- php: ^8.0
- ext-mbstring: *
- psr/container: ^1.1|^2.0
- yiisoft/definitions: ^3.0
Requires (Dev)
- league/container: ^4.2
- maglnet/composer-require-checker: ^4.2
- phpbench/phpbench: ^1.2.0
- phpunit/phpunit: ^9.5
- rector/rector: ^0.14.3
- roave/infection-static-analysis-plugin: ^1.25
- spatie/phpunit-watcher: ^1.23
- vimeo/psalm: ^4.29
- yiisoft/injector: ^1.0
- yiisoft/test-support: ^3.0
Suggests
- phpbench/phpbench: To run benchmarks.
- yiisoft/injector: ^1.0
Provides
- psr/container-implementation: 1.0.0
- dev-master
- 1.2.1
- 1.2.0
- 1.1.0
- 1.0.3
- 1.0.2
- 1.0.1
- 1.0.0
- dev-add-debug-command
- dev-lazy-services
- dev-reset-hook
- dev-parameters-name-binding
- dev-iterable
- dev-update-styleci-config
- dev-update-styleci-config2
- dev-test-class-method-extensible
- dev-fix_class_methods_on_extensiable_service
- dev-modular-container
This package is auto-updated.
Last update: 2024-09-16 20:48:24 UTC
README
Yii 依赖注入
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'));
结果是包含两个实例的数组:BlueCarService
和 RedCarService
。
标记服务的另一种方式是在容器构造函数中设置标签
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);
重置服务状态
尽管具有状态的服务不是一种很好的做法,但这些往往是不可避免的。当您使用像 Swoole 或 RoadRunner 这样的工具构建长期运行的应用程序时,您应该在每个请求后重置此类服务的状态。为此,您可以使用带有重置回调的 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 软件维护。