monolyth / disclosure
Monolyth 无框架的依赖注入
Requires
- php: >=8.1
- psr/container: ^1.0
- toast/unit: ^2.0
Requires (Dev)
- gentry/gentry: ^0.16.0
README
PHP8 依赖注入和 inversion of control (IoC) 框架。大多数现有的 DI 或 IoC 解决方案都依赖于广泛的配置文件来定义依赖关系。这很糟糕;Disclosure 更好、更简单(我们认为)。
安装
Composer(推荐)
composer require monolyth/disclosure
手动安装
- 获取或克隆代码;
- 在您的 PSR-4 自动加载器中注册
/path/to/disclosure/src为命名空间Monolyth\\Disclosure\\;
使用
将依赖添加到某个地方的 Container 对象中。通常在中心文件(例如 src/dependencies.php)中这样做是有意义的,但也可以在与类定义一起进行。
<?php use Monolyth\Disclosure\Container; $container = new Container; $container->register(fn (&$foo) => $foo = new Foo);
容器现在将 foo 键与实例 Foo 的对象相关联。键的命名无关紧要;只需记住它们必须是唯一的。
您还可以向注册方法提供键值对的数组;这对于您总是需要的对象很有用,例如环境对象。
使用 Injector 特性提供的 inject 方法告诉您的类它们应该依赖什么
<?php use Monolyth\Disclosure\Injector; class MyClass { use Injector; public function __construct() { $this->inject(function ($foo, $bar) {}); // Or, alternatively: $this->inject('foo', 'bar'); } } class Foo { } $myInstance = new MyClass; var_dump($myInstance->foo instanceof Foo); // true
inject 接受任意数量的参数,其中每个参数都是一个字符串,表示依赖名称,或者是一个可调用的函数,其中包含依赖名称作为参数。您使用哪种样式取决于您的个人喜好。
使用属性进行注入
从版本 3.0 开始,还可以在 PHP8 属性 中指定依赖项。这是通过在应该注入的属性上指定 Monolyth\Disclosure\Depends 属性来完成的。属性名称应与注册的依赖项匹配。
使用属性指定依赖项时,您可以简单地调用不带任何参数的 inject。您也可以混合这些策略;由于注入名称必须是唯一的,所以这并不重要。
使用 Disclosure 工厂实例化
版本 3.0 中还新增了 Monolyth\Disclosure\Factory 的包含。通过其 build 方法构造的对象将自动添加依赖项
<?php use Monolyth\Disclosure\{ Depends, Factory }; class MyObject { [#Depends] private Foo $foo; public function __construct($someArgument, $anotherArgument) { $this->someArgument = $someArgument; $this->anotherArgument = $anotherArgument; } public function doSomething() { return $this->foo->method($this->someArgument, $this->anotherArgument); } } $myobject = Factory::build(MyObject::class, 'someArgument', 'anotherArgument'); var_dump($myobject->doSomething()); // Whatever Foo::method does...
使用提升的构造函数属性进行注入
PHP8 中的一项酷炫新功能是 提升的构造函数属性。简而言之,您现在可以这样做
<?php class Foo { private $bar; public function __construct(Bar $bar) { $this->bar = $bar; // ...other constructor stuff... } }
...
<?php class Foo { public function __construct(private Bar $bar) { // ...other constructor stuff, $this->bar is already set... } }
您猜怎么着?这些也可以被注释!您猜对了:如果您使用 Depends 注释提升的构造函数属性,并使用 Factory::build 方法进行构造,您甚至不必担心它们!
<?php use Monolyth\Disclosure\{ Inject, Factory }; class Foo { public function __construct( #[Depends] private Bar $bar ) { } } $foo = Factory::build(Foo::class);
任何非提升的构造函数参数都将按顺序从提供给 build 的附加参数中传递
<?php use Monolyth\Disclosure\{ Inject, Factory }; class Foo { public function __construct( #[Depends] private Bar $bar, string $someOtherArgument, #[Depends] public DateTime $dateTime, int $aNumber ); } $foo = Factory::build(Foo::class, 'Hello world!', 42);
请注意,当使用提升的参数进行注入时,如果您不使用此策略,则不再需要“使用”Injector 特性。
理论上,您还可以使提升的属性为可空,然后从构造函数(或任何其他地方)调用 inject。但是,你知道吗?真的吗?
调用也依赖于提升属性的父构造函数?
为此,Disclosure 提供了带有 callParentConstructor 方法的 Mother 特性。将任何额外的(非注入)参数作为,嗯,参数传递,特性能填补其余部分并在需要的地方注入
<?php use Monolyth\Disclosure\{ Factory, Mother, Depends }; class Foo { public function __construct( #[Depends] protected SomeDependency $something, public int $someArgument ) {} } class Bar extends Foo { use Mother; public function __construct(protected string $anotherArgument) { $this->callParentConstructor(42); echo get_class($this->something); // SomeDependency echo $this->someArgument; // 42 echo $this->anotherArgument; // hello world } } $bar = Factory::build(Foo::class, 'hello world');
当然,Mother 特性可以在父类是否使用 Factory::build 实例化或使用 Injector(或两者都不是)的情况下使用。所以这也很好
<?php $bar = new Bar('hello world');
解决循环依赖
有时你会遇到一个棘手的状况,其中依赖关系形成循环。所以,类A依赖于类B的一个对象,而类B又依赖于类A的一个对象。这会导致无限循环,并且根据你所使用的环境,可能会引发致命错误、段错误,或者只是一个非常无用的空白屏幕。
当Disclose检测到这种情况时,会抛出Monolyth\Disclosure\CircularDependencyException异常,并附带一条消息,详细说明导致循环依赖的完整堆栈。你必须使用这条消息来修复你的循环逻辑。这个异常扩展了PHP内置的LogicException。我们正在开发一个工具,试图识别这些问题(只要你的
假设你不能从逻辑上解决循环依赖(即A真的需要B,反之亦然),你最好的办法是回退到Injector,并即时(JIT)注入有问题的依赖项。这将允许相关对象完全实例化,之后问题通常就会消失。
另一种选择是不将有问题的对象作为依赖项注入,而是手动传递或设置它。