模块结构
首先我们来看一下CWebApplication的类的继承结构:
从上面我们可以看到CWebApplication本身也是一个CModue。在YII中,模块之间是一个树形结构。即每一个模块都可以包含多个子模块,每一个子模块可以继续包含子模块.其中APP为树的头节点,如图:
对于一个具体请求,假设route=A/B/C/D,下面我们讲述一下,APP怎么选择相应的模块和模块中的控制器Controller和动作Action。具体的流程图如下:
整个过程包括两个部分:选择模块和选择模块中的控制器。整个过程如下:
初始化情况下:module为YII::APP,route=a/b/c/d,id=null。
在module=YII::APP情况下,此时id=a,route=b/c/d。
如果id=a为当前module下控制器,此时,module,controller,action和$_GET都确定。
如果id=a确定不是当前module下的控制器,
在当前module下面找到了id=a的子模块,此时就更新模块为module=YII::APP/a,并且循环上面过程。
在当期module下面没有找到id=a的子模块,此时module不改变,并且在后面的过程中,模块也不会再改变。此时id=a指的当前模块下的目标控制器Controller的命名空间,即controller Namespace=a。
后面过程主要是更新当前模块下的目标控制器Controller的命名空间。
描述的不是很清楚,但是上面流程图还是很清晰的吧。
对于读过YII源码都直到,任何一个web请求都是通过CApplication::Run()函数开始,进入到CWebApplication::processRequest()。源码分别如下:
//CApplication
public function run() { if($this->hasEventHandler('onBeginRequest')) $this->onBeginRequest(new CEvent($this)); $this->processRequest();//=========== if($this->hasEventHandler('onEndRequest')) $this->onEndRequest(new CEvent($this)); } //CWebApplication public function processRequest() { if(is_array($this->catchAllRequest) && isset($this->catchAllRequest[0])) { $route=$this->catchAllRequest[0]; foreach(array_splice($this->catchAllRequest,1) as $name=>$value) $_GET[$name]=$value; } else $route=$this->getUrlManager()->parseUrl($this->getRequest()); $this->runController($route);//+++++++++++++++++++++ }
程序真正的进行到相应模块,控制器,动作是在函数processRequest的$this->runController($route);中,该函数传入的参数为$route。何为 $route。其实$route是一个类似A/B/C/D格式的字符串,该字符串中可能包含了模块,控制器,动作和$_GET相关参数。该函数是也是定义在CWebApplication中。代码如下:
//CWebApplication
public function runController($route) { if(($ca=$this->createController($route))!==null) { list($controller,$actionID)=$ca; $oldController=$this->_controller; $this->_controller=$controller; $controller->init(); $controller->run($actionID); $this->_controller=$oldController; } else throw new CHttpException(404,Yii::t('yii','Unable to resolve the request "{route}".', array('{route}'=>$route===''?$this->defaultController:$route))); }
通过该函数的名称字面意思为"运行控制器"。因此首先第一步就是要创建一个控制器,即函数createController($route)。我们知道控制器的是包含在模块中,因此也在该函数中,通过分析$route可以得到相应的模块,以及模块中的控制器和相应的动作,即list($controller,$actionID)=$ca;上面的流程图即为该函数createController($route)的函数流程图。
现在应该清楚了一个模块是怎么被web用户所调用了吧。下面我们说一下程序怎么在应用程序中添加模块,当然可能是通过配置文件。。。废话。
添加模块
上面我们知道模块之间是呈现树形结构,在每一个模块的basePath里面默认有一个modules目录来存放该模块的子模块,每一个子模块为一个子目录,一直这样的递归下去。对于web应用程序来说,把模块的程序文件分到相应的目录下面,不代表就就可以被用户所调用,需要通过配置文件进行配置,从而可以通过分析URL得到。在官方的文档也讲的怎么配置一个子模块。下面我们用例子来讲解怎么配置模块。
1.假设当前Web中只有一个YII::APP应用程序,当然它也是一个模块,访问的方式为http://localhost/index.php,此时在配置文件main.php中配置的modules如下:
'modules'=>array(),
2.假设YII::APP模块有两个子模块gii和test,此时我们就可以通过http://localhost/index.php/gii和http://localhost/index.php/test分别进行访问,此时配置文件为:
'modules'=>array( 'gii'=>array( 'class'=>'system.gii.GiiModule', 'password'=>'chenze', ), 'test', ),
3.此时我们假设gii模块下面还有一个子模块giiTest,此时我们就可以通过http://localhost/index.php/gii/giiTest进行访问,此时配置为:
'modules'=>array( 'gii'=>array( 'class'=>'system.gii.GiiModule', 'password'=>'chenze', 'modules'=>array( 'giiTest' ), ), 'test', ),
就这样递归下去咯
模块源码分析
CModule分析
1. 首先模块是一个CModule类型的组件(CComponent),即如下:
abstract class CModule extends CComponent
一个组件对象CComponent是一个可以用来挂载在组件上事件数组CComponet::$_event[]和行为数组CComponent::$_behavie[]的对象。因此也可以在CModule对象上挂载和模块相关的事件和行为信息。
2. 一个CModule对象包含了一些基本信息,如下:
一个模块拥有一个名称,即CModule::$_id属性。
一个模块有一个他所归属的父模块,即CModule::$_parentModule属性。
一个模块在物理上一个目录,拥有一个路径,可以通过该目录下面找到相应的控制器,模型和视图的程序文件。即CModule::$_basePath。这个值为模块程序文件(***Module.php)所在的目录。
一个模块下面可以包含多个子模块,子模块的程序文件放在当前模块目录下的"modules"里面,该目录由CModule::$_modulePath变量来标识,子模块通过子模块ID索引存储在CModule::$_modules[]和CModule::$_moduleConfig[]里面。
一个模块的正常运行需要其他组件CCOmponent的支持,比如db组件。通过CModule::$_components[]和CModule::$_componentConfig[]可以进行索引。对于一个模块,可以通过设置CModule::$preload[]数组来设置该模块需要预先加载的组件。
一个模块可以挂载一些与模块相关的行为CModule::$behaviors[]
3.一个CModule对象包含了一些基本操作,如下:
在当前模块下,创建和获得子模块:getModule($id)。
在当前模块下,加载和获得的模块中的组件对象:function getComponent($id,$createIfNull=true)。
4.在CModule对象的构造函数中定义了一个模块的初始化过程,因此一个继承的自定义模块的构造函数要调用父类的构造函数parent::__construct($id,$parent,$config=null)来初始化一个模块。
public function __construct($id,$parent,$config=null)//$id为模块ID,$parent为父模块ID { $this->_id=$id; $this->_parentModule=$parent; // set basePath at early as possible to avoid trouble if(is_string($config)) $config=require($config); if(isset($config['basePath'])) { $this->setBasePath($config['basePath']);//设置模块路径 unset($config['basePath']); } Yii::setPathOfAlias($id,$this->getBasePath());//把该模块的路径添加到Alias中,方便访问 //下面几个函数就是模块的初始化需要做的几件事情,可以通过重写preinit()和init()来自定义模块可以通过自定义相应的函数来定制和初始化模块的参数 $this->preinit();//预初始化,一般用来设置模块相关的行为对象$behaviors[],预加载对象$preload[],和其他配置。 $this->configure($config);//加载配置 $this->attachBehaviors($this->behaviors);//挂载行为对象 $this->preloadComponents();//加载CModule::$preload[]中的预加载组件 $this->init();//模块真正初始化,一般情况下,重写该函数完整模块初始化 }
CWebModule分析
YII有cli和web两种运行模式,所有CApplication,CModule都有相应在web环境的下的实现类。对于模块,即CWebModule类,该类是平时自定义模块时候的直接父类。该类主要定义了一些和一个web模块相关的参数,比如该模块的默认控制器和动作,页面布局对象等,下面我们具体来分析一下:
1. CWebModule中定义了上述和web相关的变量,即
一个模块包含了多个控制器,控制器通过名称进行索引,这些控制器一旦创建,可以通过数组CWebModule::$controllerMap[]进行索引,对于一个未提供控制器的请求,模块使用CWebModule::$defaultController='default'作为默认的控制器,相当于模块首页,所以在该模块下面的Controllers目录下,一般都一个DefaultController.php模块首页程序文件。这个和CWebApplication是不同的,CWebApplication::$defaultController="site"。
模块的控制器还有一个命名空间的概念,即CWebModule::$controllerNamespace。
一个模块有三个主要的路径:控制器文件,布局文件,视图文件分别由CWebModule::$_controllerPath,CWebMouble::$_layoutPath,CWebModule::$_viewPath指定,如果没有通过相应的set函数进行手动设定,默认值为分别为:$_controllerPath=$this->getBasePath().'/controllers';$_layoutPath=$_viewPath."/layouts" ; $_viewPath =$this->getBasePath()."/views";
一般情况下,需要手动指定一个模块的布局文件,通过设置CWebModule::$layout。如果没有指定,那么该模块将会使用父模块的布局或者YII::APP中定义的布局文件,即CWebApplication::$layout="main"。
2.CWebModule中定义的操作不多,对于一个自定义的模块一般只需要重写CModule中的函数就可以定制。CWebModule只有两个函数可以进行覆盖,即如下:
public function beforeControllerAction($controller,$action) { if(($parent=$this->getParentModule())===null) $parent=Yii::app(); return $parent->beforeControllerAction($controller,$action); } public function afterControllerAction($controller,$action) { if(($parent=$this->getParentModule())===null) $parent=Yii::app(); $parent->afterControllerAction($controller,$action); }
可以通过此两个函数对该模块中的每一个请求进行一些改写,控制以及收尾的相关工作。在自定义的模块中,两个函数的覆盖的模版如下:
public function beforeControllerAction($controller, $action) { if(parent::beforeControllerAction($controller, $action)) { // this method is called before any module controller action is performed // you may place customized code here return true; } else return false; }