kabuto / yii-di
Yii DI 容器
Requires
- php: ^7.1.0
- ext-mbstring: *
Requires (Dev)
- phpbench/phpbench: ^0.14.0
- phpunit/phpunit: ^7.2
- squizlabs/php_codesniffer: ^3.3
Provides
- psr/container-implementation: 1.0.0
This package is auto-updated.
Last update: 2024-09-20 09:57:10 UTC
README
Yii 依赖注入
该库是一个与 PSR-11 兼容的 依赖注入 容器,能够实例化和配置类以解析依赖。
功能
- PSR-11 兼容。
- 支持属性注入、构造函数注入和方法注入。
- 检测循环引用。
- 接受数组定义,因此可用于合并配置。
使用容器
使用 DI 容器相当简单。首先,您将其设置为对象定义,然后它们可以直接在应用程序中使用或用于解析其他定义的依赖项。
通常整个应用程序中只有一个容器,因此它通常配置在入口脚本(如 index.php
)或配置文件中。
$container = new Container($config);
配置可以存储在返回数组的 .php
文件中。
return [ EngineInterface::class => EngineMarkOne::class, 'full_definition' => [ '__class' => EngineMarkOne::class, '__construct()' => [42], 'argName' => 'value', 'setX()' => [42], ], 'closure' => function($container) { return new MyClass($container->get('db')); }, 'static_call' => [MyFactory::class, 'create'], 'object' => new MyClass(), ];
接口定义简单地将一个 ID(通常是一个接口)映射到特定的类。
完整定义详细描述了如何实例化一个类。
__class
包含要实例化的类的名称。__construct()
包含构造函数参数的数组。- 配置的其余部分和属性值以及方法调用。它们按照数组中的顺序设置/调用。
闭包在实例化很复杂且应在代码中描述时很有用。如果非常复杂,将此类代码移动到工厂中并将其作为静态调用引用是一个好主意。
尽管通常不是一个好主意,但您可以设置已实例化的对象到容器中。
此外,定义可以通过调用 set()
来添加。
$container->set($id, Example::class); $container->set($id, [ '__class' => Example::class, '__construct()' => ['a', 'b'], 'property1' => 'val1', 'setMethod()' => 'val2', 'property2' => 'val3', ]);
配置容器后,可以通过 get()
获取依赖项。
$object = $container->get('interface_name');
请注意,直接使用容器是一种不良实践,而通过通过 yiisoft/injector 包提供的 Injector 实现自动注入则更好。
使用别名
容器通过 Reference
类支持别名。能够以接口和命名的方式检索对象可能很有用。
$container = new Container([ EngineInterface::class => EngineMarkOne::class, 'engine_one' => EngineInterface::class, ]);
委托查找和组合容器
Container
类支持委托查找。当使用委托查找时,所有依赖项始终从给定的根容器中获取。这允许我们将多个容器组合成组合容器,然后我们可以将其用于查找。使用这种方法时,应仅使用组合容器。
$composite = new CompositeContainer(); $container = new Container([], [], $composite);
上下文容器
在应用程序中,我们可能希望为 DI 容器设置多个配置级别。例如,在 Yii 应用程序中,这些可能是
- 提供默认配置的扩展
- 具有配置的应用程序
- 使用与主应用程序不同配置的应用程序中的模块
虽然通常不希望将 DI 容器注入到您的对象中,但有一些例外,例如需要访问容器的 Yii 应用程序模块。
为了在模块级别支持自定义配置的同时支持此用例,我们实现了上下文容器。主要类是CompositeContextContainer
;在它不包含任何定义的意义上,它与CompositeContainer
相似。上下文容器的attach()
函数有一个额外的字符串参数,用于定义容器的上下文。
使用上下文,我们可以创建一个简单的范围系统
$root = new CompositeContextContainer(); $coreContainer = new Container([], [], $root); $extensionContainer = new Container([], [], $root); $appContainer = new Container([ LoggerInterface::class => MainLogger::class ], [], $root); $moduleAContainer = new Container([ LoggerInterface::class => LoggerA::class ], [], $root); $moduleBContainer = new Container([ LoggerInterface::class => LoggerB::class ], [], $root); $composite->attach($moduleContainer, '/moduleB'); $composite->attach($moduleContainer, '/moduleA'); $composite->attach($appContainer); $composite->attach($extensionContainer); $composite->attach($coreContainer); // The composite context container will allow us to create contextual containers with virtually no overhead. $moduleAContainer = $composite->getContextContainer('/moduleA'); $moduleBContainer = $composite->getContextContainer('/moduleB'); $composite->get(LoggerInterface::class); // MainLogger $composite->get(LoggerInterface::class); // MainLogger $moduleAContainer->get(LoggerInterface::class // LoggerA $moduleBContainer->get(LoggerInterface::class // LoggerB
搜索是按照最长前缀首先进行,然后按照它们被添加的顺序检查容器。对于Yii的上下文容器,模块的容器是自动创建的。
使用服务提供者
服务提供者是一种特殊类,负责将复杂服务或依赖组绑定到容器中,包括注册服务及其引用、事件监听器、中间件等。
所有服务提供者都扩展了yii\di\support\ServiceProvider
类,并包含一个register
方法。在register方法内部,您应该仅将东西绑定到容器中。您永远不应该在服务提供者中实现任何业务逻辑、与环境引导相关的功能、更改数据库或与绑定东西无关的任何其他功能。要在服务提供者中访问容器,您应该使用container
字段。容器通过构造函数传递给服务提供者,并保存到container
字段。
典型的服务提供者可能看起来像
use yii\di\contracts\ServiceProvider; class CarFactoryProvider implements ServiceProvider { public function register(Container $container): void { $container->registerDependencies($container); $container->registerService($container); } protected function registerDependencies(Container $container): void { $container->set(EngineInterface::class, SolarEngine::class); $container->set(WheelInterface::class, [ '__class' => Wheel::class, 'color' => 'black', ]); $container->set(CarInterface::class, [ '__class' => BMW::class, 'model' => 'X5', ]); } protected function registerService(Container $container): void { $container->set(CarFactory::class, [ '__class' => CarFactory::class, 'color' => 'red', ]); } }
要将服务提供者添加到容器中,您需要将服务提供者类(或配置数组)传递给容器的addProvider
方法
$container->addProvider(CarFactoryProvider::class);
或通过使用providers
键通过配置数组传递它
$container = new Container([ 'providers' => [ CarFactoryProvider::class, ], ]);
在上面的代码中,我们创建了一个负责启动具有所有依赖项的汽车工厂的服务提供者。一旦通过addProvider
方法或通过配置数组添加了服务提供者,服务提供者的register
方法就会立即调用,并将服务注册到容器中。
注意,如果您在register
方法中执行重操作,服务提供者可能会降低您的应用程序的性能。
使用延迟服务提供者
如前所述,服务提供者在注册重服务时可能会降低您的应用程序的性能。因此,为了避免性能下降,您可以使用所谓的延迟服务提供者。
延迟服务提供者扩展了yii\di\support\DeferredServiceProvider
,除了register
方法外,还包含一个provides
方法,该方法返回服务提供者绑定到容器中的服务名称和标识符数组。延迟服务提供者被添加到容器的相同方式,但延迟服务提供者的register
方法仅在请求provides
方法中列出的服务之一时才会调用。示例
use yii\di\support\DeferredServiceProvider; class CarFactoryProvider extends DeferredServiceProvider { public function provides(): array { return [ CarFactory::class, CarInterface::class, EngineInterface::class, WheelInterface::class, ]; } public function register(Container $container): void { $this->registerDependencies($container); $this->registerService($container); } protected function registerDependencies(Container $container): void { $container->set(EngineInterface::class, SolarEngine::class); $container->set(WheelInterface::class, [ '__class' => Wheel::class, 'color' => 'black', ]); $container->set(CarInterface::class, [ '__class' => BMW::class, 'model' => 'X5', ]); } protected function registerService(Container $container): void { $container->set(CarFactory::class, [ '__class' => CarFactory::class, 'color' => 'red', ]); } } $container->addProvider(CarFactoryProvider::class); // returns false as provider wasn't registered $container->has(EngineInterface::class); // returns SolarEngine, registered in the provider $engine = $container->get(EngineInterface::class); // returns true as provider was registered when EngineInterface was requested from the container $container->has(EngineInterface::class);
在上面的代码中,我们将CarFactoryProvider
添加到容器中,但CarFactoryProvider
的register
方法直到从容器请求EngineInterface
时才执行。当我们请求EngineInterface
时,容器查看CarFactoryProvider
的provides
列表,并且由于EngineInterface
列在provides
中,容器调用了CarFactoryProvider
的register
方法。
注意,您可以使用延迟服务提供者不仅来延迟重服务的引导,而且当它们实际需要时才将服务注册到容器中。