go>> di>> 返回
项目作者: constanze-standard

项目描述 :
A PHP dependency injection manager
高级语言: PHP
项目地址: git://github.com/constanze-standard/di.git
创建时间: 2019-10-02T14:25:13Z
项目社区:https://github.com/constanze-standard/di

开源协议:Apache License 2.0

下载


DI 是一个 PHP 依赖注入管理组件,它与 PSR-11 标准配合,为你的程序提供多种形式的依赖注入解决方案:

它的主要功能包括:

  • 根据类型提示注入参数 (type-hint)
  • 通过注解 (Annotation) 的形式注入
  • 对类的属性进行注入
  • 通过多种形式向程序中注入自定义的参数
  • 获取参数列表

基本示例

通过 DI 管理器调用函数,并向函数中注入参数:

  1. <?php
  2. use ConstanzeStandard\Container\Container;
  3. use ConstanzeStandard\DI\Annotation\Params;
  4. use ConstanzeStandard\DI\Manager;
  5. require __DIR__ . '/../vendor/autoload.php';
  6. $injectionTest = new class() {
  7. /** @Params(arg1 = "foo") */
  8. public function __invoke($arg1)
  9. {
  10. echo $arg1;
  11. }
  12. };
  13. $container = new Container();
  14. $container->add('foo', 'bar');
  15. $manager = new Manager($container);
  16. $manager->call($injectionTest);
  17. // 输出: bar

DI\Manager 可以直接调用一个可调用 (callable) 对象,这个过程中,你可以选择一种方式将参数注入到程序中。接下来我们将由浅入深介绍更多的使用技巧。

谁来提供依赖项?

依赖项的来源有两个,一是容器 (container),如上例所示,@Params(arg1 = "foo") 就是将名为 arg1 的参数与名为 “foo” 的容器数据绑定起来;二是当我们通过 Manager 或 Resolver 调用程序时,也可以手动传入额外的自定义参数。

自定义参数

call 方法的第二个参数是自定义参数,我们有两种方式传递自定义参数:

  1. 传递以参数名称作为键值的自定义参数:
    ```php
    function injection_test($foo) {
    echo $foo;
    }

$manager->call(‘injection_test’, [‘foo’ => ‘bar’]);
// 输出: bar

  1. 这种形式的传参,无论参数在参数列表的什么位置都可以直接绑定。
  2. 2. 传递无指向的自定义参数数组:
  3. ```php
  4. function injection_test($foo) {
  5. echo $foo;
  6. }
  7. $manager->call('injection_test', ['bar']);
  8. // 输出: bar

这种形式的传参,会按参数列表的顺序,将值依次传递进去。

这两种形式可以结合使用,Manager 会首先注入指定名称(键)的自定义参数,之后会将剩余的,没有任何其他形式的注入,也没有默认值的参数,依次与自定义参数列表中剩余的参数进行绑定。

  1. function injection_test($a, $b, $c) {
  2. echo $a, $b, $c;
  3. }
  4. $manager->call('injection_test', [1, 'a' => 2, 3]);
  5. // 输出: 213

大部分情况下,我们并不推荐你将两种方式结合使用,传参应该按照一定的规则进行,否则便会造成视觉上的混乱。

根据类型提示注入参数

DI\Manager 支持类型提示形式的参数注入:

  1. class Serve {
  2. public $foo = 'bar';
  3. }
  4. $container->add(Serve::class, new Serve);
  5. function injection_test(Serve $serve) {
  6. echo $serve->foo;
  7. };
  8. $manager->call('injection_test');
  9. // 输出: bar

如上所示,通过 call 方法运行的程序,加类型声明会被判定为需要注入依赖项的参数,之后 Manager 会从容器中取出对应类型名称的依赖项传入。所以,如果你的参数不需要依赖注入,就不要加任何的类型声明。

通过注解 (Annotation) 的形式注入参数

DI 引入了 Annotation 组件,来解析类方法的注解,从而得到依赖关系,完成参数注入,这是一种比较高级的用法。

  1. $injectionTest = new class() {
  2. /**
  3. * @Params(arg1 = "foo")
  4. */
  5. public function __invoke($arg1)
  6. {
  7. echo $arg1;
  8. }
  9. };
  10. $container = new Container();
  11. $container->add('foo', 'bar');
  12. $manager = new Manager($container);
  13. $manager->call($injectionTest);
  14. // 输出: bar

通过注解法,我们可以摆脱类型声明的约束,将普通的容器索引与参数绑定,耦合度将进一步降低。
但需要注意的是,注解法只适用于类方法,并不适用于函数和闭包。

参数注入的顺序与规则

现在我们知道了很多种参数注入的方式,你可以随意的将它们搭配使用,并不会产生冲突。但当多种注入方法指向同一参数时,也会按照一定的优先级顺序进行注入。

Manager 在注入参数之前,按顺序做了以下几件事:

  • 解析方法的注解,并获取参数列表
  • 将注解得到的参数列表与自定义参数列表合并得到最终的参数列表(自定义参数列表会覆盖注解)。
  • 将参数列表中,与参数名称相对应的项注入给对应的参数。
  • 检测方法参数中,具有默认值的参数,然后跳过它们。
  • 检测参数的类型声明,然后根据参数类型,从容器中获取对应的数据并注入给参数。
  • 如果方法中有剩余的参数没有被处理,会将参数列表中没有指定名称(键)的参数,按顺序赋给它们。如果参数列表中没有足够的数据,将抛出一个 \InvalidArgumentException

所以,注入方式的优先级,按照从大到小的顺序,依次为:

  1. 与参数名称相对应的自定义参数 > 注解 > 参数默认值 > 类型提示 > 未指定参数名称的自定义参数

类的实例化与构造方法注入

