Maison > développement back-end > tutoriel php > Essai yii2 (7) Injection de dépendances - (3) Injection de dépendances de yii2

Essai yii2 (7) Injection de dépendances - (3) Injection de dépendances de yii2

黄舟
Libérer: 2023-03-04 21:18:01
original
1104 Les gens l'ont consulté


Le code principal de l'injection de dépendances yii2 est dans yiidi. Il y a 3 fichiers sous ce package (dossier), à savoir Container.php (conteneur), Instance.php (instance), ServiceLocator (localisateur de service) ), Parlons maintenant des deux premiers. Le localisateur de services peut comprendre le registre d'un service. Cela n'affecte pas notre discussion sur l'injection de dépendances.

Commençons par le code pour expliquer comment yii2 utilise l'injection de dépendances.

// 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));
    }
}
Copier après la connexion

En lisant le code ci-dessus, Yii::createObject() donne des "matières premières" qualifiées au "conteneur ($conteneur)" pour générer l'objet cible, alors le conteneur est notre "dépendance" Injecter" là où l'objet de production est produit. Alors, quand $container a-t-il été introduit (notez que static::$container est utilisé ici, pas self::$container) ? Vous souvenez-vous encore de la déclaration lors de l'importation du framework yii sur la page d'accueil ?

//导入yii框架
require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php');
Copier après la connexion

Le code est le suivant

//引入基本的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();
Copier après la connexion

Vous avez bien lu ! C'est la dernière phrase, yii2 utilise l'implémentation de yiidiContainer pour son propre usage. Voyons ensuite comment les conteneurs sont implémentés ?

En suivant la méthode static::$container->get() ci-dessus, avant d'expliquer la méthode get, nous devons d'abord comprendre plusieurs propriétés du conteneur, ce qui aidera à comprendre l'implémentation de get

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

ok, si vous êtes suffisamment prudent et comprenez les attributs ci-dessus, vous aurez probablement une compréhension générale du conteneur yii2. Commençons par get.

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;
}
Copier après la connexion

Après avoir recherché ce point, nous avons constaté que la fonction get n'est qu'une "entrée", la fonction principale est dans le build

//创建对象
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;
    }
}
Copier après la connexion

D'accord, le build est par ici , comme suit Voyons comment le conteneur obtient les poignées de réflexion et les dépendances

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];
}
Copier après la connexion

Voyons comment le conteneur garantit les dépendances

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;
}
Copier après la connexion

Voyant cela, nous maintenant que vous pouvez comprendre comment yii2 utilise des conteneurs pour implémenter "l'injection de dépendances", il y a une question, comment assurer la dépendance des fermetures ? Je pense que c'est parce que yii2 pense que l'existence de fermetures résout le problème des limitations, qu'il n'y a pas de dépendances, ou que les dépendances sont laissées aux développeurs à résoudre par eux-mêmes. De plus, si le paramètre du conteneur yii2 est une fermeture, une erreur se produira. En raison de la dépendance à la fermeture, lors de l'analyse des paramètres de fermeture, vous obtiendrez $dependencies[]
= Instance::of($. c == = null ? null : $c->getName()); Ce que vous obtenez est une instance de Closure, et lorsque vous instancierez cette instance plus tard, des problèmes surviendront, donc lorsque vous utiliserez le conteneur yii2 pour implémenter l'objet. , il est implémenté L'objet ne peut pas contenir de paramètres de fermeture, s'il existe un paramètre de fermeture, il doit avoir une valeur par défaut, ou garantir artificiellement que ce paramètre de fermeture sera transmis, en contournant l'instruction générée automatiquement.

Ce sont les principales fonctions du conteneur ok. Autres méthodes, set, setSingleton, has, hasSingleton, clear, vous saurez ce qu'elles signifient en un coup d'œil. De plus, ces méthodes ne sont fondamentalement pas utilisées dans le. framework (vous pouvez utiliser ces fonctions Write exit et voir si votre page sera vide), ou si vous utilisez le conteneur pour générer quelque chose vous-même, vous pouvez vérifier vous-même l'utilisation de ces fonctions.

Enfin, regardons quel rôle joue Instance

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

Dans le conteneur, ces deux méthodes d'Instance sont utilisées, ce qui signifie que Instance est dans l'instance et assure uniquement Dépendant disponibilité. De plus, Instance fournit également d'autres fonctions, dont get obtient l'objet instancié de l'identifiant correspondant à l'instance actuelle. De plus, il existe une fonction statique Ensure

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

Ce qui précède est yii2 Essay (7. ) Injection de dépendances - (3) Le contenu de l'injection de dépendances de yii2 Pour plus de contenu connexe, veuillez faire attention au site Web PHP chinois (m.sbmmt.com) !


Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal