今日は当初 yii2.0 の AR モデルの実装原理を勉強する予定でしたが、計画が変更に追いつかず、急遽 yii2.0 のデータベースコンポーネントの作成プロセスを勉強することになりました。 yii ソースコードの学習を通じて、yii コンポーネントの登録と作成のプロセスについて学び、元の yii コンポーネントは登録直後に作成されるのではなく、特定のコンポーネントが実際に必要になったときに対応するコンポーネントが作成されることがわかりました。この記事ではその探索過程を大まかに記録します。
yii コンポーネントの登録と作成を理解するには、もちろん yiiエントリー ファイルindex.php から始める必要があります。ファイル コード全体は次のとおりです:
<?php defined('YII_DEBUG') or define('YII_DEBUG', true); defined('YII_ENV') or define('YII_ENV', 'dev'); require(DIR . '/../../vendor/autoload.php'); require(DIR . '/../../vendor/yiisoft/yii2/Yii.php'); require(DIR . '/../../common/config/bootstrap.php'); require(DIR . '/../config/bootstrap.php'); $config = yii\helpers\ArrayHelper::merge( require(DIR . '/../../common/config/main.php'), require(DIR . '/../../common/config/main-local.php'), require(DIR . '/../config/main.php'), require(DIR . '/../config/main-local.php') ); (new yii\web\Application($config))->run();
エントリー ファイルにはいくつかの設定が導入されていることがわかります。ファイルとすべて 構成ファイルの内容は $config 構成配列にマージされ、この構成配列はアプリケーション インスタンスを作成するためのパラメーターとして使用されます。この設定配列を出力すると、添字「components」に対応する要素に yii コンポーネントのパラメータ情報が含まれていることがわかります (ここではスクリーンショットのごく一部のみを抜粋しています):
The information ofこれらのコンポーネントは、いくつかの設定ファイルで設定され、これらのパラメータ情報を使用して登録および作成されます。
次に、yiiwebApplication クラスのインスタンス化プロセスに入ります。 yiiwebApplication クラスには constructor がありませんが、yiibaseApplication クラスを継承します:
したがって、yiibaseApplication クラスのコンストラクターが自動的に実行されます:
public function construct($config = []) { Yii::$app = $this; static::setInstance($this); $this->state = self::STATE_BEGIN; $this->preInit($config); $this->registerErrorHandler($config); Component::construct($config); }
Byちなみに、ここに前置きの初期化メソッド preInit() があります。そのコードは次のとおりです:
public function preInit(&$config) { /* 此处省略对$config数组的预处理操作代码 */ // merge core components with custom components foreach ($this->coreComponents() as $id => $component) { if (!isset($config['components'][$id])) { $config['components'][$id] = $component; } elseif (is_array($config['components'][$id]) && !isset($config['components'][$id]['class'])) { $config['components'][$id]['class'] = $component['class']; } } }
この関数は、コンストラクターに渡された構成配列 $config に対していくつかの前処理操作を実行し (ここでは省略)、最後に によって返された配列を使用します。 $config への coreComponents() メソッド 配列が改良され、coreComponents() メソッドは次のようになります:
public function coreComponents() { return [ 'log' => ['class' => 'yii\log\Dispatcher'], 'view' => ['class' => 'yii\web\View'], 'formatter' => ['class' => 'yii\i18n\Formatter'], 'i18n' => ['class' => 'yii\i18n\I18N'], 'mailer' => ['class' => 'yii\swiftmailer\Mailer'], 'urlManager' => ['class' => 'yii\web\UrlManager'], 'assetManager' => ['class' => 'yii\web\AssetManager'], 'security' => ['class' => 'yii\base\Security'], ]; }
実際には、これはいくつかのコアコンポーネントの設定であり、これらのコンポーネントを設定する必要がないことを意味します設定ファイルに、Yii がそれらを自動的に登録します。
さて、yiibaseApplication クラスのコンストラクターに戻ります。この関数は最終的に yiibaseComponent クラスのコンストラクターを呼び出しますが、yiibaseComponent クラスにはコンストラクターはありませんが、yiibaseObject クラスを継承します。
したがって、yiibaseObject クラスも自動的に呼び出されます。実行されたコンストラクター:
public function construct($config = []) { if (!empty($config)) { Yii::configure($this, $config); } $this->init(); }
これは主に yiiBaseYii クラスの静的メソッド configure() を呼び出します:
public static function configure($object, $properties) { foreach ($properties as $name => $value) { $object->$name = $value; } return $object; }
このメソッドはループ エントリ ファイルの $config 配列です (new yiiwebApplication($config))->run();
(この配列の構造については、この記事の最初のスクリーンショットを参照してください) )、配列を使用します。キー名はオブジェクト属性名として使用され、対応するキー値は代入操作のオブジェクト属性値として使用されます。したがって、コンポーネント構成パラメータにループする場合、次のようになります: $object->components = $value ($value はすべてのコンポーネントの構成配列です)。つまり、$object のコンポーネント属性に値を割り当てます。 this $object それはオブジェクトのどのクラスですか?最初の呼び出しのソースを振り返ると、実際には、エントリ ファイル内でインスタンス化する必要がある yiiwebApplication クラスのオブジェクトです。ただし、このクラスにもその祖先クラスにもメンバー変数コンポーネントはありません。yiiwebApplication クラスの継承関係をたどってレイヤーごとに検索する必要があります。クラスは最終的にも継承します。 yiibaseObject クラスは属性をサポートするため、 yiiwebApplication クラスも属性をサポートします (属性については、私の他のブログ投稿: yii2 の属性を参照してください)。代入操作でコンポーネントのメンバー変数が見つからない場合、setComponents() は次のことを行います。メソッドが呼び出され、このメソッドの場所を探したところ、最終的にその祖先クラス yiidiServiceLocator で setComponents() メソッドが見つかりました。アプリケーション インスタンスのコンポーネント属性に値を割り当てると、実際にこのメソッドが呼び出されます。
さて、setComponents() メソッドが何をするのか見てみましょう:
public function setComponents($components) { foreach ($components as $id => $component) { $this->set($id, $component); } }
実際はとても簡単で、各コンポーネントの構成配列をループして set() メソッドを呼び出すだけです。 set() メソッドは次のとおりです。
public function set($id, $definition) { unset($this->_components[$id]); if ($definition === null) { unset($this->_definitions[$id]); return; } if (is_object($definition) || is_callable($definition, true)) { // an object, a class name, or a PHP callable $this->_definitions[$id] = $definition; } elseif (is_array($definition)) { // a configuration array if (isset($definition['class'])) { $this->_definitions[$id] = $definition; } else { throw new InvalidConfigException("The configuration for the \"$id\" component must contain a \"class\" element."); } } else { throw new InvalidConfigException("Unexpected configuration type for the \"$id\" component: " . gettype($definition)); } }
実際、コンポーネント設定はプライベート メンバー変数 $_definitions に保存されます (つまり、登録されています)。それならもうありません。 。 。
搞了半天,原来yii创建应用实例的时候只是进行组件的注册,并没有实际创建组件,那么组件实例是什么时候进行创建的?在哪里进行创建的呢?别急。从上面推导的这个过程我们知道\yii\di\ServiceLocator类是\yii\web\Application类的祖先类,所以其实yii的应用实例其实就是一个服务定位器,比如我们想访问数据库组件的时候,我们可以这样来访问:Yii::$app->db,这个Yii::$app就是yii应用实例,也就是\yii\web\Application类的实例,但是\yii\web\Application类和它的父类、祖先类都找不到db这个属性啊。哈哈,别忘了,php读取不到类属性的时候会调用魔术方法get(),所以开始查找\yii\web\Application继承关系最近的祖先类中的get()方法,最后在\yii\di\ServiceLocator类中找到了,也就是说,Yii::$app->db最终会调用\yii\di\ServiceLocator类中的get()方法:
public function get($name) { if ($this->has($name)) { return $this->get($name); } else { return parent::get($name); } }
get()方法首先调用has()方法(这个不再贴代码了)判断组件是否已注册,若已注册则调用get()方法:
public function get($id, $throwException = true) { if (isset($this->_components[$id])) { return $this->_components[$id]; } if (isset($this->_definitions[$id])) { $definition = $this->_definitions[$id]; if (is_object($definition) && !$definition instanceof Closure) { return $this->_components[$id] = $definition; } else { return $this->_components[$id] = Yii::createObject($definition); } } elseif ($throwException) { throw new InvalidConfigException("Unknown component ID: $id"); } else { return null; } }
其中私有成员变量$_components是存储已经创建的组件实例的,若发现组件已经创建过则直接返回组件示例,否则使用$_definitions中对应组件的注册信息,调用\yii\BaseYii::createObject()方法进行组件创建,这个方法最终会调用依赖注入容器\yii\di\Container的get()方法,接着就是依赖注入创建对象的过程了,关于这个过程已经在我的上一篇博文中讲解过了,可以参考一下:yii2之依赖注入与依赖注入容器。
好了,yii组件注册与创建的整个过程就是这样的。最后总结一下,其实yii创建应用实例的时候只是进行了各个组件的注册,也就是将组件的配置信息存入\yii\di\ServiceLocator类的私有成员变量$_definitions中,并没有进行实际创建,等到程序运行过程中真正需要使用到某个组件的时候才根据该组件在$_definitions中保存的注册信息使用依赖注入容器\yii\di\Container进行组件实例的创建,然后把创建的实例存入私有成员变量$_components,这样下次访问相同组件的时候就可以直接返回组件实例,而不再需要执行创建过程了。yii的这个组件注册与创建机制其实是大有裨益的,试想一下,如果在应用实例创建的时候就进行所有组件的创建,将会大大增加应用实例创建的时间,用户每次刷新页面都会进行应用实例的创建的,也就是说用户每刷新一次页面都很慢,这用户体验就很不好了,而且很多情况下有很多组件其实是没有使用到的,但是我们还是花了不少时间去创建这些组件,这是很不明智的,所以yii的做法就是:先把组件参数信息保存起来,需要使用到哪些组件再去创建相应的实例,大大节省了应用创建的时间,同时也节省了内存,这种思路是很值得我们学习的!
总结
以上がYii2におけるコンポーネントの登録・作成方法を詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。