quanta / container
实现 Psr-11 的简约依赖注入容器
1.0
2022-09-13 11:14 UTC
Requires
- psr/container: ^2.0
Requires (Dev)
- phpstan/phpstan: ^1.8
- phpunit/phpunit: ^9.5
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';