依赖注入

    安装

    该组件默认存在 项目中并作为主要组件存在,如希望在其它框架内使用该组件可通过下面的命令安装。

    通常来说,类的关系及注入是无需显性定义的,这一切 Hyperf 都会默默的为您完成,我们通过一些代码示例来说明一下相关的用法。假设我们需要在 IndexController 内调用 UserService 类的 getInfoById(int $id) 方法。

    1. <?php
    2. namespace App\Service;
    3. class UserService
    4. {
    5. public function getInfoById(int $id)
    6. {
    7. // 我们假设存在一个 Info 实体
    8. return (new Info())->fill($id);
    9. }
    10. }

    通过构造方法注入

    1. <?php
    2. namespace App\Controller;
    3. use App\Service\UserService;
    4. class IndexController
    5. {
    6. /**
    7. * @var UserService
    8. */
    9. private $userService;
    10. // 通过在构造函数的参数上声明参数类型完成自动注入
    11. public function __construct(UserService $userService)
    12. {
    13. $this->userService = $userService;
    14. }
    15. public function index()
    16. {
    17. $id = 1;
    18. // 直接使用
    19. return $this->userService->getInfoById($id);
    20. }
    21. }

    当您希望定义一个可选的依赖项时,可以通过给参数定义为 nullable 或将参数的默认值定义为 null,即表示该参数如果在 DI 容器中没有找到或无法创建对应的对象时,不抛出异常而是直接使用 null 来注入。(该功能仅在 1.1.0 或更高版本可用)

    1. <?php
    2. namespace App\Controller;
    3. use App\Service\UserService;
    4. class IndexController
    5. {
    6. /**
    7. */
    8. private $userService;
    9. // 通过设置参数为 nullable,表明该参数为一个可选参数
    10. public function __construct(?UserService $userService)
    11. {
    12. $this->userService = $userService;
    13. }
    14. public function index()
    15. {
    16. $id = 1;
    17. if ($this->userService instanceof UserService) {
    18. return $this->userService->getInfoById($id);
    19. }
    20. return null;
    21. }
    22. }

    通过 @Inject 注解注入

    1. <?php
    2. namespace App\Controller;
    3. use App\Service\UserService;
    4. use Hyperf\Di\Annotation\Inject;
    5. class IndexController
    6. {
    7. /**
    8. * 通过 `@Inject` 注解注入由 `@var` 注解声明的属性类型对象
    9. *
    10. * @Inject
    11. * @var UserService
    12. */
    13. private $userService;
    14. public function index()
    15. {
    16. $id = 1;
    17. // 直接使用
    18. return $this->userService->getInfoById($id);
    19. }
    20. }
    Required 参数

    基于上面的例子,从合理的角度上来说,Controller 面向的不应该直接是一个 UserService 类,可能更多的是一个 UserServiceInterface 的接口类,此时我们可以通过 config/autoload/dependencies.php 来绑定对象关系达到目的,我们还是通过代码来解释一下。

    定义一个接口类:

    1. <?php
    2. namespace App\Service;
    3. interface UserServiceInterface
    4. {
    5. public function getInfoById(int $id);
    6. }

    UserService 实现接口类:

    <?php
    namespace App\Service;
    
    class UserService implements UserServiceInterface
    {
        public function getInfoById(int $id)
        {
            // 我们假设存在一个 Info 实体
            return (new Info())->fill($id);    
        }
    }
    

    config/autoload/dependencies.php 内完成关系配置:

    <?php
    return [
        \App\Service\UserServiceInterface::class => \App\Service\UserService::class
    ];
    

    这样配置后就可以直接通过 UserServiceInterface 来注入 UserService 对象了,我们仅通过注解注入的方式来举例,构造函数注入也是一样的:

    <?php
    namespace App\Controller;
    
    use App\Service\UserServiceInterface;
    use Hyperf\Di\Annotation\Inject;
    
    class IndexController
    {
        /**
         * @Inject 
         * @var UserServiceInterface
         */
        private $userService;
    
        public function index()
        {
            $id = 1;
            // 直接使用
            return $this->userService->getInfoById($id);    
        }
    }
    

    我们假设 UserService 的实现会更加复杂一些,在创建 UserService 对象时构造函数还需要传递进来一些非直接注入型的参数,假设我们需要从配置中取得一个值,然后 UserService 需要根据这个值来决定是否开启缓存模式(顺带一说 Hyperf 提供了更好用的 功能)

    我们需要创建一个工厂来生成 UserService 对象:

    <?php
    namespace App\Service;
    
    class UserService implements UserServiceInterface
    {
    
        /**
         * @var bool
         */
        private $enableCache;
    
        public function __construct(bool $enableCache)
        {
            // 接收值并储存于类属性中
            $this->enableCache = $enableCache;
        }
    
        public function getInfoById(int $id)
        {
            return (new Info())->fill($id);    
        }
    }
    

    config/autoload/dependencies.php 调整绑定关系:

    <?php
    return [
        \App\Service\UserServiceInterface::class => \App\Service\UserServiceFactory::class
    ];
    

    这样在注入 UserServiceInterface 的时候容器就会交由 UserServiceFactory 来创建对象了。

    注意事项

    换种方式理解就是容器内管理的对象都是单例,这样的设计对于长生命周期的应用来说会更加的高效,减少了大量无意义的对象创建和销毁,这样的设计也就意味着所有需要交由 DI 容器管理的对象均不能包含 状态 值。状态 可直接理解为会随着请求而变化的值,事实上在 编程中,这些状态值也是应该存放于 协程上下文 中的,即 Hyperf\Utils\Context

    通过 new 关键词创建的对象毫无疑问的短生命周期的,那么如果希望创建一个短生命周期的对象但又希望通过依赖注入容器注入相关的依赖呢?这是我们可以通过 make(string $name, array $parameters = []) 函数来创建 $name 对应的的实例,代码示例如下:

    $userService = make(UserService::class, ['enableCache' => true]);
    

    获取容器对象

    有些时候我们可能希望去实现一些更动态的需求时,会希望可以直接获取到 容器(Container) 对象,在绝大部分情况下,框架的入口类(比如命令类、控制器、RPC服务提供者等)都是由 容器(Container) 创建并维护的,也就意味着您所写的绝大部分业务代码都是在 容器(Container) 的管理作用之下的,也就意味着在绝大部分情况下您都可以通过在 构造函数(Constructor) 声明或通过 @Inject 注解注入 Psr\Container\ContainerInterface 接口类都能够获得 Hyperf\Di\Container 容器对象,我们通过代码来演示一下:

    <?php
    namespace App\Controller;
    
    use Hyperf\HttpServer\Annotation\AutoController;
    use Psr\Container\ContainerInterface;
    
    class IndexController
    {
        /**
         * @var ContainerInterface
         */
        private $container;
    
        // 通过在构造函数的参数上声明参数类型完成自动注入
        public function __construct(ContainerInterface $container)
        {
            $this->container = $container;
        }
    }