利用 PHP 8 的注解特性来实现依赖注入

问题

在使用 ThinkPHP(以下简称 tp)开发时,我们经常会遇到需要依赖注入的场景。比如在控制器类方法里注入 Request 类:

public function index(Request $request)
{
    // todo
}

但是,当我们需要一个类为整个控制器服务时,上述方法就不适用了。这种方式只适合某一个方法内使用。如果多个方法都要使用这个类,那么就需要将该类作为控制器的成员变量来使用。

class Index {
    private Request $request;
}

既然作为成员变量使用,那么我们需要在系统调用控制器方法前实例化 Request,比如放在构造函数里实例化:

public function __construct()
{
    // tp6/8 里 app 函数可以实例化 Request 等类
    $this->request = app('request');
}

当然,我们也可以利用 tp 的自动注入机制来实例化:

public function __construct(Request $request)
{
    $this->request = $request;
}

这种方式在一个或两个控制器里使用时还是方便的。但是,当项目里有较多控制器时,这种方法显得繁琐。

解决

我的编程原则是尽量减少重复性高的代码和步骤。因此,我们应该让调用的类在声明后,自动注入到控制器类中。这点我们可以参考 Spring Boot 里的方式:

@RestController
public class SysLoginController
{
    @Autowired
    private SysLoginService loginService;
}

使用 @Autowired 注解将需要的服务类注入到控制器中,这样就不需要手动实例化类了。

在 PHP 8 的新特性中,也增加了注解功能,我们可以利用注解来实现自动依赖注入,如:

class Index extends Controller
{
    #[Inject]
    private LoginService $service;

    public function login(string $username, string $password)
    {
        // 不需要手动实例化,直接调用登录服务类实现登录
        $this->service->login($username, $password);
        // .....
    }
}

在上面的例子里,模拟了一个登录场景。我们在控制器里声明了一个 LoginService 服务类,并且没有在控制器的任何地方实例化这个类,而是在登录方法里直接调用了 LoginService 类的方法,省去了实例化 LoginService 类的步骤。

我们使用了 #[Inject] 注解,来实现 LoginService 的自动注入。不过,PHP 8 并没有内置这个注解,我们需要自定义这个注解来实现这个功能。

在上面的代码里,Index 继承了 Controller,Controller 里完成了对 LoginService 的依赖注入。打开 Controller 查看源代码:

abstract class Controller
{
    public function __construct()
    {
        $this->inject();
    }

    protected function inject(): void
    {
        $reflect = new \ReflectionObject($this);
        $properties = $reflect->getProperties(\ReflectionProperty::IS_PRIVATE);
        foreach ($properties as $property) {
            if ($getAttribute = ($property->getAttributes()[0] ?? false)) {
                if ('app\annotation\Inject' === $getAttribute->getName()) {
                    $this->{$property->name} = app()->make($property->getType()->getName());
                }
            }
        }
    }

}

可以注意到,Controller 的构造方法里调用了 inject() 方法,正是这个方法实现了依赖注入。

分析代码,首先是创建对象的反射对象 $reflect = new \ReflectionObject($this),接着是用 $reflect->getProperties() 获取控制器类里的私有成员变量,随后遍历这些成员变量,使用 $property->getAttributes() 获取变量的所有注解,并取第一个注解,检查注解的名称是否为 app\annotation\Inject。如果是,就调用 app()->make() 来实例化注入类的实例,并将实例化后的对象赋值给当前对象的对应变量。

总结来说,就是利用了反射(Reflection)和属性注解(Attributes)来动态地处理类属性的依赖注入。

app\annotation\Inject 类则是用于定义注解类。由于我们并没有在示例中详细使用这个类,所以里面并没有其他代码,只是定义了类:

namespace app\annotation;

class Inject
{
}

本文只展示了利用注解来简单实现自动注入,其实并没有真正使用到注解的特性。比如定义自定义注解的作用范围(Attribute)来进行范围和语法检测等。

总结

通过上面的三个文件,我们实现了通过 #[Inject] 注解来完成依赖注入。这种方式减少了在构造函数中手动实例化依赖的繁琐步骤,使代码更加简洁和易于维护。

本文展示了如何通过 PHP 8 的注解特性结合反射机制来实现自动依赖注入,这种方式借鉴了 Spring Boot 的依赖注入方式,能够有效地提高代码的可维护性和可读性。希望这篇文章对你在实际开发中有所帮助。

  • 25
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值