>  기사  >  백엔드 개발  >  yii2 Essay (7) 의존성 주입 - (3) yii2의 의존성 주입

yii2 Essay (7) 의존성 주입 - (3) yii2의 의존성 주입

黄舟
黄舟원래의
2017-01-17 10:36:511092검색


yii2 종속성 주입의 핵심 코드는 yiidi에 있습니다. 이 패키지(폴더)에는 Container.php(컨테이너), Instance.php(인스턴스), ServiceLocator(서비스 로케이터)라는 3개의 파일이 있습니다. 이제 처음 두 가지에 대해 논의하겠습니다. 서비스 로케이터는 서비스의 레지스트리를 이해할 수 있습니다. 이는 종속성 주입에 대한 논의에 영향을 주지 않습니다.

yii2가 종속성 주입을 사용하는 방법을 설명하는 코드부터 시작하겠습니다.

// yii\base\application
//这个是yii2的依赖注入使用入口,参数的解释请参考源码,这里不多解释
public static function createObject($type, array $params = [])
{
    if (is_string($type)) {//type 是字符串的话,它就把type当做一个对象的“原材料”,直接把它传给容器并通过容器得到想要的对象。
        return static::$container->get($type, $params);
    } elseif (is_array($type) && isset($type['class'])) {
    //type 是数组,并且有class的键,经过简单处理后,得到对象的“原材料”,然后把得到的“原材料”传给容器并通过容器得到想要的对象。
        $class = $type['class'];
        unset($type['class']);
        return static::$container->get($class, $params, $type);
    } elseif (is_callable($type, true)) {//如果type是可调用的结构,就直接调用
        return call_user_func($type, $params);
    } elseif (is_array($type)) {//如果type是array,并且没有'class'的键值,那么就抛出异常
        throw new InvalidConfigException('Object configuration must be an array containing a "class" element.');
    } else {//其他情况,均抛出另一个异常,说type不支持的配置类型
        throw new InvalidConfigException("Unsupported configuration type: " . gettype($type));
    }
}

위 코드를 읽으면 Yii::createObject()는 대상 개체를 생성하기 위해 "컨테이너($container)"에 정규화된 "원재료"를 제공하고, 컨테이너는 "의존성 주입"입니다. 물건이 생산되는 곳. 그러면 $container는 언제 도입되었습니까(여기에서는 self::$container가 아니라 static::$container가 사용되었습니다). 홈 페이지에서 yii 프레임워크를 가져올 때의 설명을 아직도 기억하시나요?

//导入yii框架
require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php');

코드는 다음과 같습니다

//引入基本的yii框架
require(__DIR__ . '/BaseYii.php');
//只是做了继承,这里给我们留了二次开发的余地,虽然很少能用到
class Yii extends \yii\BaseYii
{
}
//设置自动加载
spl_autoload_register(['Yii', 'autoload'], true, true);
//注册 classMap
Yii::$classMap = require(__DIR__ . '/classes.php');
//注册容器
Yii::$container = new yii\di\Container();

잘 읽으셨네요! 이것이 마지막 문장입니다. yii2는 자체 용도로 yiidiContainer 구현을 사용합니다. 다음으로 컨테이너가 어떻게 구현되는지 살펴보겠습니다.

위의 static::$container->get() 메소드에 이어 get 메소드를 설명하기 전에 먼저 컨테이너의 여러 속성을 이해해야 합니다. 이는 get

$_singletons; // 单例数组,它的键值是类的名字,如果生成的对象是单例,则把他保存到这个数组里,值为null的话,表示它还没有被实例化
$_definitions;// 定义数组,它的键值是类的名字,值是生成这个类所需的“原材料”,在set 或 setSingleton的时候写入
$_params; // 参数,它的键值是类的名字,值是生成这个类所需的额外的“原材料”,在set 或 setSingleton的时候写入
$_reflections; //反射,它的键值是类的名字,值是要生成的对象的反射句柄,在生成对象的时候写入
$_dependencies;//依赖,它的键值是类的名字,值是要生成对象前的一些必备“原材料”,在生成对象的时候,通过反射函数得到。
구현을 이해하는 데 도움이 됩니다.

자, 충분히 주의 깊게 위의 속성을 이해했다면 아마도 yii2 컨테이너에 대한 일반적인 이해를 갖게 될 것입니다.

