做为源码分析的首秀,我就挑了yii(读作歪依依而不是歪爱爱);它的赞美之词我就不多说了,直接入正题。先准备材料,建议直从官网下载yii的源码包(1.1.15)最新版本。
在demos里边有一个最简单的应用—helloworld.就是用yii框架输出一句话:”hello world”;
我就从它下手,分析框架执行一个最小流程要经过哪些组件,浅析它的运行过程。
首先从单一入口文件开始阅读。(源码一般都是从调用处开始分析)
Index.php->
// include Yii bootstrap file
//引入启动文件
require_once(dirname(__FILE__).'/../../framework/yii.php');
yii.php ->
//YiiBase is a helper class serving common framework functionalities.
//YiiBase是一个助手类,它服务于整个框架。 这里定义了许多重要的常量
require(dirname(__FILE__).'/YiiBase.php');
//注册自动加载类
spl_autoload_register(array('YiiBase','autoload'));
//导入接口类
require(YII_PATH.'/base/interfaces.php');
//启动应用
Yii::createWebApplication()->run();
代码到这里似乎就终结了,页面的内容也程现出来,可是框架到底做了些什么,我们却一无所知。所以我们需要把这一步进行分解,把里边的细节暴露出来。
Yii::createWebApplication()->run() 一共可以分成三部分
Yii 这个东西是什么?
从yii.php 可以找到这样一行代码class Yii extends YiiBase
说明它就是YiiBase的继承类,而且作者的扩展是留空的,所以Yii就是YiiBase的一个引用而已。所以createWebApplication这个静态方法也得去YiiBase中查找了。
在YiiBase.php中,很容易就发现了这个方法:
public static function createWebApplication($config=null)
{
return self::createApplication('CWebApplication',$config);
}
它又把任务传递给了createApplication:
public static function createApplication($class,$config=null)
{
return new $class($config);
}
结合起来看,createWebApplication () 就是return new CWebApplication($config);
这个CWebApplication类又在哪呢?它又是怎么引入的呢?
带着一系列的问题,我又回到了YiiBase.php
那里边定义了一个很长的数组,你可以找到:
'CWebApplication' => '/web/CWebApplication.php',这就是自动加载类中的一员
关于它是如何实现自动加载的,可以查看spl_autoload_register的相关文档,此处就节外生枝了.
我们继续往CWebApplication这个里边深挖。打开/web/CWebApplication.php这个文件。
前面提到return new CWebApplication($config);根据我的经验,用了new ,通常会有一个构造函数的,但我却没有找到它的构造函数,肯定是在它的父类中,于是我往上找,class CWebApplication extends CApplication 果然被我发现了,这就跟挖泥鳅一样的,得顺着线索一点点的找,要有耐心。
CApplication 中果然有构造函数,代码如下:
<span><span>public</span> <span>function</span> __construct(<span>$config</span>=<span>null</span><span>) { Yii</span>::setApplication(<span>$this</span><span>); </span><span>//</span><span> set basePath at early as possible to avoid trouble</span> <span>if</span>(<span>is_string</span>(<span>$config</span><span>)) </span><span>$config</span>=<span>require</span>(<span>$config</span><span>); </span><span>if</span>(<span>isset</span>(<span>$config</span>['basePath'<span>])) { </span><span>$this</span>->setBasePath(<span>$config</span>['basePath'<span>]); </span><span>unset</span>(<span>$config</span>['basePath'<span>]); } </span><span>else</span> <span>$this</span>->setBasePath('protected'<span>); Yii</span>::setPathOfAlias('application',<span>$this</span>-><span>getBasePath()); Yii</span>::setPathOfAlias('webroot',<span>dirname</span>(<span>$_SERVER</span>['SCRIPT_FILENAME'<span>])); </span><span>if</span>(<span>isset</span>(<span>$config</span>['extensionPath'<span>])) { </span><span>$this</span>->setExtensionPath(<span>$config</span>['extensionPath'<span>]); </span><span>unset</span>(<span>$config</span>['extensionPath'<span>]); } </span><span>else</span><span> Yii</span>::setPathOfAlias('ext',<span>$this</span>->getBasePath().DIRECTORY_SEPARATOR.'extensions'<span>); </span><span>if</span>(<span>isset</span>(<span>$config</span>['aliases'<span>])) { </span><span>$this</span>->setAliases(<span>$config</span>['aliases'<span>]); </span><span>unset</span>(<span>$config</span>['aliases'<span>]); }</span></span>
//以上都可以看成是初始化,设置类的引用,别名,路径什么的。
$this->preinit();//暂时未发现有什么用,估计是留给后面扩展用的
$this->initSystemHandlers();//设置错误处理
$this->registerCoreComponents();//注册核心组件
$this->configure($config); //通过配置文件扩展类的属性,为空的时候什么也不做
$this->attachBehaviors($this->behaviors);
$this->preloadComponents();
$this->init();
}
$this下面的某些方法,在当前类是找不到的,因为它们可能是来自父类,最简单的方法就是ctrl+f搜索一下,没有就去类的声明处查看:
abstract class CApplication extends CModule
显然要进入CModule,如果此时还找不到想要方法,那么继续上一过程:
abstract class CModule extends CComponent
直到class CComponent
说明这就是当前这些家伙的老巢了。从代码的中注释中也可以看到:
CComponent is the base class for all components
说明我的想法是正确的,没错,它就是基类。
透过代码,我们可以发现,我们当前的应用,因为很多参数是空,所以很多逻辑都是直接跳过的。
看到这,$this的内容也大致明了啦。我们再回头看看
return new CWebApplication($config)->run();
通过前面的层层分析,此时的run方法也很好找了。就在CApplication 里边:
<span><span>public</span> <span>function</span><span> run() { </span><span>if</span>(<span>$this</span>->hasEventHandler('onBeginRequest'<span>)){ </span><span>$this</span>->onBeginRequest(<span>new</span> CEvent(<span>$this</span><span>)); } </span><span>register_shutdown_function</span>(<span>array</span>(<span>$this</span>,'end'),0,<span>false</span><span>); </span><span>$this</span>-><span>processRequest(); </span><span>if</span>(<span>$this</span>->hasEventHandler('onEndRequest'<span>)){ </span><span>$this</span>->onEndRequest(<span>new</span> CEvent(<span>$this</span><span>)); } }</span></span>
重点放在:$this->processRequest(); 因为前面和后面部分都是注册事件相关的,当前条件下执行不到。
<span><span>abstract</span> <span>public</span> <span>function</span> processRequest(); 这个方法在当前类中是抽象的,所以肯定在它的子类中实现了。回去找CWebApplication: <span>public</span> <span>function</span><span> processRequest() { </span><span>if</span>(<span>is_array</span>(<span>$this</span>->catchAllRequest) && <span>isset</span>(<span>$this</span>->catchAllRequest[0<span>])) { </span><span>$route</span>=<span>$this</span>->catchAllRequest[0<span>]; </span><span>foreach</span>(<span>array_splice</span>(<span>$this</span>->catchAllRequest,1) <span>as</span> <span>$name</span>=><span>$value</span><span>) </span><span>$_GET</span>[<span>$name</span>]=<span>$value</span><span>; } </span><span>else</span> <span>$route</span>=<span>$this</span>->getUrlManager()->parseUrl(<span>$this</span>-><span>getRequest()); </span><span>$this</span>->runController(<span>$route</span><span>); }</span></span>
注意重点在$this->runController($route);
<span><span>public</span> <span>function</span> runController(<span>$route</span><span>) { </span><span>if</span>((<span>$ca</span>=<span>$this</span>->createController(<span>$route</span>))!==<span>null</span><span>) { </span><span>list</span>(<span>$controller</span>,<span>$actionID</span>)=<span>$ca</span><span>; </span><span>$oldController</span>=<span>$this</span>-><span>_controller; </span><span>$this</span>->_controller=<span>$controller</span><span>; </span><span>$controller</span>-><span>init(); </span><span>$controller</span>->run(<span>$actionID</span><span>); </span><span>$this</span>->_controller=<span>$oldController</span><span>; } </span><span>else</span> <span>throw</span> <span>new</span> CHttpException(404,Yii::t('yii','Unable to resolve the request "{route}".', <span>array</span>('{route}'=><span>$route</span>===''?<span>$this</span>->defaultController:<span>$route</span><span>))); }</span></span>
我们要注意的代码只有两行:
$controller->init();
$controller->run($actionID);
这里的$controller可以能过查看createController得知,就是默认的控制器Sitecontroller.php
而Action则是index,你问我是怎么看出来的?哈哈,我在猜不出来的地方echo或var_dump一下不就可以了吗?这么简单的逻辑,还轮不到xdebug 这样的神器出场。
显然,init什么也没有做,看看run做了什么
Sitecontroller中没有run方法,又要去它的父类中查找。
class SiteController extends CController
在CController中有这个方法:
<span><span>public</span> <span>function</span> run(<span>$actionID</span><span>) { </span><span>if</span>((<span>$action</span>=<span>$this</span>->createAction(<span>$actionID</span>))!==<span>null</span><span>) { </span><span>if</span>((<span>$parent</span>=<span>$this</span>->getModule())===<span>null</span><span>){ </span><span>$parent</span>=Yii::<span>app(); } </span><span>if</span>(<span>$parent</span>->beforeControllerAction(<span>$this</span>,<span>$action</span><span>)) { </span><span>$this</span>->runActionWithFilters(<span>$action</span>,<span>$this</span>-><span>filters()); </span><span>$parent</span>->afterControllerAction(<span>$this</span>,<span>$action</span><span>); } } </span><span>else</span> <span>$this</span>->missingAction(<span>$actionID</span><span>); } </span></span>
能过查看$this->createAction($actionID),得到return new CInlineAction($this,$actionID);
我们呆会再看这个CInlineAction,先看$this->runActionWithFilters($action,$this->filters());
<span><span>public</span> <span>function</span> runActionWithFilters(<span>$action</span>,<span>$filters</span><span>) { </span><span>if</span>(<span>empty</span>(<span>$filters</span><span>)){ </span><span>$this</span>->runAction(<span>$action</span><span>); } </span><span>else</span><span> { </span><span>$priorAction</span>=<span>$this</span>-><span>_action; </span><span>$this</span>->_action=<span>$action</span><span>; CFilterChain</span>::create(<span>$this</span>,<span>$action</span>,<span>$filters</span>)-><span>run(); </span><span>$this</span>->_action=<span>$priorAction</span><span>; } }</span></span>
显然$filters是空的,所以执行第一个表达式$this->runAction($action);
<span><span>public</span> <span>function</span> runAction(<span>$action</span><span>) { </span><span>$priorAction</span>=<span>$this</span>-><span>_action; </span><span>$this</span>->_action=<span>$action</span><span>; </span><span>if</span>(<span>$this</span>->beforeAction(<span>$action</span><span>)) { </span><span>if</span>(<span>$action</span>->runWithParams(<span>$this</span>->getActionParams())===<span>false</span><span>){ </span><span>$this</span>->invalidActionParams(<span>$action</span><span>); } </span><span>else</span><span>{ </span><span>$this</span>->afterAction(<span>$action</span><span>); } } </span><span>$this</span>->_action=<span>$priorAction</span><span>; }</span></span>
这段代码的重点是 $action->runWithParams($this->getActionParams())这一句;
这里的$action就是$this->createAction($actionID)返回的结果,而它的结果就是
return new CInlineAction($this,$actionID);
CInlineAction.php
是时候查看CInlineAction了;
<span> <span>public</span> <span>function</span> runWithParams(<span>$params</span><span>) { </span><span>$methodName</span>='action'.<span>$this</span>-><span>getId(); </span><span>$controller</span>=<span>$this</span>-><span>getController(); </span><span>$method</span>=<span>new</span> ReflectionMethod(<span>$controller</span>, <span>$methodName</span><span>); </span><span>if</span>(<span>$method</span>->getNumberOfParameters()>0<span>) </span><span>return</span> <span>$this</span>->runWithParamsInternal(<span>$controller</span>, <span>$method</span>, <span>$params</span><span>); </span><span>else</span> <span>return</span> <span>$controller</span>-><span>$methodName</span><span>(); }</span></span>
哇哦,好高级,居然还用了反射,不过我喜欢!
不过呢,打印$method发现: