自动依赖注入可以帮助我们更快速地导入一个类,不用手动进行实例化就可以使用这个类。本文将使用反射来获取参数列表和类型,实现自动依赖注入,并获得类实例。

下面有Customer、Request、Config三个类,Customer类构造方法需要Request类实例,Request类构造方法需要Config类实例,如下

Config

class Config
{
    public function __construct()
    {
        echo 'Config.';
    }
}

Request

class Request
{
    public function __construct(Config $config)
    {
    }
}

Customer

class Customer
{
    public function __construct(Request $request)
    {
        var_dump($request);
    }
}

在通常情况下,我们的调用方式为

new Customer(new Request(new Config()));

这样调用需要我们手动去new一个Request类,然后给到Customer类的构造方法,如果Request类的构造方法也有一个或多个类实例作为参数,那我们就还需要手动实例化更多的类。参数自动注入可以帮助我们不用去考虑这些依赖,直接获取Customer实例,让程序自动去实例化所需要的依赖,原理就是使用反射获取参数列表,并判断参数类型,如果参数为类实例则自动实例化,并将实例当作参数来实例化需要的类

获取Customer类构造方法的参数类型

$reflectionClass = new ReflectionClass(Customer::class);
$construct = $reflectionClass->getConstructor();

foreach ($construct->getParameters() as $parameter) {
    // 这里假设只有类实例
    $parameterType = $parameter->getType();
    if ($parameterType) {
        var_dump($parameterType->getName());
    }
}

以上例程输出

string(7) "Request"

成功打印出类名,接下来就简单了。我们来实现一个Instance类,使用Instance类的静态方法get获取Customer实例,自动解决依赖问题。

class Instance
{
    /**
     * 获取一个类实例
     * @param $className
     * @return bool|object
     * @throws ReflectionException
     */
    public static function get($className)
    {
        $reflectionClass = new ReflectionClass($className);
        // 如果$className不能被实例化则直接抛出异常,测试return false
        if (!$reflectionClass->isInstantiable()) {
            return false;
        }
        // 检查构造方法,如果没有构造方法直接返回实例
        $construct = $reflectionClass->getConstructor();
        if (is_null($construct)) {
            return new $className;
        }
        // 检查构造方法的参数,如果没有参数则直接返回实例
        $parameters = $construct->getParameters();
        if (empty($parameters)) {
            return new $className;
        }
        // 调用$className的参数
        $callParameters = [];
        foreach ($parameters as $parameter) {
            $parameterType = $parameter->getType();
            $name = $parameter->getName();
            if (is_null($parameterType)) {
                // 如果不是类实例则使用默认值
                try {
                    $value = $parameter->getDefaultValue();
                } catch (ReflectionException $reflectionException) {
                    // 没有默认值则使用null
                    $value = null;
                }
                $callParameters[$name] = $value;
            } else {
                // 参数为一个类实例则递归实例化类
                $callParameters[$name] = self::get($parameterType->getName());
            }
        }
        // 返回准备好的实例
        return $reflectionClass->newInstanceArgs($callParameters);
    }
}

使用Instance类的get方法来获取Customer类实例

Instance::get(Customer::class);

已经成功打印出Request类的实例$request并且已经执行了Config类的构造方法。
至此,我们已经能够自动解决实例化时的依赖问题。如果需要解决方法依赖的话,原理也是类似的。

注:1、反射相关内容在https://www.php.net/manual/zh/book.reflection.php

标签: 参数注入, 自动注入

添加新评论