오늘은 원래 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();
항목 파일에 여러 구성이 도입된 것을 볼 수 있습니다. files 및 all 구성 파일의 내용을 $config 구성 배열에 병합한 다음 이 구성 배열을 매개변수로 사용하여 애플리케이션 인스턴스를 생성합니다. 이 구성 배열을 인쇄해 보면 "comminents" 아래 첨자에 해당하는 요소에 yii 구성 요소의 매개변수 정보가 포함되어 있음을 알 수 있습니다(여기서는 스크린샷의 일부만 촬영됨).
정보 이러한 구성 요소는 여러 구성 파일에 구성되어 있으며 Yii 구성 요소는 이러한 매개 변수 정보를 사용하여 등록되고 생성됩니다.
다음으로 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 그런데 여기에 prelude 초기화 메소드 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 클래스의 정적 메소드인 구성()을 호출합니다:
public static function configure($object, $properties) { foreach ($properties as $name => $value) { $object->$name = $value; } return $object; }
이 메소드는 루프 항목 파일의 $config 배열입니다 (new yiiwebApplication($config))->run();
(이 배열의 구조는 이 문서의 첫 번째 스크린샷을 참조하세요) ), 배열을 사용하여 키 이름은 객체 속성 이름으로 사용되며 해당 키 값은 할당 작업의 객체 속성 값으로 사용됩니다. 따라서 구성 요소 구성 매개 변수를 반복하면 다음과 같습니다. $object->comComponents = $value($value는 모든 구성 요소의 구성 배열입니다.) 즉, $object의 구성 요소 속성에 값을 할당한 다음 this $object 어떤 클래스의 객체인가요? 초기 호출의 소스를 되돌아보면 실제로 항목 파일에서 인스턴스화되어야 하는 것은 yiiwebApplication 클래스의 개체입니다. 하지만 이 클래스나 그 조상 클래스에는 멤버 변수 구성 요소가 없으므로 걱정하지 마세요. yiiwebApplication 클래스의 상속 관계를 따라가면 yiiwebApplication을 찾을 수 있습니다. 클래스도 결국 상속됩니다. yiibaseObject 클래스는 속성을 지원하므로 yiiwebApplication 클래스도 속성을 지원합니다(속성에 대해서는 내 다른 블로그 게시물: yii2의 속성을 참조하세요). 할당 작업이 구성 요소 멤버 변수를 찾을 수 없으면 setComponents()는 다음과 같습니다. 호출되어 이 메소드의 위치를 찾은 후 마침내 조상 클래스 yiidiServiceLocator에서 setComponents() 메소드를 찾았습니다. 예, 애플리케이션 인스턴스의 구성 요소 속성에 값을 할당하면 실제로 이 메소드가 호출됩니다!
좋아, 이제 setComponents() 메서드가 수행하는 작업을 살펴보겠습니다.
public function setComponents($components) { foreach ($components as $id => $component) { $this->set($id, $component); } }
실제로는 매우 간단합니다. 각 구성 요소의 구성 배열을 반복하고 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!