YII 的源码分析(二)
上一篇简单分析了一下yii的流程,从创建一个应用,到屏幕上输出结果。这一次我来一个稍复杂一点的,重点在输出上,不再是简单的一行"hello world",而是要经过view(视图)层的处理。
依然是demos目录,这次我们选择hangman,一个简单的猜字游戏。老规则,还是从入口处开始看。
index.php:
<span style="color: #000000;">php</span><span style="color: #008000;">//</span><span style="color: #008000;"> change the following paths if necessary</span><span style="color: #800080;">$yii</span>=<span style="color: #008080;">dirname</span>(<span style="color: #ff00ff;">__FILE__</span>).'/../../framework/yii.php'<span style="color: #000000;">;</span><span style="color: #800080;">$config</span>=<span style="color: #008080;">dirname</span>(<span style="color: #ff00ff;">__FILE__</span>).'/protected/config/main.php'<span style="color: #000000;">;</span><span style="color: #008000;">//</span><span style="color: #008000;"> remove the following line when in production mode// defined('YII_DEBUG') or define('YII_DEBUG',true);</span><span style="color: #0000ff;">require_once</span>(<span style="color: #800080;">$yii</span><span style="color: #000000;">);Yii</span>::createWebApplication(<span style="color: #800080;">$config</span>)->run();
和helloworld应用相比,这次多了main.php,打开main看下源码:
<span style="color: #000000;">php</span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">array</span><span style="color: #000000;">( </span>'name'=>'Hangman Game', 'defaultController'=>'game', 'components'=><span style="color: #0000ff;">array</span><span style="color: #000000;">( </span>'urlManager'=><span style="color: #0000ff;">array</span><span style="color: #000000;">( </span>'urlFormat'=>'path', 'rules'=><span style="color: #0000ff;">array</span><span style="color: #000000;">( </span>'game/guess/<g:>'=>'game/guess',<span style="color: #000000;"> )</span>,<span style="color: #000000;"> )</span>,<span style="color: #000000;"> )</span>,<span style="color: #000000;">);</span></g:>
在我们以后的实际项目中,也是经常要用到配置文件的,所以我觉得有必要了解一下yii的配置文件--main.php
'name'=>'这里通常是定义网站的标题',也就是我们打开index.php时,在网页上显示的标题。
'defaultController'=>'这里是默认的控制器',也就是我们的index.php后面没有指定控制器时系统采用的控制器,如果我们这里没有指出来,默认就是site
'components'=>'这里是组件的参数,用多维数组进行配置。' 具体的参数可以查看yii手册。
Yii::createWebApplication($config)->run(); 上一次我们已经详细分析过它了,这里再简单的走一遍:
CWebApplication.php -> CApplication.php -> __construct($config) :
<span style="color: #800080;">$this</span>-><span style="color: #000000;">preinit(); </span><span style="color: #800080;">$this</span>-><span style="color: #000000;">initSystemHandlers(); </span><span style="color: #800080;">$this</span>-><span style="color: #000000;">registerCoreComponents(); </span><span style="color: #800080;">$this</span>->configure(<span style="color: #800080;">$config</span><span style="color: #000000;">); </span><span style="color: #800080;">$this</span>->attachBehaviors(<span style="color: #800080;">$this</span>-><span style="color: #000000;">behaviors); </span><span style="color: #800080;">$this</span>-><span style="color: #000000;">preloadComponents(); </span><span style="color: #800080;">$this</span>->init();
上次我们没有配置过程,所以$this->configure($config)什么也没有做,但是这次有配置参数,所以我们进去看看yii做了哪些操作:
CApplication自己没有实现configure方法,是继承于CModule.php的:
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> configure(<span style="color: #800080;">$config</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">if</span>(<span style="color: #008080;">is_array</span>(<span style="color: #800080;">$config</span><span style="color: #000000;">)) { </span><span style="color: #0000ff;">foreach</span>(<span style="color: #800080;">$config</span> <span style="color: #0000ff;">as</span> <span style="color: #800080;">$key</span>=><span style="color: #800080;">$value</span><span style="color: #000000;">) </span><span style="color: #800080;">$this</span>-><span style="color: #800080;">$key</span>=<span style="color: #800080;">$value</span><span style="color: #000000;">; } }</span>
代码非常简单,就是把配置参数的键做为类的属性名,value做为类的属性值进行了扩展。完成这一过程就运行CApplication 上的run方法了。
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span><span style="color: #000000;"> run() { </span><span style="color: #0000ff;">if</span>(<span style="color: #800080;">$this</span>->hasEventHandler('onBeginRequest'<span style="color: #000000;">)) </span><span style="color: #800080;">$this</span>->onBeginRequest(<span style="color: #0000ff;">new</span> CEvent(<span style="color: #800080;">$this</span><span style="color: #000000;">)); </span><span style="color: #008080;">register_shutdown_function</span>(<span style="color: #0000ff;">array</span>(<span style="color: #800080;">$this</span>,'end'),0,<span style="color: #0000ff;">false</span><span style="color: #000000;">); </span><span style="color: #800080;">$this</span>-><span style="color: #000000;">processRequest(); </span><span style="color: #0000ff;">if</span>(<span style="color: #800080;">$this</span>->hasEventHandler('onEndRequest'<span style="color: #000000;">)) </span><span style="color: #800080;">$this</span>->onEndRequest(<span style="color: #0000ff;">new</span> CEvent(<span style="color: #800080;">$this</span><span style="color: #000000;">)); }</span>
我们前面说过,这里只要关注 $this->processRequest(); 就可以了。运行的结果就是执行$this->runController('');
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> runController(<span style="color: #800080;">$route</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">if</span>((<span style="color: #800080;">$ca</span>=<span style="color: #800080;">$this</span>->createController(<span style="color: #800080;">$route</span>))!==<span style="color: #0000ff;">null</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">list</span>(<span style="color: #800080;">$controller</span>,<span style="color: #800080;">$actionID</span>)=<span style="color: #800080;">$ca</span><span style="color: #000000;">; </span><span style="color: #800080;">$oldController</span>=<span style="color: #800080;">$this</span>-><span style="color: #000000;">_controller; </span><span style="color: #800080;">$this</span>->_controller=<span style="color: #800080;">$controller</span><span style="color: #000000;">; </span><span style="color: #800080;">$controller</span>-><span style="color: #000000;">init(); </span><span style="color: #800080;">$controller</span>->run(<span style="color: #800080;">$actionID</span><span style="color: #000000;">); </span><span style="color: #800080;">$this</span>->_controller=<span style="color: #800080;">$oldController</span><span style="color: #000000;">; } </span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span> CHttpException(404,Yii::t('yii','Unable to resolve the request "{route}".', <span style="color: #0000ff;">array</span>('{route}'=><span style="color: #800080;">$route</span>===''?<span style="color: #800080;">$this</span>->defaultController:<span style="color: #800080;">$route</span><span style="color: #000000;">))); }</span>
由于url是index.php,后面没有任何参数,所以都是走的默认控制器,也就是我们在main.php中设定的game. 所以$controller 就等于 controllers/gameController.php, 通过上次的源码分析我们可以知道,在gameController.php中没有init方法时,都是走的父类中定义的默认方法(实际上是一个空方法),
$controller->run($actionID<span>); == gameController</span>->run(''); gameController上没有实现run方法,于是又是去父类中找run
从class GameController extends CController 可以看出,父类是CController , 找到相应的run方法:
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> run(<span style="color: #800080;">$actionID</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">if</span>((<span style="color: #800080;">$action</span>=<span style="color: #800080;">$this</span>->createAction(<span style="color: #800080;">$actionID</span>))!==<span style="color: #0000ff;">null</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">if</span>((<span style="color: #800080;">$parent</span>=<span style="color: #800080;">$this</span>->getModule())===<span style="color: #0000ff;">null</span><span style="color: #000000;">) </span><span style="color: #800080;">$parent</span>=Yii::<span style="color: #000000;">app(); </span><span style="color: #0000ff;">if</span>(<span style="color: #800080;">$parent</span>->beforeControllerAction(<span style="color: #800080;">$this</span>,<span style="color: #800080;">$action</span><span style="color: #000000;">)) { </span><span style="color: #800080;">$this</span>->runActionWithFilters(<span style="color: #800080;">$action</span>,<span style="color: #800080;">$this</span>-><span style="color: #000000;">filters()); </span><span style="color: #800080;">$parent</span>->afterControllerAction(<span style="color: #800080;">$this</span>,<span style="color: #800080;">$action</span><span style="color: #000000;">); } } </span><span style="color: #0000ff;">else</span> <span style="color: #800080;">$this</span>->missingAction(<span style="color: #800080;">$actionID</span><span style="color: #000000;">); }</span>
前面已经分析过了,没有指定时,都是默认参数。那么此时的$actionID为空,actionID就是gameController中定义的默认动作:public $defaultAction='play';
runActionWithFilters ---> runAction --> $action->runWithParams<br><br>这里的$action 需要从CAction -> CInlineAction中去找<br><br>
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> runWithParams(<span style="color: #800080;">$params</span><span style="color: #000000;">) { </span><span style="color: #800080;">$methodName</span>='action'.<span style="color: #800080;">$this</span>-><span style="color: #000000;">getId(); </span><span style="color: #800080;">$controller</span>=<span style="color: #800080;">$this</span>-><span style="color: #000000;">getController(); </span><span style="color: #800080;">$method</span>=<span style="color: #0000ff;">new</span> ReflectionMethod(<span style="color: #800080;">$controller</span>, <span style="color: #800080;">$methodName</span><span style="color: #000000;">); </span><span style="color: #0000ff;">if</span>(<span style="color: #800080;">$method</span>->getNumberOfParameters()>0<span style="color: #000000;">) </span><span style="color: #0000ff;">return</span> <span style="color: #800080;">$this</span>->runWithParamsInternal(<span style="color: #800080;">$controller</span>, <span style="color: #800080;">$method</span>, <span style="color: #800080;">$params</span><span style="color: #000000;">); </span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">return</span> <span style="color: #800080;">$controller</span>-><span style="color: #800080;">$methodName</span><span style="color: #000000;">(); }</span>
走了这么多过程,和hello world的流程是差不多的。据上次的分析可以知道,这里执行了
$controller->$methodName<span>(); 也就是GameController->actionPlay()<br><br>到此,我们本节的重点才真正开始:<br></span>
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span><span style="color: #000000;"> actionPlay() { </span><span style="color: #0000ff;">static</span> <span style="color: #800080;">$levels</span>=<span style="color: #0000ff;">array</span><span style="color: #000000;">( </span>'10'=>'Easy game; you are allowed 10 misses.', '5'=>'Medium game; you are allowed 5 misses.', '3'=>'Hard game; you are allowed 3 misses.',<span style="color: #000000;"> ); </span><span style="color: #008000;">//</span><span style="color: #008000;"> if a difficulty level is correctly chosen</span> <span style="color: #0000ff;">if</span>(<span style="color: #0000ff;">isset</span>(<span style="color: #800080;">$_POST</span>['level']) && <span style="color: #0000ff;">isset</span>(<span style="color: #800080;">$levels</span>[<span style="color: #800080;">$_POST</span>['level'<span style="color: #000000;">]])) { </span><span style="color: #800080;">$this</span>->word=<span style="color: #800080;">$this</span>-><span style="color: #000000;">generateWord(); </span><span style="color: #800080;">$this</span>->guessWord=<span style="color: #008080;">str_repeat</span>('_',<span style="color: #008080;">strlen</span>(<span style="color: #800080;">$this</span>-><span style="color: #000000;">word)); </span><span style="color: #800080;">$this</span>->level=<span style="color: #800080;">$_POST</span>['level'<span style="color: #000000;">]; </span><span style="color: #800080;">$this</span>->misses=0<span style="color: #000000;">; </span><span style="color: #800080;">$this</span>->setPageState('guessed',<span style="color: #0000ff;">null</span><span style="color: #000000;">); </span><span style="color: #008000;">//</span><span style="color: #008000;"> show the guess page</span> <span style="color: #800080;">$this</span>->render('guess'<span style="color: #000000;">); } </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> { </span><span style="color: #800080;">$params</span>=<span style="color: #0000ff;">array</span><span style="color: #000000;">( </span>'levels'=><span style="color: #800080;">$levels</span>, <span style="color: #008000;">//</span><span style="color: #008000;"> if this is a POST request, it means the level is not chosen</span> 'error'=>Yii::app()->request->isPostRequest,<span style="color: #000000;"> ); </span><span style="color: #008000;">//</span><span style="color: #008000;"> show the difficulty level page</span> <span style="color: #800080;">$this</span>->render('play',<span style="color: #800080;">$params</span><span style="color: #000000;">); } }</span>
<span>显然走的是else的逻辑,重点请看</span> $this->render('play',$params); 这个render方法这么面熟,很多框架中都有类似的方法,比如discuz,smarty,CI 等等. 纵观yii框架,rnder 在它整个MVC模式中,是V得以实现的重要骨干。所以有必要把它翻个底朝天。<br>在CController.php中有这个方法:
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">function</span> render(<span style="color: #800080;">$view</span>,<span style="color: #800080;">$data</span>=<span style="color: #0000ff;">null</span>,<span style="color: #800080;">$return</span>=<span style="color: #0000ff;">false</span><span style="color: #000000;">) { </span><span style="color: #0000ff;">if</span>(<span style="color: #800080;">$this</span>->beforeRender(<span style="color: #800080;">$view</span><span style="color: #000000;">)) { </span><span style="color: #800080;">$output</span>=<span style="color: #800080;">$this</span>->renderPartial(<span style="color: #800080;">$view</span>,<span style="color: #800080;">$data</span>,<span style="color: #0000ff;">true</span><span style="color: #000000;">); </span><span style="color: #0000ff;">if</span>((<span style="color: #800080;">$layoutFile</span>=<span style="color: #800080;">$this</span>->getLayoutFile(<span style="color: #800080;">$this</span>->layout))!==<span style="color: #0000ff;">false</span><span style="color: #000000;">) </span><span style="color: #800080;">$output</span>=<span style="color: #800080;">$this</span>->renderFile(<span style="color: #800080;">$layoutFile</span>,<span style="color: #0000ff;">array</span>('content'=><span style="color: #800080;">$output</span>),<span style="color: #0000ff;">true</span><span style="color: #000000;">); </span><span style="color: #800080;">$this</span>->afterRender(<span style="color: #800080;">$view</span>,<span style="color: #800080;">$output</span><span style="color: #000000;">); </span><span style="color: #800080;">$output</span>=<span style="color: #800080;">$this</span>->processOutput(<span style="color: #800080;">$output</span><span style="color: #000000;">); </span><span style="color: #0000ff;">if</span>(<span style="color: #800080;">$return</span><span style="color: #000000;">) </span><span style="color: #0000ff;">return</span> <span style="color: #800080;">$output</span><span style="color: #000000;">; </span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">echo</span> <span style="color: #800080;">$output</span><span style="color: #000000;">; } }</span>
当我们echo $output=$this->renderPartial($view,$data,true);的时候,就发现,此时的$output已经就拿到我们最终的结果了。它对应的文件是views/game/play.php
也就是我们在index.php上最终看到的内容了。由于本次渲染比较简单,所以程序经过的流程也较少,但是从源码中可以看到,里边进行了许多的处理,比如主题什么的。本次就先分析到这。晚安!
<br><br><br>