aolbrich/php-di-container

PHP 依赖注入容器

v1.0.8 2023-03-20 17:31 UTC

This package is not auto-updated.

Last update: 2024-10-01 03:03:33 UTC


README

此类实现了一个简单的构造函数依赖注入容器

安装

composer require aolbrich/php-di-container

基本用法

如果类的构造函数实现了可调用的类,则它将自动绑定,无需映射。例如

class ExampleClass
{
    public function __construct(private readonly ExampleClass $exampleClass) {}

    ...    
}

实现

use Aolbrich\PhpDiContainer\Container;

// Create new container
$container = new Container();

// Resolve class
$class = $container->get(ExampleClass::class);

返回新类并自动绑定所有带有 @autowire 注解的方法

(注意:新类不会返回相同的类名,甚至可能不是从原始类继承而来的)

$container = new Container();
$container->set(ExampleServiceInterface::class, ExampleService::class);

$class = $container->resolveClass(ExampleClassForFunctionLevelResolve::class);

echo $class->getResponseWithAutowiredParams(); // they will be auto wired

使用数组设置解析定义

也可以通过构造函数传递定义到类中,或者调用 setDefinitions 方法而不是使用 set 方法。这使得可以一次性设置多个定义

示例

$definitions = [
    ExampleServiceInterface::class => ExampleService::class,
];

$container = new Container($definitions);

或者

$definitions = [
    ExampleServiceInterface::class => ExampleService::class,
];

$container = new Container();
$container->setDefinitions($definitions);

该类

class ExampleClassForFunctionLevelResolve
{
    /**
     * @Autowire
     */
    public function getResponseWithAutowiredParams(
        ExampleServiceInterface $exampleService,
        ExampleSubService $exampleSubService): string
    {
        return
            $exampleService->getResponse() . ' / ' .
            $exampleSubService->getResponse() . PHP_EOL;
    }
}

使用接口解析的用法

如果类实现了具有接口类型提示的构造函数注入,则容器无法自动解析依赖项,因此应通过 set 方法提供映射。映射可以通过接口和类名,或接口名和闭包来完成。

以下两个示例说明了这些解决方案

class ExampleClass
{
    public function __construct(private readonly ExampleInterface $example) {}

    ...    
}

实现

use Aolbrich\PhpDiContainer\Container;

// Create new container
$container = new Container();

// Configure what to resolve
$container->set(
    ExampleInterface::class,
    ExampleClass::class
);
$class = $container->get(ExampleClass::class);

使用闭包的相同实现

use Aolbrich\PhpDiContainer\Container;

// Create new container
$container = new Container();

// Configure what to resolve
$container->set(ExampleServiceInterface::class, function(Container $container) {
    return $container->get(ExampleService::class);
});

$class = $container->get(ExampleClass::class);

设置器自动绑定

还可以自动绑定设置器,自动绑定的要求是

  • 方法必须是公共的
  • 方法名必须以 "set" 开头,例如 "setLogger()"
  • 要自动绑定,需要添加 @autowire 注解。

可以有任何数量的设置器

示例类在一个设置器中绑定两个依赖项。

class ExampleSetterAutowireClass
{
    private ExampleServiceInterface $exampleService;
    private ExampleSubService $exampleSubService;

    /**
     * @Autowire
     */
    public function setAutowire(
        ExampleServiceInterface $exampleService,
        ExampleSubService $exampleSubService
    ) {
        $this->exampleService =  $exampleService;
        $this->exampleSubService = $exampleSubService;
    }

    public function getResponse(): string
    {
        return
            $this->exampleService->getResponse() . ' / ' .
            $this->exampleSubService->getResponse() . PHP_EOL;
    }
}

添加原始参数

可以在类解析中添加额外的参数以绑定

示例

$class = $container->get(ExampleParameterBinding::class, [
    'intValue' => 10,
    'stringValue' => "Hello String",
    'anyValue' => "This is any value"
]);
echo $class->getResponse();

类实现

class ExampleParameterBinding
{
    private mixed $anyValue;

    public function __construct(
        private readonly ExampleServiceInterface $exampleService,
        private readonly ExampleSubService $exampleSubService,
        private readonly int $intValue,
        private readonly string $stringValue,
        $anyValue,
    ) {
        $this->anyValue = $anyValue;
    }

    public function getResponse(): string
    {
        return
            $this->intValue . ' / ' .
            $this->stringValue . ' / ' .
            $this->anyValue . ' / ' .
            $this->exampleService->getResponse() . ' / ' .
            $this->exampleSubService->getResponse() . PHP_EOL;
    }
}

单例创建支持

可以使用 singleton() 函数创建类为单例,或者使用闭包自动绑定。示例

$container = new Container();

// Resolve as non singleton
$class = $container->get(ExampleSetterAutowireClass::class);
$class2 = $container->get(ExampleSetterAutowireClass::class);

echo $class === $class2 ? "Same class instance created\n" : "Different class instance created\n";

// Resolve as singleton
$class = $container->singleton(ExampleSetterAutowireClass::class);
$class2 = $container->singleton(ExampleSetterAutowireClass::class);

echo $class === $class2 ? "Same class instance created\n" : "Different class instance created\n";

// Autowire as Singleton
$container->set(ExampleService::class, function(Container $container) {
    return $container->singleton(ExampleService::class);
});

$class = $container->get(ExampleService::class);
$class2 = $container->get(ExampleService::class);

echo $class === $class2 ? "Same class instance created\n" : "Different class instance created\n";

运行单元测试

./vendor/bin/phpunit test

工具

运行代码质量检查

./vendor/bin/phpstan analyse src test
./vendor/bin/psalm --show-info=true

注意

这不是一个完整的依赖注入容器实现。它只解析构造函数依赖。

缺少的功能/将添加

  • 构造函数注入
  • 设置器注入
  • 方法注入
  • 单例支持
  • 属性注入(不会实现,因为不再被视为最佳实践)
  • 注入原始参数值
  • 缓存
  • 别名
  • 注解(部分通过设置器中的 @autowire 关键字实现)
  • 不可变设置器注入
  • 仅添加了基本的循环引用检查,将改进以检查例如,如果 ClassA 依赖于 ClassB,而 ClassB 依赖于 ClassC,而 ClassC 又反过来依赖于 ClassA,则代码将不会检测到这种循环引用。这可以通过维护依赖项堆栈并检查堆栈中的循环来解决。

许可证

MIT 许可证