public function get($class, $params = [], $config = [])
{
    if (isset($this->_singletons[$class])) {//查看将要生成的对象是否在单例里,如果是,则直接返回
        // singleton
        return $this->_singletons[$class];
    } elseif (!isset($this->_definitions[$class])) {//如果没有要生成类的定义,则直接生成,yii2自身大部分走的是这部分,并没有事先在容器里注册什么,
    那么配置文件是在哪里注册呢?还记的文章最开始的时候的"服务定位器"么?我们在服务定位器里讲看到这些。
        return $this->build($class, $params, $config);
    }
    //如果已经定义了这个类,则取出这个类的定义
    $definition = $this->_definitions[$class];

    if (is_callable($definition, true)) {//如果定义是可调用的结构
        //先整合一下参数,和$_params里是否有这个类的参数,如果有则和传入的参数以传入覆盖定义的方式整和在一起
        //然后再检查整合后的参数是否符合依赖,就是说是否有必填的参数,如果有直接抛出异常,否则返回参数。检查依赖的时候,需要判断是否为实例(Instance),如果是,
        则要实现实例。注意:这里出现了Instance。
        $params = $this->resolveDependencies($this->mergeParams($class, $params));
        //把参数专递给可调用结果,返回结果
        $object = call_user_func($definition, $this, $params, $config);
    } elseif (is_array($definition)) {//如果定义是一个数组
        //把代表要生成的class取出
        $concrete = $definition['class'];
        //注销这个键值
        unset($definition['class']);
        //把定义 和 配置整合成新的定义
        $config = array_merge($definition, $config);
        //整合参数
        $params = $this->mergeParams($class, $params);
        //如果传入的$class 和 定义里的class完全一样,则直接生成,build第一个参数确保为真实的类名,而传入的$type可能是别名
        if ($concrete === $class) {
            $object = $this->build($class, $params, $config);
        } else {//如果是别名,则回调自己,生成对象,因为这时的类也有可能是别名
            $object = $this->get($concrete, $params, $config);
        }
    } elseif (is_object($definition)) {//如果定义是一个对象,则代表这个类是个单例,保存到单例里,并返回这个单例,这里要自己动脑想一下,
    为什么是个对象就是单例?只可意会不可言传,主要是我也组织不好语言怎么解释它。
        return $this->_singletons[$class] = $definition;
    } else {//什么都不是则抛出异常
        throw new InvalidConfigException("Unexpected object definition type: " . gettype($definition));
    }
    //判断这个类的名字是否在单例里,如果在,则把生成的对象放到单例里
    if (array_key_exists($class, $this->_singletons)) {
        // singleton
        $this->_singletons[$class] = $object;
    }
    //返回生成的对象
    return $object;
}

이 점을 조사한 결과 get 함수는 단지 "항목"이고 기본 함수는 빌드에 있다는 것을 발견했습니다.

//创建对象
protected function build($class, $params, $config)
{
    //通过类名得到反射句柄,和依赖(依赖就是所需参数)
    //所以前面提到,传输buile的第一个参数必须为有效的“类名”否则,会直接报错
    list ($reflection, $dependencies) = $this->getDependencies($class);
    //把依赖和参数配置,因为依赖可能有默认参数,这里覆盖默认参数
    foreach ($params as $index => $param) {
        $dependencies[$index] = $param;
    }
    //确保依赖没问题,所有原材料是否都ok了,否则抛出异常
    $dependencies = $this->resolveDependencies($dependencies, $reflection);
    if (empty($config)) {//如果config为空,则返回目标对象
        return $reflection->newInstanceArgs($dependencies);
    }
    
    if (!empty($dependencies) && $reflection->implementsInterface('yii\base\Configurable')) 
    {//如果目标对象是 Configurable的接口
        // set $config as the last parameter (existing one will be overwritten)
        $dependencies[count($dependencies) - 1] = $config;
        return $reflection->newInstanceArgs($dependencies);
    } else {//其他的情况下
        $object = $reflection->newInstanceArgs($dependencies);
        foreach ($config as $name => $value) {
            $object->$name = $value;
        }
        return $object;
    }
}

좋아, 빌드는 여기까지입니다. 함께 살펴보기 컨테이너는 어떻게 리플렉션 핸들과 종속성을 얻나요

protected function getDependencies($class)
{
    if (isset($this->_reflections[$class])) {//是否已经解析过目标对象了
        return [$this->_reflections[$class], $this->_dependencies[$class]];
    }
   
    $dependencies = [];//初始化依赖数组
    $reflection = new ReflectionClass($class);//得到目标对象的反射,请参考php手册

    $constructor = $reflection->getConstructor();//得到目标对象的构造函数
    if ($constructor !== null) {//如果目标对象有构造函数,则说明他有依赖
        //解析所有的参数,注意得到参数的顺序是从左到右的,确保依赖时也是按照这个顺序执行
        foreach ($constructor->getParameters() as $param) {
            if ($param->isDefaultValueAvailable()) {//如果参数的默认值可用
                $dependencies[] = $param->getDefaultValue();//把默认值放到依赖里
            } else {//如果是其他的
                $c = $param->getClass();//得到参数的类型,如果参数的类型不是某类,是基本类型的话,则返回null
                //如果,是基本类型,则生成null的实例,如果不是基本类型,则生成该类名的实例。
                注意:这里用到了实例(Instance)
                $dependencies[] = Instance::of($c === null ? null : $c->getName());
            }
        }
    }
    //把引用保存起来,以便下次直接使用
    $this->_reflections[$class] = $reflection;
    //把依赖存起来,以便下次直接使用
    $this->_dependencies[$class] = $dependencies;
    //返回结果
    return [$reflection, $dependencies];
}

