quanta/container

实现 Psr-11 的简约依赖注入容器

1.0 2022-09-13 11:14 UTC

This package is auto-updated.

Last update: 2024-09-13 16:20:22 UTC


README

此包提供了一个实现 Psr-11 的简约依赖注入容器。

目标是实现一个具有最小配置即可工作的容器,实现接口别名和基本的自动装配机制。

入门

要求 php >= 7.4

安装 composer require quanta/container

运行测试 php ./vendor/bin/phpunit

使用 docker 测试特定的 php 版本

  • docker build . --build-arg PHP_VERSION=7.4 --tag quanta/container/tests:7.4
  • docker run --rm quanta/container/tests:7.4

基本用法

  • 容器条目使用任何可迭代对象定义,只要键可以转换为字符串
  • 任何非可调用值以原样返回,就像关联数组一样
  • 任何可调用值被视为一个工厂,用于构建关联的值(结果被缓存,因此可调用只运行一次,每次 ->get() 调用都返回相同的值)
<?php

// class definitions
final class SomeClass
{
    public function __construct(public SomeDependency $dependency)
    {
    }
}

final class SomeDependency
{
}

// container configuration
$container = new Quanta\Container([
    'id' => 'value',

    SomeClass::class => fn ($container) => new SomeClass(
        $container->get(SomeDependency::class),
    ),

    SomeDependency::class => fn () => new SomeDependency,

    'throwing' => function () {
        throw new Exception('some exception');
    },
]);

// true
$container instanceof Psr\Container\ContainerInterface;
$container->has('id');
$container->has(SomeClass::class);
$container->has(SomeDependency::class);
$container->has('throwing');
$container->get('id') === 'value';
$container->get(SomeClass::class) == new SomeClass(new SomeDependency);
$container->get(SomeDependency::class) == new SomeDependency;
$container->get(SomeClass::class) === $container->get(SomeClass::class);
$container->get(SomeDependency::class) === $container->get(SomeDependency::class);
$container->get(SomeClass::class)->dependency === $container->get(SomeDependency::class);

// false
$container->has('not.defined');

// throws Quanta\Container\NotFoundException
try {
    $container->get('not.defined');
} catch (Quanta\Container\NotFoundException $e) {
    // 'No 'not.defined' entry defined in the container'
    echo $e->getMessage() . "\n";
}

// throws Quanta\Container\ContainerException with the caught exception as previous
try {
    $container->get('throwing');
} catch (Quanta\Container\ContainerException $e) {
    // 'Cannot get 'throwing' from the container: factory has thrown an uncaught exception'
    echo $e->getMessage() . "\n";

    // 'some exception'
    echo $e->getPrevious()->getMessage() . "\n";
}

接口别名

  • 与字符串关联的接口名称被视为别名
// class definitions
interface SomeInterface
{
}

final class SomeImplementation implements SomeInterface
{
}

// container configuration
$container = new Quanta\Container([
    SomeInterface::class => SomeImplementation::class,

    SomeImplementation::class => fn () => new SomeImplementation,
]);

// true
$container->has(SomeInterface::class);
$container->has(SomeImplementation::class);

$container->get(SomeInterface::class) == new SomeImplementation;
$container->get(SomeImplementation::class) == new SomeImplementation;

$container->get(SomeInterface::class) === $container->get(SomeInterface::class);
$container->get(SomeInterface::class) === $container->get(SomeImplementation::class);
$container->get(SomeImplementation::class) === $container->get(SomeImplementation::class);

自动装配

容器将尝试使用简单的规则构建未定义类的实例,以推断构造函数参数值

  • 当参数类型是一个已定义的接口名称时,其值从容器中检索
  • 当参数类型是类名时,其值从容器中检索(如果未定义,则自动装配)
  • 当参数类型既不是接口/类名时,如果存在,则使用默认值
  • 如果参数允许,则使用 null 作为后备
  • 当以下情况发生时,会抛出 Quanta\Container\ContainerException 异常:
    • 无法推断参数值(类型不是接口/类名,没有默认值,不允许为 null)
    • 尝试推断具有联合/交集类型参数的值,没有默认值,不允许为 null(php 8.0/8.1)
    • 尝试自动装配抽象类或具有受保护/私有构造函数的类
  • ->has() 方法对任何现有类返回 true
  • 通过自动装配构建的对象被缓存

当需要更多控制类实例化时,必须定义一个工厂。

<?php

// class definitions
interface SomeInterface
{
}

final class SomeImplementation implements SomeInterface
{
}

final class AnotherUndefinedClass
{
}

final class UndefinedClass
{
    public function __construct(
        public SomeInterface $dependency1,
        public AnotherUndefinedClass $dependency2,
        public ?int $dependency3,
        public string $dependency4 = 'test',
    ) {
    }
}

// container configuration
$container = new Quanta\Container([
    SomeInterface::class => SomeImplementation::class,
]);

// true
$container->has(SomeInterface::class);
$container->has(SomeImplementation::class);
$container->has(UndefinedClass::class);
$container->has(AnotherUndefinedClass::class);

$container->get(SomeInterface::class) == new SomeImplementation;
$container->get(SomeImplementation::class) == new SomeImplementation;
$container->get(UndefinedClass::class) == new UndefinedClass(new SomeImplementation, new AnotherUndefinedClass, null);
$container->get(AnotherUndefinedClass::class) == new AnotherUndefinedClass;

$container->get(SomeInterface::class) === $container->get(SomeInterface::class);
$container->get(SomeInterface::class) === $container->get(SomeImplementation::class);
$container->get(SomeImplementation::class) === $container->get(SomeImplementation::class);
$container->get(UndefinedClass::class) === $container->get(UndefinedClass::class);
$container->get(AnotherUndefinedClass::class) === $container->get(AnotherUndefinedClass::class);

$container->get(UndefinedClass::class)->dependency1 === $container->get(SomeInterface::class);
$container->get(UndefinedClass::class)->dependency2 === $container->get(AnotherUndefinedClass::class);
$container->get(UndefinedClass::class)->dependency3 === null;
$container->get(UndefinedClass::class)->dependency4 === 'test';