Manager 除了可以调用程序之外,也可以实例化一个对象,在实例化的同时,对构造方法进行参数注入。

  1. class Client
  2. {
  3. /**
  4. * @Params(foo = "foo")
  5. */
  6. public function __construct($foo)
  7. {
  8. $this->foo = $foo;
  9. }
  10. }
  11. $container->add('foo', 'bar');
  12. $client = $manager->instance(Client::class);
  13. echo $client->foo;
  14. // 输出:bar

instance 方法与 call 方法的第一个参数是类的名称,第二个参数是向构造方法传递的自定义参数数组。

通过 instance 方法对构造方法的参数注入与普通方法相同,但 instance 方法返回的是实例化后的对象实例。

对类属性进行依赖注入

我们可以通过注解 (Annotation) 对类的属性进行依赖注入。

  1. use ConstanzeStandard\DI\Annotation\Property;
  2. class Client
  3. {
  4. /** @Property("foo") */
  5. public $foo;
  6. }
  7. $container->add('foo', 'bar');
  8. $client = new Client();
  9. $manager->resolvePropertyAnnotation($client);
  10. echo $client->foo;
  11. // 输出:bar

通过 Property 注解,将类属性与一个容器的数据绑定,并在调用 resolvePropertyAnnotation 方法时将依赖项注入到属性中。

Resolver

Manager 封装了比较常用的依赖注入操作,如果你还需要更加细化的功能,可以使用不同的 Resolver 进行控制。

获取参数列表

使用 CallableResolver 获取 Resolver 对象,然后你可以通过它获取可调用对象的参数列表:

  1. class serve { }
  2. $container->add(serve::class, new Serve());
  3. function resolver_test(Serve $serve, $foo) { }
  4. $resolver = $manager->getCallableResolver('resolver_test');
  5. $params = $resolver->resolveParameters(['foo' => 'bar']);
  6. print_r($params);
  7. /* 输出:
  8. Array
  9. (
  10. [0] => serve Object
  11. (
  12. )
  13. [1] => bar
  14. )
  15. */

获取构造方法的参数列表与可调用对象相似,但你需要通过 ConstructResolver 进行操作:

  1. class Client
  2. {
  3. public function __construct(serve $serve, $foo)
  4. {
  5. }
  6. }
  7. $resolver = $manager->getConstructResolver(Client::class);
  8. $params = $resolver->resolveParameters(['foo' => 'bar']);
  9. print_r($params);
  10. /* 输出:
  11. Array
  12. (
  13. [0] => serve Object
  14. (
  15. )
  16. [1] => bar
  17. )
  18. */

AnnotationResolver

AnnotationResolver 是针对注解形式的依赖注入进行控制的组件。
AnnotationResolver 可以帮助你对类属性进行注入、获取在方法上注解的参数、对方法进行注解形式的依赖注入并调用。

根据类属性获取 Property 对象:

获取 Property 对象需要借助 PHP 的 Reflection 机制:

  1. use ConstanzeStandard\DI\Annotation\Property;
  2. class Client
  3. {
  4. /** @Property("foo") */
  5. public $property1;
  6. }
  7. $instance = new Client();
  8. $reflectionClass = new \ReflectionClass($instance);
  9. $reflectionProperty = $reflectionClass->getProperty('property1');
  10. $resolver = $manager->getAnnotationResolver();
  11. $property = $resolver->getProperty($reflectionProperty);
  12. $name = $property->getName(); // foo

类属性注入

通过 Property 对类属性进行依赖注入,这与 Manager 相同(实际上 Manager 在内部调用了 Resolver)。

  1. $container->add('foo', 'bar');
  2. $resolver = $manager->getAnnotationResolver();
  3. $resolver->resolveProperty($instance);
  4. echo $instance->property1; // 输出:bar

获取类类方法上注解的参数列表

获取注解参数需要借助 PHP 的 Reflection 机制:

  1. use ConstanzeStandard\Container\Container;
  2. use ConstanzeStandard\DI\Annotation\Params;
  3. class Client
  4. {
  5. /**
  6. * @Params(
  7. * foo = "foo"
  8. * )
  9. */
  10. public function test($foo, $abc = 1)
  11. {
  12. }
  13. }
  14. $container = new Container();
  15. $container->add('foo', 'bar');
  16. $instance = new Client();
  17. $reflectionMethod = new \ReflectionMethod($instance, 'test');
  18. $resolver = $manager->getAnnotationResolver();
  19. $parameters = $resolver->resolveMethodParameters($reflectionMethod);
  20. print_r($parameters);
  21. /* 输出:
  22. Array
  23. (
  24. [foo] => bar
  25. )
  26. */

需要注意的是,resolveMethodParameters 方法只获取注解的参数列表,其他参数将被忽略。

自定义 Annotation Reader

DI 集成了 doctrine/annotations 帮助 Resolver 解析注释,这个组件的消耗比较大,所以通常情况下,需要使用一种 Annotation 缓存来规避性能损耗。这时,你可以将自定义的 Annotation Reader 传入 Manager 构造方法的第三个参数中,以替换默认的 \Doctrine\Common\Annotations\AnnotationReader.

  1. use Doctrine\Common\Annotations\AnnotationReader;
  2. use Doctrine\Common\Annotations\CachedReader;
  3. use Doctrine\Common\Cache\PhpFileCache;
  4. $reader = new CachedReader(
  5. new AnnotationReader(),
  6. new PhpFileCache(__DIR__ . '/cache'),
  7. $debug = true
  8. );
  9. $annotationResolver = new AnnotationResolver($container, $reader);
  10. $manager = new Manager($container, null, $annotationResolver);

这样就可以使用带有缓存功能的 CachedReader 以提升性能了。