yii2源码分析之组件实例化流程
读本篇文章,建议先看看我之前的文章php依赖注入
到此,现在我们正式开始分析yii2框架组件构造流程
我们先从yii\di\ServiceLocator(服务定位器)入手吧!!让我们先看个实例:
useyii\di\ServiceLocator;useyii\caching\FileCache;$locator=newServiceLocator;//通过一个可用于创建该组件的类名,注册"cache"(缓存)组件。$locator->set('cache','yii\caching\ApcCache');//通过一个可用于创建该组件的配置数组,注册"db"(数据库)组件。$locator->set('db',['class'=>'yii\db\Connection','dsn'=>'mysql:host=localhost;dbname=demo','username'=>'root','password'=>'',]);
我们直接打开ServiceLocator的set方法:
//这个函数很简单,就是定义一个id对应$definition,并保存到$this->_definitions数组中publicfunctionset($id,$definition){if($definition===null){unset($this->_components[$id],$this->_definitions[$id]);return;}unset($this->_components[$id]);if(is_object($definition)||is_callable($definition,true)){//anobject,aclassname,oraPHPcallable$this->_definitions[$id]=$definition;}elseif(is_array($definition)){//aconfigurationarrayif(isset($definition['class'])){$this->_definitions[$id]=$definition;}else{thrownewInvalidConfigException("Theconfigurationforthe\"$id\"componentmustcontaina\"class\"element.");}}else{thrownewInvalidConfigException("Unexpectedconfigurationtypeforthe\"$id\"component:".gettype($definition));}}
现在我们有set方法,如果我们需要指定类名的对象呢,这时候需要看看ServiceLocator的get方法:
publicfunctionget($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)&&!$definitioninstanceofClosure){return$this->_components[$id]=$definition;}else{//Yii::createObject创建对象实例(重点方法)return$this->_components[$id]=Yii::createObject($definition);}}elseif($throwException){thrownewInvalidConfigException("UnknowncomponentID:$id");}else{returnnull;}}
跟踪Yii::createObject方法
publicstaticfunctioncreateObject($type,array$params=[]){if(is_string($type)){//static::$container就是yii\di\Container对象(依赖注入容器)returnstatic::$container->get($type,$params);}elseif(is_array($type)&&isset($type['class'])){$class=$type['class'];unset($type['class']);returnstatic::$container->get($class,$params,$type);}elseif(is_callable($type,true)){returncall_user_func($type,$params);}elseif(is_array($type)){thrownewInvalidConfigException('Objectconfigurationmustbeanarraycontaininga"class"element.');}else{thrownewInvalidConfigException('Unsupportedconfigurationtype:'.gettype($type));}}
我们来看看yii2的依赖注入容器怎么实现的,打开yii\di\Container的get方法:
publicfunctionget($class,$params=[],$config=[]){//$this->_singletons保存单例对象if(isset($this->_singletons[$class])){//singletonreturn$this->_singletons[$class];}elseif(!isset($this->_definitions[$class])){//通过php反射机制实现$class的实例化return$this->build($class,$params,$config);}//代表已经使用过set方法定义过$class$definition=$this->_definitions[$class];if(is_callable($definition,true)){$params=$this->resolveDependencies($this->mergeParams($class,$params));$object=call_user_func($definition,$this,$params,$config);}elseif(is_array($definition)){$concrete=$definition['class'];unset($definition['class']);$config=array_merge($definition,$config);$params=$this->mergeParams($class,$params);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{thrownewInvalidConfigException('Unexpectedobjectdefinitiontype:'.gettype($definition));}if(array_key_exists($class,$this->_singletons)){//单例对象$this->_singletons[$class]=$object;}return$object;}
我们打开这个$this->build方法:
protectedfunctionbuild($class,$params,$config){/*$reflection为反射类$denpendencies为$class类构造函数参数,如果构造函数参数里面有依赖别的类,举例:[newInstance(id属性=该类名),newInstance(id属性=该类名)]*/list($reflection,$dependencies)=$this->getDependencies($class);//将$params的key和值加到$class构造函数参数数组列表中foreach($paramsas$index=>$param){$dependencies[$index]=$param;}//将构造函数参数中有yii\di\Instance的id属性不为空,则将继续递归调用$this->get将所依赖的类实例化$dependencies=$this->resolveDependencies($dependencies,$reflection);if(empty($config)){return$reflection->newInstanceArgs($dependencies);}if(!empty($dependencies)&&$reflection->implementsInterface('yii\base\Configurable')){//set$configasthelastparameter(existingonewillbeoverwritten)$dependencies[count($dependencies)-1]=$config;return$reflection->newInstanceArgs($dependencies);}else{$object=$reflection->newInstanceArgs($dependencies);//将$config数组赋值到实例化对象的属性上foreach($configas$name=>$value){$object->$name=$value;}return$object;}}
继续追$this->getDependencies方法:
//得到[$reflection,$dependencies];protectedfunctiongetDependencies($class){if(isset($this->_reflections[$class])){return[$this->_reflections[$class],$this->_dependencies[$class]];}$dependencies=[];$reflection=newReflectionClass($class);$constructor=$reflection->getConstructor();if($constructor!==null){foreach($constructor->getParameters()as$param){if($param->isDefaultValueAvailable()){$dependencies[]=$param->getDefaultValue();}else{$c=$param->getClass();$dependencies[]=Instance::of($c===null?null:$c->getName());}}}//缓存$reflection反射类,以及$class类构造函数参数数组$this->_reflections[$class]=$reflection;$this->_dependencies[$class]=$dependencies;return[$reflection,$dependencies];}
我们打开$this->resolveDependencies方法:
protectedfunctionresolveDependencies($dependencies,$reflection=null){foreach($dependenciesas$index=>$dependency){if($dependencyinstanceofInstance){if($dependency->id!==null){$dependencies[$index]=$this->get($dependency->id);}elseif($reflection!==null){$name=$reflection->getConstructor()->getParameters()[$index]->getName();$class=$reflection->getName();thrownewInvalidConfigException("Missingrequiredparameter\"$name\"wheninstantiating\"$class\".");}}}return$dependencies;}
最后来个yii\di\Instance类的具体内容(跟我之前那个类代码基本一样,多了个ensure方法):
namespaceyii\di;useYii;useyii\base\InvalidConfigException;classInstance{public$id;protectedfunction__construct($id){$this->id=$id;}publicstaticfunctionof($id){returnnewstatic($id);}/****```php*useyii\db\Connection;**//returnsYii::$app->db*$db=Instance::ensure('db',Connection::className());*//returnsaninstanceofConnectionusingthegivenconfiguration*$db=Instance::ensure(['dsn'=>'sqlite:path/to/my.db'],Connection::className());*```*/publicstaticfunctionensure($reference,$type=null,$container=null){if(is_array($reference)){$class=isset($reference['class'])?$reference['class']:$type;if(!$containerinstanceofContainer){$container=Yii::$container;}unset($reference['class']);return$container->get($class,[],$reference);}elseif(empty($reference)){thrownewInvalidConfigException('Therequiredcomponentisnotspecified.');}if(is_string($reference)){$reference=newstatic($reference);}elseif($type===null||$referenceinstanceof$type){return$reference;}if($referenceinstanceofself){$component=$reference->get($container);if($type===null||$componentinstanceof$type){return$component;}else{thrownewInvalidConfigException('"'.$reference->id.'"referstoa'.get_class($component)."component.$typeisexpected.");}}$valueType=is_object($reference)?get_class($reference):gettype($reference);thrownewInvalidConfigException("Invaliddatatype:$valueType.$typeisexpected.");}publicfunctionget($container=null){if($container){return$container->get($this->id);}if(Yii::$app&&Yii::$app->has($this->id)){returnYii::$app->get($this->id);}else{returnYii::$container->get($this->id);}}}
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。