yii2源码分析之执行基本流程
用yii2框架用了将近2年,一直都没有去看过它底层源码, 马上快不用了,最近对其源码研究一番,哈哈
废话少说,上代码,
入口文件是web/index.php
<?phpdefined('YII_DEBUG')ordefine('YII_DEBUG',true);defined('YII_ENV')ordefine('YII_ENV','dev');//这行我在composerautoload流程已经分析过require__DIR__.'/../vendor/autoload.php';//见解释1-1require__DIR__.'/../vendor/yiisoft/yii2/Yii.php';//配置文件$config=require__DIR__.'/../config/web.php';//最关键的一点,见解释1-2(newyii\web\Application($config))->run();
解释1-1
直接上Yii.php文件源码
<?phprequire(__DIR__.'/BaseYii.php');classYiiextends\yii\BaseYii{}//实际上调用的是BaseYii的autoload方法,自动加载yii的类spl_autoload_register(['Yii','autoload'],true,true);//yii类名和yii类名所在文件的映射数组Yii::$classMap=require(__DIR__.'/classes.php');//依赖注入容器,这个后续文章再分析,先知道有这么一个东东Yii::$container=newyii\di\Container();
解释1-2
我们最关键的点来了分析application启动流程
首先看看Application构造函数
首先进入yii\web\Application类,发现没有构造方法,于是跟踪它的层级关系,列出来:
yii\web\Application ->\yii\base\Application ->\yii\base\Module ->\yii\di\ServiceLocator ->\yii\base\Component
->\yii\base\BaseObject -> \yii\base\Configurable(接口interface)
首先进入yii\base\Application找到__construct方法:
publicfunction__construct($config=[]){//保存当前启动的application实例Yii::$app=$this;//将Yii::$app->loadedModules[实例类名]=当前实例;$this->setInstance($this);$this->state=self::STATE_BEGIN;//见解释1-2-1$this->preInit($config);//见解释1-2-2$this->registerErrorHandler($config);//见解释1-2-3Component::__construct($config);}
解释1-2-1:
/*该函数作用是将配置数组进一步合并完善数组中的key$config即为入口文件包含到的config/web.php返回的数组,举例如下:$config=['id'=>'basic','basePath'=>dirname(__DIR__),'bootstrap'=>['log'],'aliases'=>['@bower'=>'@vendor/bower-asset','@npm'=>'@vendor/npm-asset',],'components'=>['request'=>[//!!!insertasecretkeyinthefollowing(ifitisempty)-thisisrequiredbycookievalidation'cookieValidationKey'=>'',],'cache'=>['class'=>'yii\caching\FileCache',],'user'=>['identityClass'=>'app\models\User','enableAutoLogin'=>true,],'errorHandler'=>['errorAction'=>'site/error',],'mailer'=>['class'=>'yii\swiftmailer\Mailer','useFileTransport'=>true,],'log'=>['traceLevel'=>YII_DEBUG?3:0,'targets'=>[['class'=>'yii\log\FileTarget','levels'=>['error','warning'],],],],'db'=>$db,],'params'=>$params,];*/publicfunctionpreInit(&$config){if(!isset($config['id'])){thrownewInvalidConfigException('The"id"configurationfortheApplicationisrequired.');}if(isset($config['basePath'])){$this->setBasePath($config['basePath']);unset($config['basePath']);}else{thrownewInvalidConfigException('The"basePath"configurationfortheApplicationisrequired.');}if(isset($config['vendorPath'])){$this->setVendorPath($config['vendorPath']);unset($config['vendorPath']);}else{$this->getVendorPath();}if(isset($config['runtimePath'])){$this->setRuntimePath($config['runtimePath']);unset($config['runtimePath']);}else{//set"@runtime"$this->getRuntimePath();}//设置时区if(isset($config['timeZone'])){$this->setTimeZone($config['timeZone']);unset($config['timeZone']);}elseif(!ini_get('date.timezone')){$this->setTimeZone('UTC');}if(isset($config['container'])){$this->setContainer($config['container']);unset($config['container']);}/*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'],];合并配置文件数组的componentskey内容*/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'];}}}
解释1-2-2:
protectedfunctionregisterErrorHandler(&$config){//YII_ENABLE_ERROR_HANDLER可以在文件中配,默认为trueif(YII_ENABLE_ERROR_HANDLER){if(!isset($config['components']['errorHandler']['class'])){echo"Error:noerrorHandlercomponentisconfigured.\n";exit(1);}/*晒个默认配置'errorHandler'=>['errorAction'=>'site/error',],$this->set方法是引自\yii\di\ServiceLocator的set方法,注册组件$this->_definitions['erroHandler']=['errorAction'=>'site/error','class'=>'yii\web\ErrorHandler'];*/$this->set('errorHandler',$config['components']['errorHandler']);unset($config['components']['errorHandler']);//这个方法会实例化errorHandler的class,实例化这步实际上用到依赖注入,之前我已经讲过一点,以后写个yii2创建对象流程//并将实例化的对象保存到$this->__components['errorHandler']$this->getErrorHandler()->register();}}
解释1-2-3:
//实际调用的是yii\base\BaseObject类的构造方法publicfunction__construct($config=[]){if(!empty($config)){//将$config数组中的每个key都赋值$this->本地化变量Yii::configure($this,$config);}$this->init();}
很明显追踪$this->init()方法,后面追踪到yii\base\Application的init方法。
publicfunctioninit(){$this->state=self::STATE_INIT;$this->bootstrap();}
再看看bootstrap方法
先看看yii\web\Application的bootstrap方法
protectedfunctionbootstrap(){//获得request对象实例$request=$this->getRequest();Yii::setAlias('@webroot',dirname($request->getScriptFile()));Yii::setAlias('@web',$request->getBaseUrl());parent::bootstrap();}
再看看yii\base\Application的bootstrap方法
protectedfunctionbootstrap(){if($this->extensions===null){$file=Yii::getAlias('@vendor/yiisoft/extensions.php');$this->extensions=is_file($file)?include$file:[];}foreach($this->extensionsas$extension){if(!empty($extension['alias'])){foreach($extension['alias']as$name=>$path){Yii::setAlias($name,$path);}}if(isset($extension['bootstrap'])){$component=Yii::createObject($extension['bootstrap']);if($componentinstanceofBootstrapInterface){Yii::debug('Bootstrapwith'.get_class($component).'::bootstrap()',__METHOD__);$component->bootstrap($this);}else{Yii::debug('Bootstrapwith'.get_class($component),__METHOD__);}}}//已配置需要初始化的组件初始化foreach($this->bootstrapas$mixed){$component=null;if($mixedinstanceof\Closure){Yii::debug('BootstrapwithClosure',__METHOD__);if(!$component=call_user_func($mixed,$this)){continue;}}elseif(is_string($mixed)){if($this->has($mixed)){$component=$this->get($mixed);}elseif($this->hasModule($mixed)){$component=$this->getModule($mixed);}elseif(strpos($mixed,'\\')===false){thrownewInvalidConfigException("UnknownbootstrappingcomponentID:$mixed");}}if(!isset($component)){$component=Yii::createObject($mixed);}if($componentinstanceofBootstrapInterface){Yii::debug('Bootstrapwith'.get_class($component).'::bootstrap()',__METHOD__);$component->bootstrap($this);}else{Yii::debug('Bootstrapwith'.get_class($component),__METHOD__);}}}
到此new Application($config)这一步分析完毕
再来看看$app->run()做了什么
先打开yii\base\Application的run方法
publicfunctionrun(){try{$this->state=self::STATE_BEFORE_REQUEST;//这里可以绑定自定义事件,类似钩子$this->trigger(self::EVENT_BEFORE_REQUEST);$this->state=self::STATE_HANDLING_REQUEST;//最重要的一点见解释2-1$response=$this->handleRequest($this->getRequest());$this->state=self::STATE_AFTER_REQUEST;$this->trigger(self::EVENT_AFTER_REQUEST);$this->state=self::STATE_SENDING_RESPONSE;//见解释2-2$response->send();$this->state=self::STATE_END;return$response->exitStatus;}catch(ExitException$e){$this->end($e->statusCode,isset($response)?$response:null);return$e->statusCode;}}
解释2-1:
打开yii\web\Application的handleRequest
//$request为yii\web\Request类的实例publicfunctionhandleRequest($request){if(empty($this->catchAll)){try{list($route,$params)=$request->resolve();}catch(UrlNormalizerRedirectException$e){$url=$e->url;if(is_array($url)){if(isset($url[0])){//ensuretherouteisabsolute$url[0]='/'.ltrim($url[0],'/');}$url+=$request->getQueryParams();}return$this->getResponse()->redirect(Url::to($url,$e->scheme),$e->statusCode);}}else{$route=$this->catchAll[0];$params=$this->catchAll;unset($params[0]);}try{Yii::debug("Routerequested:'$route'",__METHOD__);$this->requestedRoute=$route;/*例如访问url为http://domain/web/index.php?r=post/index&id=3$route为路由url字符串,得到post/index$params为QueryString数组,得到['id'=>3,'r'=>'post/index']$result的值为对应conroller执行对应action返回的值或者对象*/$result=$this->runAction($route,$params);if($resultinstanceofResponse){return$result;}//构造一个Response对象$response=$this->getResponse();if($result!==null){$response->data=$result;}return$response;}catch(InvalidRouteException$e){thrownewNotFoundHttpException(Yii::t('yii','Pagenotfound.'),$e->getCode(),$e);}}
我们进入$this->runAction看看
publicfunctionrunAction($route,$params=[]){//得到($controller实例对象和action名称的字符串)$parts=$this->createController($route);if(is_array($parts)){/*@var$controllerController*/list($controller,$actionID)=$parts;$oldController=Yii::$app->controller;Yii::$app->controller=$controller;//执行controller的对应的actionID方法,该方法返回的内容赋值给$result$result=$controller->runAction($actionID,$params);if($oldController!==null){Yii::$app->controller=$oldController;}return$result;}$id=$this->getUniqueId();thrownewInvalidRouteException('Unabletoresolvetherequest"'.($id===''?$route:$id.'/'.$route).'".');}
解释2-2:
打开yii\web\Response的send方法
publicfunctionsend(){if($this->isSent){return;}$this->trigger(self::EVENT_BEFORE_SEND);//取得$response对象的format再获得该format对象的实例执行format方法(就是header设置Content-Type)//见2-2-1$this->prepare();$this->trigger(self::EVENT_AFTER_PREPARE);//见2-2-2$this->sendHeaders();//见2-2-3$this->sendContent();$this->trigger(self::EVENT_AFTER_SEND);$this->isSent=true;}
解释2-2-1:
protectedfunctionprepare(){if($this->stream!==null){return;}if(isset($this->formatters[$this->format])){$formatter=$this->formatters[$this->format];if(!is_object($formatter)){$this->formatters[$this->format]=$formatter=Yii::createObject($formatter);}if($formatterinstanceofResponseFormatterInterface){$formatter->format($this);}else{thrownewInvalidConfigException("The'{$this->format}'responseformatterisinvalid.ItmustimplementtheResponseFormatterInterface.");}}elseif($this->format===self::FORMAT_RAW){if($this->data!==null){$this->content=$this->data;}}else{thrownewInvalidConfigException("Unsupportedresponseformat:{$this->format}");}if(is_array($this->content)){thrownewInvalidArgumentException('Responsecontentmustnotbeanarray.');}elseif(is_object($this->content)){if(method_exists($this->content,'__toString')){$this->content=$this->content->__toString();}else{thrownewInvalidArgumentException('Responsecontentmustbeastringoranobjectimplementing__toString().');}}}
解释2-2-2:
protectedfunctionsendHeaders(){if(headers_sent($file,$line)){thrownewHeadersAlreadySentException($file,$line);}if($this->_headers){$headers=$this->getHeaders();foreach($headersas$name=>$values){$name=str_replace('','-',ucwords(str_replace('-','',$name)));//setreplaceforfirstoccurrenceofheaderbutfalseafterwardstoallowmultiple$replace=true;foreach($valuesas$value){header("$name:$value",$replace);$replace=false;}}}$statusCode=$this->getStatusCode();header("HTTP/{$this->version}{$statusCode}{$this->statusText}");$this->sendCookies();}
这里补充下sendCookies方法:
protectedfunctionsendCookies(){if($this->_cookies===null){return;}$request=Yii::$app->getRequest();if($request->enableCookieValidation){if($request->cookieValidationKey==''){thrownewInvalidConfigException(get_class($request).'::cookieValidationKeymustbeconfiguredwithasecretkey.');}$validationKey=$request->cookieValidationKey;}foreach($this->getCookies()as$cookie){$value=$cookie->value;if($cookie->expire!=1&&isset($validationKey)){$value=Yii::$app->getSecurity()->hashData(serialize([$cookie->name,$value]),$validationKey);}setcookie($cookie->name,$value,$cookie->expire,$cookie->path,$cookie->domain,$cookie->secure,$cookie->httpOnly);}}
解释2-2-3:
protectedfunctionsendContent(){if($this->stream===null){echo$this->content;return;}set_time_limit(0);//Resettimelimitforbigfiles$chunkSize=8*1024*1024;//8MBperchunkif(is_array($this->stream)){list($handle,$begin,$end)=$this->stream;fseek($handle,$begin);while(!feof($handle)&&($pos=ftell($handle))<=$end){if($pos+$chunkSize>$end){$chunkSize=$end-$pos+1;}echofread($handle,$chunkSize);flush();//Freeupmemory.OtherwiselargefileswilltriggerPHP'smemorylimit.}fclose($handle);}else{while(!feof($this->stream)){echofread($this->stream,$chunkSize);flush();}fclose($this->stream);}}
至此源码整个流程分析基本完毕,有些地方可能分析不够详细,后续再详细补充。
最后附加下官网文档的部分内容帮助大家理解
以下图表展示了一个应用如何处理请求:
用户向入口脚本web/index.php
发起请求。
入口脚本加载应用配置并创建一个应用实例去处理请求。
应用通过请求组件解析请求的路由。
应用创建一个控制器实例去处理请求。
控制器创建一个动作实例并针对操作执行过滤器。
如果任何一个过滤器返回失败,则动作取消。
如果所有过滤器都通过,动作将被执行。
动作会加载一个数据模型,或许是来自数据库。
动作会渲染一个视图,把数据模型提供给它。
渲染结果返回给响应组件。
响应组件发送渲染结果给用户浏览器。
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。