yii1tech / di
为Yii1应用程序提供依赖注入支持
Requires
- php: >=7.2
- psr/container: >=1.1.0
- yiisoft/yii: ~1.1.0
Requires (Dev)
- phpunit/phpunit: ^6.0 || ^7.0 || ^8.0 || ^9.3 || ^10.0.7
README
Yii1依赖注入扩展
本扩展为Yii1应用程序提供依赖注入支持。
有关许可证信息,请检查LICENSE文件。
安装
安装此扩展的首选方式是通过composer。
运行以下命令:
php composer.phar require --prefer-dist yii1tech/di
或添加以下内容到您的composer.json文件中的"require"部分:
"yii1tech/di": "*"
使用方法
此扩展使用兼容PSR-11的容器为Yii1应用程序提供依赖注入支持。
此扩展引入了一个静态外观类\yii1tech\di\DI
,它提供了对PSR容器的全局访问和注入器。本扩展提供的所有依赖注入功能都依赖于此外观类。它提供了一个容器实体检索和依赖注入的简单方法。例如
<?php use yii1tech\di\DI; class Foo { /** * @var CDbConnection */ public $db; /** * @var string */ public $name = 'default'; public function __construct(CDbConnection $db) { $this->db = $db; } public function format(CFormatter $formatter, string $value): string { return $formatter->formatDate($value); } } $psrContainer = DI::container(); // retrieve related PSR compatible container var_dump($psrContainer instanceof \Psr\Container\ContainerInterface); // outputs `true` $db = DI::get(CDbConnection::class); // retrieve entity from PSR compatible container $object = DI::make(Foo::class); // instantiates object, resolving constructor arguments from PSR compatible container based on type-hints var_dump($object->db instanceof CDbConnection); // outputs `true` $date = DI::invoke([$object, 'format'], ['value' => time()]); // invokes given callback, resolving its arguments from PSR compatible container based on type-hints var_dump($date); // outputs '2023/07/28' $object = DI::create([ // instantiates object from Yii-style configuration, resolving constructor arguments from PSR compatible container based on type-hints 'class' => Foo::class, 'name' => 'custom', ]); var_dump($object->db instanceof CDbConnection); // outputs `true` var_dump($object->name); // outputs `custom`
容器设置
实际的依赖注入容器应通过\yii1tech\di\DI::setContainer()
方法设置。这应该在创建应用程序之前在Yii引导阶段完成。例如
<?php // file '/public/index.php' require __DIR__ . '../vendor/autoload.php'; // ... use yii1tech\di\Container; use yii1tech\di\DI; // setup DI container: DI::setContainer( Container::new() ->config(CDbConnection::class, [ 'connectionString' => 'sqlite::memory:', ]) ->lazy(ICache::class, function (Container $container) { $cache = new CDbCache(); $cache->setDbConnection($container->get(CDbConnection::class)) $cache->init(); return $cache; }) // ... ); // create and run Yii application: Yii::createWebApplication($config)->run();
而不是立即创建容器实例,您可能指定一个PHP回调,该回调将实例化它。例如
<?php // file '/public/index.php' require __DIR__ . '../vendor/autoload.php'; // ... use yii1tech\di\Container; use yii1tech\di\DI; // setup DI container: DI::setContainer(function () { return ContainerFactory::create(); }); // create and run Yii application: Yii::createWebApplication($config)->run(); // ... class ContainerFactory { public static function create(): \Psr\Container\ContainerInterface { $container = Container::new(); // fill up container return $container; } }
应用程序组件的依赖注入
此扩展允许通过DI容器配置应用程序(服务定位器)组件。为了使其正常工作,您需要使用此扩展中提供的应用程序类之一 - 该功能不会自动在标准类上工作。
以下类被提供
\yii1tech\di\web\WebApplication
- 具有DI意识的Web应用程序。\yii1tech\di\console\ConsoleApplication
- 具有DI意识的控制台应用程序。\yii1tech\di\base\Module
- 具有DI意识的模块。\yii1tech\di\web\WebModule
- 具有DI意识的Web模块。
如果您使用自己的自定义应用程序类,您可以使用\yii1tech\di\base\ResolvesComponentViaDI
特质将其应用于DI组件解析。
例如
<?php // file '/public/index.php' require __DIR__ . '../vendor/autoload.php'; // ... use yii1tech\di\DI; use yii1tech\di\web\WebApplication; // setup DI container: DI::setContainer(/* ... */); // create and run Yii DI-aware application: Yii::createApplication(WebApplication::class, $config)->run();
由于是DI意识,应用程序将根据其配置的类通过DI容器解析配置的组件。这允许您将配置从服务定位器移动到DI容器而不会破坏任何东西。例如
<?php // ... use yii1tech\di\Container; use yii1tech\di\DI; use yii1tech\di\web\WebApplication; // setup DI container: DI::setContainer( Container::new() ->lazy(CDbConnection::class, function () { $db = new CDbConnection(); $db->connectionString = 'mysql:host=127.0.0.1;dbname=example'; $db->username = 'container_user'; $db->password = 'secret'; $db->init(); return $db; }) ->lazy(ICache::class, function (Container $container) { $cache = new CDbCache(); $cache->setDbConnection($container->get(CDbConnection::class)); $cache->init(); return $cache; }) ); $config = [ 'components' => [ 'db' => [ // component 'db' will be fetched from container using ID 'CDbConnection' 'class' => CDbConnection::class, ], 'cache' => [ // component 'cache' will be fetched from container using ID 'ICache' 'class' => ICache::class, ], 'format' => [ // if component has no matching definition in container - it will be resolved in usual way 'class' => CFormatter::class, 'dateFormat' => 'Y/m/d', ], ], ]; // create and run Yii DI-aware application: Yii::createApplication(WebApplication::class, $config)->run(); //... $db = Yii::app()->getComponent('db'); var_dump($db->username); // outputs 'container_user' $cache = Yii::app()->getComponent('cache'); var_dump(get_class($cache)); // outputs 'CDbCache'
注意! 在将Yii组件定义移动到DI容器时,请小心:请记住手动调用其上的init()
方法。Yii经常将关键初始化逻辑放置在组件的init()
中,如果您省略其调用,则在从容器中检索时可能会收到损坏的组件实例。
注意! 如果您将应用程序组件配置移动到DI容器,请确保在服务定位器中清理其原始配置。任何剩余的"属性值"定义都将应用于从容器中检索的对象。当您通过工厂定义容器实体时,这可能很有用,但在大多数情况下,它可能会给您带来麻烦。
提示:通过DI容器放置到Yii应用程序(服务定位器)中的组件不需要实现
\IApplicationComponent
接口,因为不会自动调用其上的init()
方法。
Web应用程序中的依赖注入
此扩展允许根据参数的类型提示将 DI 容器实体注入到控制器构造函数和动作方法中。然而,此功能在直接从 CController
扩展的标准控制器上无法工作 - 您需要扩展 \yii1tech\di\web\Controller
类或使用 \yii1tech\di\web\ResolvesActionViaDI
特性。例如
<?php use yii1tech\di\web\Controller; class ItemController extends Controller { /** * @var CDbConnection */ protected $db; // injects `CDbConnection` from DI container at constructor level public function __construct(CDbConnection $db, $id, $module = null) { parent::__construct($id, $module); // do not forget to invoke parent constructor $this->db = $db; } // injects `ICache` from DI container at action level public function actionIndex(ICache $cache) { // ... } // injects `ICache` from DI container at action level, populates `$id` from `$_GET` public function actionView(ICache $cache, $id) { // ... } }
注意!在声明您自己的控制器构造函数时,请确保它接受父类的参数 $id
和 $module
并将它们传递给父构造函数。否则,控制器可能无法正常工作。
控制台应用程序中的依赖注入
此扩展允许根据参数的类型提示将 DI 容器实体注入到控制台命令的构造函数和动作方法中。然而,此功能在直接从 CConsoleCommand
扩展的标准控制台命令上无法工作 - 您需要扩展 \yii1tech\di\console\ConsoleCommand
类或使用 \yii1tech\di\console\ResolvesActionViaDI
特性。例如
<?php use yii1tech\di\console\ConsoleCommand; class ItemCommand extends ConsoleCommand { /** * @var CDbConnection */ protected $db; // injects `CDbConnection` from DI container at constructor level public function __construct(CDbConnection $db, $name, $runner) { parent::__construct($name, $runner); // do not forget to invoke parent constructor $this->db = $db; } // injects `ICache` from DI container at action level public function actionIndex(ICache $cache) { // ... } // injects `CFormatter` from DI container at action level, populates `$date` from shell arguments public function actionFormat(CFormatter $formatter, $date) { // ... } }
注意!在声明您自己的控制台命令构造函数时,请确保它接受父类的参数 $name
和 $runner
并将它们传递给父构造函数。否则,控制台命令可能无法正常工作。
外部(第三方)容器使用
容器 \yii1tech\di\Container
非常基本,您可能希望使用更复杂的东西,如 PHP-DI,而不是它。任何 PSR-11 兼容的容器都可以在此扩展中使用。您只需要将容器传递给 \yii1tech\di\DI::setContainer()
。例如
<?php // file '/public/index.php' require __DIR__ . '../vendor/autoload.php'; // ... use DI\ContainerBuilder; use yii1tech\di\DI; use yii1tech\di\web\WebApplication; // use 'PHP-DI' for the container: DI::setContainer(function () { $builder = new ContainerBuilder(); $builder->useAutowiring(true); // ... return $builder->build(); }); // create and run Yii DI-aware application: Yii::createApplication(WebApplication::class, $config)->run();
许多现有的 PSR-11 兼容解决方案都内置了注入机制,您可能希望利用它而不是此扩展提供的注入机制。为了做到这一点,您应该创建自己的注入器,实现 \yii1tech\di\InjectorContract
接口,并将其传递给 \yii1tech\di\DI::setInjector()
。
许多现有的 PSR-11 容器(包括 'PHP-DI')已经实现了依赖注入方法 make()
和 call()
。您可以使用 \yii1tech\di\external\ContainerBasedInjector
来利用这些方法。例如
<?php // file '/public/index.php' require __DIR__ . '../vendor/autoload.php'; // ... use DI\ContainerBuilder; use yii1tech\di\DI; use yii1tech\di\external\ContainerBasedInjector; use yii1tech\di\web\WebApplication; // use 'PHP-DI' for the container: DI::setContainer(function () { $builder = new ContainerBuilder(); // ... return $builder->build(); }) ->setInjector(new ContainerBasedInjector()); // use `\DI\Container::make()` and `\DI\Container::call()` for dependency injection // create and run Yii DI-aware application: Yii::createApplication(WebApplication::class, $config)->run();
注意!许多现有的提供内置注入方法的 PSR-11 容器在实现 \Psr\Container\ContainerInterface::has()
方法时存在不一致性。标准要求:只有在容器内部存在显式绑定时才应返回 true
- 许多解决方案即使在绑定不存在但请求的类 可以 解决的情况下也返回 true
,例如,其构造函数参数可以使用容器中的其他绑定填充。这种检查总是伴随着额外的类和方法反射创建,这会降低整体性能。这可能在通过 DI 容器解析 Yii 应用程序组件时产生重大影响。
建议在使用此扩展的容器之前解决 \Psr\Container\ContainerInterface::has()
实现中的任何不一致性。这可以通过使用 \yii1tech\di\external\ContainerProxy
类轻松实现。例如
<?php // file '/public/index.php' require __DIR__ . '../vendor/autoload.php'; // ... use DI\Container; use DI\ContainerBuilder; use yii1tech\di\DI; use yii1tech\di\external\ContainerProxy; // use 'PHP-DI' for the container: DI::setContainer(function () { $builder = new ContainerBuilder(); // ... return ContainerProxy::new($builder->build()) ->setCallbackForHas(function (Container $container, string $id) { return in_array($id, $container->getKnownEntryNames(), true); }); }); // ...