yii源码分析3
转载请注明:TheViper http://www.cnblogs.com/TheViper/
上一篇说到CWebApplication中的¥route=$this->getUrlManager ()->parseUrl ($this->getRequest());,得到$route=controler/actionid。
这篇说他后面的$this->runController ( $route );
1 <?php 2 class CWebApplication extends CApplication { 3 public $controllerNamespace; 4 private $_controllerPath; 5 private $_viewPath; 6 private $_systemViewPath; 7 private $_controller; 8 public $controllerMap=array(); 9 public function processRequest() {//开始执行请求 10 //获取urlManager组件,解析请求,得到controller/action这种格式的string, 11 //并且将隐藏参数与请求的参数一一对应,匹配起来,写入$_REQUEST中 12 $route = $this->getUrlManager ()->parseUrl ($this->getRequest()); 13 $this->runController ( $route ); 14 } 15 public function getRequest() {//获取request组件 16 return $this->getComponent ( 'request' ); 17 } 18 protected function registerCoreComponents() {//注册核心组件 19 parent::registerCoreComponents (); 20 } 21 //执行contronller 22 public function runController($route) { 23 if (($ca = $this->createController ( $route )) !== null) { 24 list ( $controller, $actionID ) = $ca; 25 $oldController = $this->_controller; 26 $this->_controller = $controller; 27 $controller->init ();//钩子,在执行action方法前调用,子类去实现 28 $controller->run ( $actionID );//开始转入controller类中action方法的执行 29 $this->_controller = $oldController; 30 } 31 } 32 //创建controller类实例,从controller/action这种格式的string中解析出$controller, $actionID 33 public function createController($route, $owner = null) { 34 if ($owner === null) 35 $owner = $this; 36 if (($route = trim ( $route, '/' )) === '') 37 $route = $owner->defaultController; 38 39 $route .= '/'; 40 while ( ($pos = strpos ( $route, '/' )) !== false ) { 41 $id = substr ( $route, 0, $pos ); 42 if (! preg_match ( '/^\w+$/', $id )) 43 return null; 44 $id = strtolower ( $id ); 45 $route = ( string ) substr ( $route, $pos + 1 ); 46 if (! isset ( $basePath )) // first segment 47 { 48 $basePath = $owner->getControllerPath (); 49 $controllerID = ''; 50 } else { 51 $controllerID .= '/'; 52 } 53 $className = ucfirst ( $id ) . 'Controller'; 54 $classFile = $basePath . DIRECTORY_SEPARATOR . $className . '.php'; 55 56 if (is_file ( $classFile )) { 57 if (! class_exists ( $className, false )) 58 require ($classFile); 59 if (class_exists ( $className, false ) && is_subclass_of ( $className, 'CController' )) { 60 $id [0] = strtolower ( $id [0] ); 61 return array ( 62 new $className ( $controllerID . $id, $owner === $this ? null : $owner ), 63 $this->parseActionParams ( $route ) 64 ); 65 } 66 return null; 67 } 68 $controllerID .= $id; 69 $basePath .= DIRECTORY_SEPARATOR . $id; 70 } 71 } 72 protected function parseActionParams($pathInfo) { 73 if (($pos = strpos ( $pathInfo, '/' )) !== false) { 74 $manager = $this->getUrlManager ();//再次获取urlManager,在上面第一次调用中已经导入。 75 $manager->parsePathInfo ( ( string ) substr ( $pathInfo, $pos + 1 ) ); 76 $actionID = substr ( $pathInfo, 0, $pos ); 77 return $manager->caseSensitive ? $actionID : strtolower ( $actionID ); 78 } else 79 return $pathInfo; 80 } 81 public function getControllerPath() { 82 if ($this->_controllerPath !== null) 83 return $this->_controllerPath; 84 else 85 return $this->_controllerPath = $this->getBasePath () . DIRECTORY_SEPARATOR . 'controllers'; 86 } 87 //两个钩子,子类去实现 88 public function beforeControllerAction($controller, $action) { 89 return true; 90 } 91 public function afterControllerAction($controller, $action) { 92 } 93 protected function init() { 94 parent::init (); 95 } 96 }
$ca = $this->createController ( $route ));createController的作用是将$route中的controller和action分离出来,并创建controller实例。
最后返回controller实例和actionid.
然后回到CWebApplication的runController($route),$controller->init ();在controller初始化时执行,这个需要在子类中重写,比如:
1 class VideoController extends CController { 2 public function init() { 3 $this->db = Yii::app ()->db; 4 } 5 public function actionBroadcast() { 6 $b = $this->db->query ( "", array (1 ) ); 7 $this->render ( "u_broadcast", array ( 8 'b' => $b [0] ; 9 ) ); 10 } 11 }
这样在VideoController中便可以用$this->db调用db组件了。
$controller->run ( $actionID );转入Ccontroller.
1 <?php 2 class CController { 3 protected $db; 4 public $defaultAction = 'index'; 5 private $_id; 6 private $_action; 7 public function __construct($id, $module = null) { 8 $this->_id = $id; 9 } 10 public function init() { 11 } 12 //过滤方法,子类重写 13 public function filters() { 14 return array (); 15 } 16 public function run($actionID) { 17 //创建action实例 18 if (($action = $this->createAction ( $actionID )) !== null) { 19 $parent = Yii::app (); 20 if ($parent->beforeControllerAction ( $this, $action )) { 21 $this->runActionWithFilters ( $action, $this->filters () ); 22 $parent->afterControllerAction ( $this, $action ); 23 } 24 } 25 } 26 public function refresh($terminate = true, $anchor = '') { 27 $this->redirect ( Yii::app ()->getRequest ()->getUrl () . $anchor, $terminate ); 28 } 29 public function redirect($url, $terminate = true, $statusCode = 302) { 30 Yii::app ()->getRequest ()->redirect ( $url, $terminate, $statusCode ); 31 } 32 //如果controller里面有filter 33 public function runActionWithFilters($action, $filters) { 34 if (empty ( $filters )) 35 $this->runAction ( $action ); 36 else { 37 $priorAction = $this->_action; 38 $this->_action = $action; 39 CFilterChain::create ( $this, $action, $filters )->run (); 40 $this->_action = $priorAction; 41 } 42 } 43 public function runAction($action) { 44 $priorAction = $this->_action; 45 $this->_action = $action; 46 if ($this->beforeAction ( $action )) { 47 if ($action->runWithParams ( $this->getActionParams () ) === false) 48 $this->invalidActionParams ( $action ); 49 else 50 $this->afterAction ( $action ); 51 } 52 $this->_action = $priorAction; 53 } 54 //渲染视图 55 public function render($view, $data = array()) { 56 if (isset ( $data )) 57 extract ( $data ); 58 include VIEWS_DIR . "/" . $this->_id . "/" . $view . ".php"; 59 } 60 public function renderFile($file, $data = array()) { 61 if (isset ( $data )) 62 extract ( $data ); 63 include VIEWS_DIR . "/" . $file; 64 } 65 //跳转到另一个controller/action,不过浏览器的地址没有变 66 public function forward($route) { 67 if (strpos ( $route, '/' ) === false) 68 $this->run ( $route ); 69 else { 70 //不在同一个controller里面,重新创建 71 Yii::app ()->runController ( $route ); 72 } 73 } 74 public function getActionParams() { 75 return $_GET; 76 } 77 public function createAction($actionID) { 78 if ($actionID === '') 79 $actionID = $this->defaultAction; 80 if (method_exists ( $this, 'action' . $actionID ) && strcasecmp ( $actionID, 's' )) 81 return new CInlineAction ( $this, $actionID ); 82 } 83 public function getAction() { 84 return $this->_action; 85 } 86 public function setAction($value) { 87 $this->_action = $value; 88 } 89 public function getId() { 90 return $this->_id; 91 } 92 //两个钩子 93 protected function beforeAction($action) { 94 return true; 95 } 96 protected function afterAction($action) { 97 } 98 }
$this->createAction ( $actionID );创建action实例.
然后是runActionWithFilters($action, $filters);如果没有filter(),直接runAction($action)。
$action->runWithParams ( $this->getActionParams () ).$action是CInlineAction实例。
1 <?php 2 class CInlineAction extends CAction 3 { 4 //执行该动作 5 public function run() 6 { 7 $method='action'.$this->getId(); 8 $this->getController()->$method(); 9 } 10 //执行带提供的请求的参数的动作 11 public function runWithParams($params) 12 { 13 $methodName='action'.$this->getId();//拼接action方法 14 $controller=$this->getController(); 15 $method=new ReflectionMethod($controller, $methodName);//反射 16 if($method->getNumberOfParameters()>0)//方法参数个数>0 17 return $this->runWithParamsInternal($controller, $method, $params); 18 else 19 return $controller->$methodName(); 20 } 21 }
CAction
1 <?php 2 abstract class CAction extends CComponent 3 { 4 private $_id; 5 private $_controller; 6 public function __construct($controller,$id) 7 { 8 $this->_controller=$controller; 9 $this->_id=$id; 10 } 11 public function getController() 12 { 13 return $this->_controller; 14 } 15 public function getId() 16 { 17 return $this->_id; 18 } 19 //运行带有请求参数的对象。 这个方法通过CController::runAction()内部调用 20 public function runWithParams($params) 21 { 22 $method=new ReflectionMethod($this, 'run'); 23 if($method->getNumberOfParameters()>0) 24 return $this->runWithParamsInternal($this, $method, $params); 25 else 26 return $this->run(); 27 } 28 //执行一个带有命名参数的对象的方法 29 protected function runWithParamsInternal($object, $method, $params) 30 { 31 $ps=array(); 32 foreach($method->getParameters() as $i=>$param) 33 { 34 $name=$param->getName(); 35 if(isset($params[$name])) 36 { 37 if($param->isArray()) 38 $ps[]=is_array($params[$name]) ? $params[$name] : array($params[$name]); 39 elseif(!is_array($params[$name])) 40 $ps[]=$params[$name]; 41 else 42 return false; 43 } 44 elseif($param->isDefaultValueAvailable()) 45 $ps[]=$param->getDefaultValue(); 46 else 47 return false; 48 } 49 $method->invokeArgs($object,$ps);//反射,执行 50 return true; 51 } 52 }
这两个类都很简单,就是执行controller类中的action方法。
回到上面的runActionWithFilters($action, $filters);如果有filter(),CFilterChain::create ( $this, $action, $filters )->run ();
显然,如果有filter的话必须在执行action方法前,就设置好filter过滤器列表。
CFilterChain就是将类似于'application.filters.LoginFilter+upload_video' 这种配置解析成过滤器链。
过滤器链的每一项是一个CInlineFilter或CFilter实例。
1 <?php 2 //过滤器列表 3 class CFilterChain extends CList { 4 public $controller; 5 public $action; 6 public $filterIndex = 0; 7 public function __construct($controller, $action) { 8 $this->controller = $controller; 9 $this->action = $action; 10 } 11 //创建过滤器列表 12 public static function create($controller, $action, $filters) { 13 $chain = new CFilterChain ( $controller, $action ); 14 $actionID = $action->getId (); 15 foreach ( $filters as $filter ) { 16 if (is_string ( $filter )) // filterName [+|- action1 action2] 17 { 18 if (($pos = strpos ( $filter, '+' )) !== false || ($pos = strpos ( $filter, '-' )) !== false) { 19 $matched = preg_match ( "/\b{$actionID}\b/i", substr ( $filter, $pos + 1 ) ) > 0; 20 if (($filter [$pos] === '+') === $matched) 21 $filter = CInlineFilter::create ( $controller, trim ( substr ( $filter, 0, $pos ) ) ); 22 } else 23 $filter = CInlineFilter::create ( $controller, $filter ); 24 } elseif (is_array ( $filter )) // array('path.to.class [+|- action1, action2]','param1'=>'value1',...) 25 { 26 $filterClass = $filter [0]; 27 unset ( $filter [0] ); 28 //开始解析过滤器配置 29 if (($pos = strpos ( $filterClass, '+' )) !== false || ($pos = strpos ( $filterClass, '-' )) !== false) { 30 preg_match ( "/\b{$actionID}\b/i", substr ( $filterClass, $pos + 1 ), $a ); 31 $matched = preg_match ( "/\b{$actionID}\b/i", substr ( $filterClass, $pos + 1 ) ) > 0; 32 //如果是filterName+action,创建一个过滤器,否则忽略 33 if (($filterClass [$pos] === '+') === $matched) { 34 //解析出过滤器的类名 35 $filterClass = trim ( substr ( $filterClass, 0, $pos ) ); 36 } else 37 continue; 38 } 39 $filter ['class'] = $filterClass; 40 $filter = Yii::createComponent ( $filter ); 41 } 42 43 if (is_object ( $filter )) { 44 $filter->init (); 45 $chain->add ( $filter );//list添加过滤器 46 } 47 } 48 return $chain; 49 } 50 public function run() { 51 if ($this->offsetExists ( $this->filterIndex )) {//过滤器列表个数不为0 52 //取出过滤器实例 53 $filter = $this->itemAt ( $this->filterIndex ++ ); 54 $filter->filter ( $this ); 55 } else 56 $this->controller->runAction ( $this->action ); 57 } 58 }
'application.filters.LoginFilter+upload_video' 这种配置会创建CFilter实例。
1 <?php 2 class CFilter extends CComponent { 3 public function filter($filterChain) { 4 //前置,后置方法 5 if ($this->preFilter ( $filterChain )) { 6 $filterChain->run (); 7 $this->postFilter ( $filterChain ); 8 } 9 } 10 //钩子 11 public function init() { 12 } 13 protected function preFilter($filterChain) { 14 return true; 15 } 16 protected function postFilter($filterChain) { 17 } 18 }
然后是上面的CFilterChain::create ( $this, $action, $filters )->run ();中的run(),如果请求被解析成的action是upload_video,yii就会取出LoginFilter实例。
比如我的LoginFilter
1 <?php 2 class LoginFilter extends CFilter { 3 protected function preFilter($filterChain) { 4 if (! isset ( $_SESSION )) { 5 session_start (); 6 } 7 if (isset ( $_SESSION ['user'] )) 8 return true; 9 else { 10 setcookie ( "return", Yii::app ()->getRequest ()->getUrl (), time () + 360, '/' ); 11 Yii::app ()->getRequest ()->redirect ( 'http://localhost/youtube/login', true, 302 ); 12 return false; 13 } 14 } 15 protected function postFilter($filterChain) { 16 } 17 } 18 ?>
里面就一个前置和后置,表示对于需要启用过滤器的action方法,分别在执行action方法之前和之后执行自己定义的preFilter,postFilter方法。
然后是$filterChain->run ();这里很容易出错。
其实是再次用CFilterChain里面的run(),注意到里面的$this->filterIndex++,这有点像递归.
如果过滤器要过滤对个action,就像这样去CFilter,然后$filterChain->run ();返回CFilterChain,同时$this->filterIndex++,然后继续$filterChain->run ();。。。。。。
对于我的'application.filters.LoginFilter+upload_video',只过滤upload_video这一个action,所以当返回CFilterChain时,$this->filterIndex已经变成1了,而过滤器列表只有一个过滤器实例,所以这次就会走$this->controller->runAction ( $this->action );了,这就和没设置过滤器时走的$this->runAction ( $action );一样了。
过滤器分析完后,就是什么数据操作之类的,最后是渲染视图render()。这就太简单了,extract($data),然后在include下视图文件就可以了。
还有forward()方法,就是跳转到另一个controller/action,实质就是返回CWebApplication的runController再来一遍上面分析的过程。
最后附上,裁剪的yii https://files.cnblogs.com/TheViper/framework.zip