yii源码分析1,yii源码分析转载请注明:theviper http://www.cnblogs.com/theviper/
本文将对yii中的mvc,路由器,filter,组件机制等最主要的部分进行自己的一点浅析,力求说明自己做一个php mvc不是那么的遥不可及,其实是很简单的。
源码基于yii 1.13,为了方便说明,我对其进行了大量的裁剪,不过还是让他保有上面的那些最重要的功能。裁剪下来,其实没有几个文件了,而且每个文件代码最多100多行,避免因为代码太多而懒得看。
所谓的mvc都是让所有请求从一个地方进去,通过对请求,配置的解析,分发到对应的类方法中。
首先当然是入口文件,index.php.
1 run ();
引入yii.php
1 path 9 private static $_imports = array (); // alias => class name or directory 10 private static $_includepaths; // list of include paths 11 private static $_app; 12 private static $_logger; 13 public static function createwebapplication($config = null) { 14 return self::createapplication ( 'cwebapplication', $config ); 15 } 16 public static function createapplication($class, $config = null) { 17 return new $class ( $config ); 18 } 19 public static function app() { 20 return self::$_app; 21 } 22 //别名路径 23 public static function getpathofalias($alias) { 24 if (isset ( self::$_aliases [$alias] )) 25 return self::$_aliases [$alias]; 26 elseif (($pos = strpos ( $alias, '.' )) !== false) { 27 $rootalias = substr ( $alias, 0, $pos ); 28 if (isset ( self::$_aliases [$rootalias] )) 29 return self::$_aliases [$alias] = rtrim ( self::$_aliases [$rootalias] . directory_separator . str_replace ( '.', directory_separator, substr ( $alias, $pos + 1 ) ), '*' . directory_separator ); 30 } 31 return false; 32 } 33 public static function setpathofalias($alias, $path) { 34 if (empty ( $path )) 35 unset ( self::$_aliases [$alias] ); 36 else 37 self::$_aliases [$alias] = rtrim ( $path, '\\/' ); 38 } 39 public static function setapplication($app) { 40 if (self::$_app === null || $app === null) 41 self::$_app = $app; 42 } 43 public static function import($alias, $forceinclude = false) { 44 if (isset ( self::$_imports [$alias] )) // previously imported 45 return self::$_imports [$alias]; 46 47 if (class_exists ( $alias, false ) || interface_exists ( $alias, false )) 48 return self::$_imports [$alias] = $alias; 49 if (($pos = strrpos ( $alias, '.' )) === false) // a simple class name 50 { 51 // try to autoload the class with an autoloader if $forceinclude is true 52 if ($forceinclude && (yii::autoload ( $alias, true ) || class_exists ( $alias, true ))) 53 self::$_imports [$alias] = $alias; 54 return $alias; 55 } 56 57 $classname = ( string ) substr ( $alias, $pos + 1 ); 58 $isclass = $classname !== '*'; 59 60 if ($isclass && (class_exists ( $classname, false ) || interface_exists ( $classname, false ))) 61 return self::$_imports [$alias] = $classname; 62 63 if (($path = self::getpathofalias ( $alias )) !== false) { 64 if ($isclass) { 65 if ($forceinclude) { 66 if (is_file ( $path . '.php' )) 67 require ($path . '.php'); 68 else 69 throw new cexception ( yii::t ( 'yii', 'alias {alias} is invalid. make sure it points to an existing php file and the file is readable.', array ( 70 '{alias}' => $alias 71 ) ) ); 72 self::$_imports [$alias] = $classname; 73 } else 74 self::$classmap [$classname] = $path . '.php'; 75 return $classname; 76 } else // a directory 77 { 78 if (self::$_includepaths === null) { 79 self::$_includepaths = array_unique ( explode ( path_separator, get_include_path () ) ); 80 if (($pos = array_search ( '.', self::$_includepaths, true )) !== false) 81 unset ( self::$_includepaths [$pos] ); 82 } 83 84 array_unshift ( self::$_includepaths, $path ); 85 86 if (self::$enableincludepath && set_include_path ( '.' . path_separator . implode ( path_separator, self::$_includepaths ) ) === false) 87 self::$enableincludepath = false; 88 89 return self::$_imports [$alias] = $path; 90 } 91 } 92 } 93 //创建组件实例 94 public static function createcomponent($config) { 95 if (is_string ( $config )) { 96 $type = $config; 97 $config = array (); 98 } elseif (isset ( $config ['class'] )) { 99 $type = $config ['class'];100 unset ( $config ['class'] );101 }102 if (! class_exists ( $type, false )) {103 $type = yii::import ( $type, true );104 }105 if (($n = func_num_args ()) > 1) {106 $args = func_get_args ();107 if ($n === 2)108 $object = new $type ( $args [1] );109 elseif ($n === 3)110 $object = new $type ( $args [1], $args [2] );111 elseif ($n === 4)112 $object = new $type ( $args [1], $args [2], $args [3] );113 else {114 unset ( $args [0] );115 $class = new reflectionclass ( $type );116 // note: reflectionclass::newinstanceargs() is available for php 5.1.3+117 // $object=$class->newinstanceargs($args);118 $object = call_user_func_array ( array (119 $class,120 'newinstance' 121 ), $args );122 }123 } else124 $object = new $type ();125 foreach ( $config as $key => $value )126 $object->$key = $value;127 128 return $object;129 }130 //按需加载相应的php131 public static function autoload($classname) {132 include self::$_coreclasses [$classname];133 }134 private static $_coreclasses = array (135 'capplication' => '/base/capplication.php',136 'cmodule' => '/base/cmodule.php',137 'cwebapplication' => '/base/cwebapplication.php',138 'curlmanager' => 'curlmanager.php',139 'ccomponent' => '/base/ccomponent.php'140 , 'curlrule' => 'curlrule.php',141 'ccontroller' => 'ccontroller.php',142 'cinlineaction' => '/actions/cinlineaction.php',143 'caction' => '/actions/caction.php',144 'cfilterchain' => '/filters/cfilterchain.php',145 'cfilter' => '/filters/cfilter.php',146 'clist' => '/collections/clist.php',147 'chttprequest' => 'chttprequest.php',148 'cdb' => 'cdb.php',149 'cinlinefilter' => 'filters/cinlinefilter.php' 150 );151 }152 153 spl_autoload_register ( array (154 'yiibase',155 'autoload' 156 ) );
看似很多,其实就三个地方注意下就可以了
1.spl_autoload_register,用这个就可以实现传说中的按需加载相应的php了,坑爹啊。
2.createcomponent($config)这个方法是yii组件调用的核心。在配置中注册的所有组件都是通过它获取组件类的实例的。比如配置:
1 dirname ( __file__ ) . directory_separator . '..', 4 5 'import' => array ( 6 'application.util.*' 7 ), 8 9 'components' => array (10 'db' => array (11 'class' => 'cdb',12 'driver' => 'mysql',13 'hostname' => 'localhost',14 'username' => 'root',15 'password' => '',16 'database' => 'youtube' 17 ),18 'urlmanager' => array (19 'urlformat' => 'path',20 'rules' => array (21 'comment_reply//' => 'reply/load_comment_reply',22 'b/' => array (23 'video/broadcast',24 'urlsuffix' => '.html' 25 ),26 'c/' => 'video/list_more_video',27 'u/reg' => 'user/reg',28 'v/upload' => 'video/upload_video',29 'login' => 'user/to_login',30 'show_chanel/' => 'show/chanel' ,31 'show/' => 'show/show',32 ) 33 ) 34 ) 35 );36 ?>
这个文件就返回了个map,里面components中的db,urlmanager便是我注册的系统中的组件,里面的array便是组件的参数.
从源码中看到$type = $config ['class'];$object = new $type;就创建了注册的类实例了。
3.import($alias, $forceinclude = false)。作用:导入一个类或一个目录。导入一个类就像包含相应的类文件。 主要区别是导入一个类比较轻巧, 它仅在类文件首次引用时包含。这个也是yii的一个核心优化点。
这个在上面createcomponent($config)中有用到,
if (!class_exists ( $type, false )) {
$type = yii::import ( $type, true );
}
如果$type类没有定义,就去导入。有些组件,核心在yii中多次create调用,这样就保证了仅在类文件首次引用时导入。
下面分析index.php中yii::createwebapplication ( $config )的调用过程。
这个调用是去了cwebapplication。
1 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 //执行contronller22 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 segment47 {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 } else79 return $pathinfo;80 }81 public function getcontrollerpath() {82 if ($this->_controllerpath !== null)83 return $this->_controllerpath;84 else85 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 }
没有构造方法,构造方法在父类capplication里面。
1 setbasepath ( $config ['basepath'] );12 unset ( $config ['basepath'] );13 } else14 $this->setbasepath ( 'protected' );15 //设置别名,后面就可以用application表示basepath了16 yii::setpathofalias ( 'application', $this->getbasepath () );17 //钩子,模块 预 初始化时执行,子类实现。不过这时,配置还没有写入框架18 $this->preinit ();19 $this->registercorecomponents ();20 //父类实现21 $this->configure ( $config );22 //加载静态应用组件23 $this->preloadcomponents ();24 //这才开始初始化模块25 $this->init ();26 }27 protected function registercorecomponents() {28 $components = array (29 'request' => array (30 'class' => 'chttprequest'31 ),32 'urlmanager' => array (33 'class' => 'curlmanager'34 )35 );36 37 $this->setcomponents ( $components );//父类实现38 }39 public function run() {40 $this->processrequest ();41 }42 public function getid() {43 if ($this->_id !== null)44 return $this->_id;45 else46 return $this->_id = sprintf ( '%x', crc32 ( $this->getbasepath () . $this->name ) );47 }48 public function setid($id) {49 $this->_id = $id;50 }51 public function getbasepath() {52 return $this->_basepath;53 }54 public function setbasepath($path) {55 if (($this->_basepath = realpath ( $path )) === false || ! is_dir ( $this->_basepath ))56 return;57 }58 public function getdb() {59 return $this->getcomponent ( 'db' );//父类实现60 }61 public function geturlmanager() {62 return $this->getcomponent ( 'urlmanager' );63 }64 public function getcontroller() {65 return null;66 }67 public function getbaseurl($absolute = false) {68 return $this->getrequest ()->getbaseurl ( $absolute );69 }70 }
__construct里面注释写的很详细了,值得注意的是registercorecomponents ()。前面说了那么多,那么yii::createwebapplication ( $config )到底是做什么的。
其实最终目的对这个裁剪过的yii而言就是注册核心组件。就这么简单.
setcomponents ( $components )在父类cmodule里面.
1 hascomponent ( $name ))17 return $this->getcomponent ( $name );18 else19 return parent::__get ( $name );20 }21 public function __isset($name) {22 if ($this->hascomponent ( $name ))23 return $this->getcomponent ( $name ) !== null;24 else25 return parent::__isset ( $name );26 }27 public function hascomponent($id) {28 return isset ( $this->_components [$id] ) || isset ( $this->_componentconfig [$id] );29 }30 //31 public function getcomponent($id, $createifnull = true) {32 if (isset ( $this->_components [$id] ))33 return $this->_components [$id];34 else if (isset ( $this->_componentconfig [$id] ) && $createifnull) {35 $config = $this->_componentconfig [$id];36 $component = yii::createcomponent ( $config );//yiibase,返回组件实例37 $component->init ();//钩子,调用子类重写的init方法38 //将组件写入数组保存,并返回39 return $this->_components [$id] = $component;40 }41 }42 public function setcomponent($id, $component, $merge = true) {43 //组件写入数组保存44 if (isset ( $this->_componentconfig [$id] ) && $merge) {45 46 $this->_componentconfig [$id] = self::mergearray ( $this->_componentconfig [$id], $component );47 } else {48 49 $this->_componentconfig [$id] = $component;50 }51 }52 public static function mergearray($a, $b) {53 $args = func_get_args ();54 $res = array_shift ( $args );55 while ( ! empty ( $args ) ) {56 $next = array_shift ( $args );57 foreach ( $next as $k => $v ) {58 if (is_integer ( $k ))59 isset ( $res [$k] ) ? $res [] = $v : $res [$k] = $v;60 elseif (is_array ( $v ) && isset ( $res [$k] ) && is_array ( $res [$k] ))61 $res [$k] = self::mergearray ( $res [$k], $v );62 else63 $res [$k] = $v;64 }65 }66 return $res;67 }68 public function setcomponents($components, $merge = true) {69 foreach ( $components as $id => $component )70 $this->setcomponent ( $id, $component, $merge );71 }72 //子类capplication调用,用来为模块指定配置73 public function configure($config) {74 if (is_array ( $config )) {75 foreach ( $config as $key => $value )76 $this->$key = $value;77 }78 }79 protected function preloadcomponents() {80 foreach ( $this->preload as $id )81 $this->getcomponent ( $id );82 }83 //又是两个钩子84 protected function preinit() {85 }86 protected function init() {87 }88 }
看到所谓的注册组件就是写入数组保存,getcomponent()的时候就是用前面讲到的yiibase里面的createcomponent($config)返回组件实例。就这么简单。
而capplication里面的什么getdb(),geturlmanager()也是在调用getcomponent()。
http://www.bkjia.com/phpjc/909083.htmlwww.bkjia.comtruehttp://www.bkjia.com/phpjc/909083.htmltecharticleyii源码分析1,yii源码分析 转载请注明:theviperhttp://www.cnblogs.com/theviper/ 本文将对yii中的mvc,路由器,filter,组件机制等最主要的部分进行自己...