컨테이너가 어떻게 종속성을 보장하는지 살펴보겠습니다

protected function resolveDependencies($dependencies, $reflection = null)
{
    //拿到依赖关系
    foreach ($dependencies as $index => $dependency) {
        //如果依赖是一个实例,因为经过处理的依赖,都是Instance的对象
        if ($dependency instanceof Instance) {
            if ($dependency->id !== null) {//这个实例有id,则通过这个id生成这个对象,并且代替原来的参数
                $dependencies[$index] = $this->get($dependency->id);
            } elseif ($reflection !== null) {//如果反射句柄不为空,注意这个函数是protected 类型的,
            所以只有本类或者本类的衍生类可访问,但是本类里只有两个地方用到了,一个是 get 的时候,
            如果目标对象是可调用的结果(is_callable),那么$reflection===null,另外一个build的时候,
            $reflection不为空,这个时候代表目标对象有一个必须参数,但是还不是一个实例(Instance的对象),
            这个时候代表缺乏必须的“原材料”抛出异常
                //则拿到响应的必填参数名字,并且抛出异常
                $name = $reflection->getConstructor()->getParameters()[$index]->getName();
                $class = $reflection->getName();
                throw new InvalidConfigException("Missing required parameter \"$name\" when instantiating \"$class\".");
            }
        }
    }

    //确保了所有的依赖后,返回所有依赖,如果目标是is_callable($definition, true),则不会抛出异常,仅仅把Instance类型的参数实例化出来。
    return $dependencies;
}

이를 보면 yii2가 컨테이너를 어떻게 사용하는지 이해할 수 있습니다. 이제 "종속성은 주입"이 구현되면 질문이 있습니다: 클로저의 종속성을 어떻게 보장합니까? yii2는 클로저의 존재가 제한 문제를 해결하거나, 종속성이 없거나, 종속성을 개발자가 스스로 해결하도록 남겨둔다고 믿기 때문이라고 생각합니다. 또한 yii2 컨테이너의 매개변수가 클로저인 경우 클로저에 대한 종속성으로 인해 오류가 발생하며 클로저 매개변수를 구문 분석할 때 $dependents[]
= Instance::of($를 얻습니다. c == = null ? null : $c->getName()); 얻는 것은 Closure의 인스턴스인데 나중에 이 인스턴스를 인스턴스화하면 문제가 발생하므로 yii2 컨테이너를 사용하여 객체를 구현할 때 , 구현됩니다. 객체는 클로저 매개변수를 포함할 수 없습니다. 클로저 매개변수가 있는 경우 기본값을 가지거나 자동으로 생성된 명령문을 우회하여 이 클로저 매개변수가 전달되도록 인위적으로 보장해야 합니다.

ok 컨테이너의 주요 기능입니다. 다른 메소드인 set, setSingleton, has, hasSingleton,clear도 한눈에 알 수 있습니다. 또한 이러한 메소드는 기본적으로 사용되지 않습니다. 프레임워크(이 기능을 사용하여 종료하고 페이지가 비어 있는지 확인할 수 있음) 또는 컨테이너를 사용하여 직접 무언가를 생성하는 경우 이러한 기능의 사용법을 직접 확인할 수 있습니다.

마지막으로 인스턴스가 어떤 역할을 하는지 살펴보겠습니다.

//yii\di\Instance
//很诧异吧,就是实例化一个自己,注意这个自己是 static,以后你可能需要用到这个地方
public static function of($id)
{
    return new static($id);
}
[/php]
那么这个函数的构造函数呢?
[php]
//禁止外部实例化
protected function __construct($id)
{
    //赋值id
    $this->id = $id;
}

컨테이너에서는 이 두 가지 Instance 방식을 사용하는데, 이는 Instance가 인스턴스 내에서만 종속성을 보장한다는 의미입니다. 또한 Instance는 다른 기능도 제공하는데, 그 중 get은 현재 Instance에 해당하는 id의 인스턴스화된 개체를 가져옵니다. 또한 정적 기능인 verify

//确保 $reference 是 $type类型的,如果不是则抛出异常
//在框架中多次用到,请自行查找
//另外,如果$type==null的时候,他也可以当做依赖注入的入口,使用方法请自行查看源码,到现在你应该可以自己看懂这些代码了。
public static function ensure($reference, $type = null, $container = null)
{
    //...
}

가 있습니다. 위는 yii2 Essay (7) 종속성입니다. 주입 — —(3) yii2의 종속성 주입 내용, 더 많은 관련 내용을 보려면 PHP 중국어 웹사이트(www.php.cn)를 참고하세요!


성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.