monolyth/disclosure

Monolyth 无框架的依赖注入

3.2.1 2022-10-29 04:57 UTC

README

PHP8 依赖注入和 inversion of control (IoC) 框架。大多数现有的 DI 或 IoC 解决方案都依赖于广泛的配置文件来定义依赖关系。这很糟糕;Disclosure 更好、更简单(我们认为)。

安装

Composer(推荐)

composer require monolyth/disclosure

手动安装

  1. 获取或克隆代码;
  2. 在您的 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)注入有问题的依赖项。这将允许相关对象完全实例化,之后问题通常就会消失。

另一种选择是不将有问题的对象作为依赖项注入,而是手动传递或设置它。