yii2 エッセイ (7) 依存性の注入 - (3) yii2 の依存性の注入

黄舟
リリース: 2023-03-04 21:18:01
オリジナル
1090 人が閲覧しました


yii2 の依存関係注入のコア コードは yiidi にあります。このパッケージ (フォルダー) には、Container.php (コンテナー)、Instance.php (インスタンス)、ServiceLocator (サービス ロケーター) という 3 つのファイルがあります。最初の 2 つは、サービス ロケーターはサービスのレジストリを理解できますが、これは依存関係注入のアプリケーションでもあります。

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;//依赖,它的键值是类的名字,值是要生成对象前的一些必备“原材料”,在生成对象的时候,通过反射函数得到。
ログイン後にコピー

ok の実装を理解するのに役立ちます。十分に注意して上記の属性を理解すれば、おそらく yii2 コンテナについて一般的に理解できるでしょう。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;
}
ログイン後にコピー

この点を調査した結果、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コン​​テナのパラメータがクロージャの場合、クロージャに依存しているため、クロージャのパラメータを解析するとエラーが発生します $dependency[]
= Instance::of($c = == null ? null : $c->getName()); 得られるのは Closure のインスタンスです。したがって、yii2 コンテナを使用してオブジェクトを実装すると、問題が発生します。実装されたオブジェクトのクロージャ パラメータを含めることはできません。クロージャ パラメータがある場合は、デフォルト値を持たせるか、自動的に生成されたステートメントをバイパスして、このクロージャ パラメータが確実に渡されるようにする必要があります。

その他のメソッド、set、setSingleton、has、hasSingleton、clear は、これらが何を意味するか一目でわかります。また、これらのメソッドは基本的にフレームワークでは使用されません。これらの関数に exit を記述することもできます (「ページが空白かどうかを確認する」を参照)。コンテナを使用して自分で何かを生成する場合は、これらの関数の使用法を自分で確認できます。

最後に、インスタンスがどのような役割を果たしているか見てみましょう

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

コンテナでは、インスタンスのこれら 2 つのメソッドが使用されます。つまり、インスタンスは、インスタンス内の依存関係の可用性のみを保証します。さらに、Instance は他の関数も提供しており、get は現在のインスタンスに対応する ID のインスタンス化されたオブジェクトを取得します。さらに、静的関数 ensure

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

以上が yii2 Essay (7) dependency Injection - (3) です。 ) yii2 の依存性注入コンテンツについては、PHP 中国語 Web サイト (www.php.cn) にさらに関連コンテンツがあるのでご注意ください。


関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート