• 技术文章 >后端开发 >php教程

    yii框架源码分析(一)

    2016-08-08 09:33:26原创980
    yii框架源码分析(一)

    本文将对yii中的mvc,路由器,filter,组件机制等最主要的部分进行自己的一点浅析,力求说明自己做一个php mvc不是那么的遥不可及,其实是很简单的。

    源码基于yii 1.13,为了方便说明,我对其进行了大量的裁剪,不过还是让他保有上面的那些最重要的功能。裁剪下来,其实没有几个文件了,而且每个文件代码最多100多行,避免因为代码太多而懒得看。

    所谓的mvc都是让所有请求从一个地方进去,通过对请求,配置的解析,分发到对应的类方法中。

    首先当然是入口文件,index.php.

    1 php 2$app = "app"; 3$yii = dirname ( __FILE__ ) . '/framework/yii.php'; 4$config = dirname ( __FILE__ ) . '/app/protected/config/main.php';//载入配置5require_once ($yii); 6 Yii::createWebApplication ( $config )->run ();

    引入yii.php

    1 php 2define ( "VIEWS_DIR", "$app/protected/views/" ); 3define ( "CONTROLLERS_DIR", "$app/protected/controllers/" ); 4require(dirname(__FILE__).'/YiiBase.php'); 5class Yii extends YiiBase 6{ 7 }

    原来yii是个空的类啊,去看YiiBase.

     1 php  2defined ( 'YII_PATH' ) or define ( 'YII_PATH', dirname ( __FILE__ ) );  3class YiiBase {  4publicstatic$classMap = array ();  5publicstatic$enableIncludePath = true;  6privatestatic$_aliases = array (  7 'system' => YII_PATH  8 ); // alias => path 9privatestatic$_imports = array (); // alias => class name or directory 10privatestatic$_includePaths; // list of include paths 11privatestatic$_app;  12privatestatic$_logger;  13publicstaticfunction createWebApplication($config = null) {  14return self::createApplication ( 'CWebApplication', $config );  15 }  16publicstaticfunction createApplication($class, $config = null) {  17returnnew$class ( $config );  18 }  19publicstaticfunction app() {  20return self::$_app;  21 }  22//别名路径 23publicstaticfunction getPathOfAlias($alias) {  24if (isset ( self::$_aliases [$alias] ))  25return self::$_aliases [$alias];  26elseif (($pos = strpos ( $alias, '.' )) !== false) {  27$rootAlias = substr ( $alias, 0, $pos );  28if (isset ( self::$_aliases [$rootAlias] ))  29return self::$_aliases [$alias] = rtrim ( self::$_aliases [$rootAlias] . DIRECTORY_SEPARATOR . str_replace ( '.', DIRECTORY_SEPARATOR, substr ( $alias, $pos + 1 ) ), '*' . DIRECTORY_SEPARATOR );  30 }  31returnfalse;  32 }  33publicstaticfunction setPathOfAlias($alias, $path) {  34if (empty ( $path ))  35unset ( self::$_aliases [$alias] );  36else 37 self::$_aliases [$alias] = rtrim ( $path, '\\/' );  38 }  39publicstaticfunction setApplication($app) {  40if (self::$_app === null || $app === null)  41 self::$_app = $app;  42 }  43publicstaticfunction import($alias, $forceInclude = false) {  44if (isset ( self::$_imports [$alias] )) // previously imported 45return self::$_imports [$alias];  46 47if (class_exists ( $alias, false ) || interface_exists ( $alias, false ))  48return self::$_imports [$alias] = $alias;  49if (($pos = strrpos ( $alias, '.' )) === false) // a simple class name 50 {  51// try to autoload the class with an autoloader if $forceInclude is true 52if ($forceInclude && (Yii::autoload ( $alias, true ) || class_exists ( $alias, true )))  53 self::$_imports [$alias] = $alias;  54return$alias;  55 }  56 57$className = ( string ) substr ( $alias, $pos + 1 );  58$isClass = $className !== '*';  59 60if ($isClass && (class_exists ( $className, false ) || interface_exists ( $className, false )))  61return self::$_imports [$alias] = $className;  62 63if (($path = self::getPathOfAlias ( $alias )) !== false) {  64if ($isClass) {  65if ($forceInclude) {  66if (is_file ( $path . '.php' ))  67require ($path . '.php');  68else 69thrownew 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';  75return$className;  76 } else// a directory 77 {  78if (self::$_includePaths === null) {  79 self::$_includePaths = array_unique ( explode ( PATH_SEPARATOR, get_include_path () ) );  80if (($pos = array_search ( '.', self::$_includePaths, true )) !== false)  81unset ( self::$_includePaths [$pos] );  82 }  83 84array_unshift ( self::$_includePaths, $path );  85 86if (self::$enableIncludePath && set_include_path ( '.' . PATH_SEPARATOR . implode ( PATH_SEPARATOR, self::$_includePaths ) ) === false)  87 self::$enableIncludePath = false;  88 89return self::$_imports [$alias] = $path;  90 }  91 }  92 }  93//创建组件实例 94publicstaticfunction createComponent($config) {  95if (is_string ( $config )) {  96$type = $config;  97$config = array ();  98 } elseif (isset ( $config ['class'] )) {  99$type = $config ['class']; 100unset ( $config ['class'] ); 101 } 102if (! class_exists ( $type, false )) { 103$type = Yii::import ( $type, true ); 104 } 105if (($n = func_num_args ()) > 1) { 106$args = func_get_args (); 107if ($n === 2) 108$object = new$type ( $args [1] ); 109elseif ($n === 3) 110$object = new$type ( $args [1], $args [2] ); 111elseif ($n === 4) 112$object = new$type ( $args [1], $args [2], $args [3] ); 113else { 114unset ( $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 (); 125foreach ( $configas$key => $value ) 126$object->$key = $value; 127128return$object; 129 } 130//按需加载相应的php131publicstaticfunction autoload($className) { 132include self::$_coreClasses [$className]; 133 } 134privatestatic$_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} 152153 spl_autoload_register ( array ( 154 'YiiBase', 155 'autoload' 156 ) );

    看似很多,其实就三个地方注意下就可以了

    1.spl_autoload_register,用这个就可以实现传说中的按需加载相应的php了,坑爹啊。

    2.createComponent($config)这个方法是yii组件调用的核心。在配置中注册的所有组件都是通过它获取组件类的实例的。比如配置:

     1 php  2returnarray (  3 'basePath' => 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 php  2class CWebApplication extends CApplication {  3public$controllerNamespace;  4private$_controllerPath;  5private$_viewPath;  6private$_systemViewPath;  7private$_controller;  8public$controllerMap=array();  9publicfunction processRequest() {//开始执行请求 10 //获取urlManager组件,解析请求,得到controller/action这种格式的string, 11 //并且将隐藏参数与请求的参数一一对应,匹配起来,写入$_REQUEST中12$route = $this->getUrlManager ()->parseUrl ($this->getRequest()); 13$this->runController ( $route ); 14 } 15publicfunction getRequest() {//获取request组件16return$this->getComponent ( 'request' ); 17 } 18protectedfunction registerCoreComponents() {//注册核心组件19 parent::registerCoreComponents (); 20 } 21//执行contronller22publicfunction runController($route) { 23if (($ca = $this->createController ( $route )) !== null) { 24list ( $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 33publicfunction createController($route, $owner = null) { 34if ($owner === null) 35$owner = $this; 36if (($route = trim ( $route, '//m.sbmmt.com/m/' )) === '') 37$route = $owner->defaultController; 3839$route .= '//m.sbmmt.com/m/'; 40while ( ($pos = strpos ( $route, '//m.sbmmt.com/m/' )) !== false ) { 41$id = substr ( $route, 0, $pos ); 42if (! preg_match ( '/^\w+$/', $id )) 43returnnull; 44$id = strtolower ( $id ); 45$route = ( string ) substr ( $route, $pos + 1 ); 46if (! isset ( $basePath )) // first segment47 { 48$basePath = $owner->getControllerPath (); 49$controllerID = ''; 50 } else { 51$controllerID .= '//m.sbmmt.com/m/'; 52 } 53$className = ucfirst ( $id ) . 'Controller'; 54$classFile = $basePath . DIRECTORY_SEPARATOR . $className . '.php'; 5556if (is_file ( $classFile )) { 57if (! class_exists ( $className, false )) 58require ($classFile); 59if (class_exists ( $className, false ) && is_subclass_of ( $className, 'CController' )) { 60$id [0] = strtolower ( $id [0] ); 61returnarray ( 62new$className ( $controllerID . $id, $owner === $this ? null : $owner ), 63$this->parseActionParams ( $route ) 64 ); 65 } 66returnnull; 67 } 68$controllerID .= $id; 69$basePath .= DIRECTORY_SEPARATOR . $id; 70 } 71 } 72protectedfunction parseActionParams($pathInfo) { 73if (($pos = strpos ( $pathInfo, '//m.sbmmt.com/m/' )) !== false) { 74$manager = $this->getUrlManager ();//再次获取urlManager,在上面第一次调用中已经导入。75$manager->parsePathInfo ( ( string ) substr ( $pathInfo, $pos + 1 ) ); 76$actionID = substr ( $pathInfo, 0, $pos ); 77return$manager->caseSensitive ? $actionID : strtolower ( $actionID ); 78 } else79return$pathInfo; 80 } 81publicfunction getControllerPath() { 82if ($this->_controllerPath !== null) 83return$this->_controllerPath; 84else85return$this->_controllerPath = $this->getBasePath () . DIRECTORY_SEPARATOR . 'controllers'; 86 } 87//两个钩子,子类去实现88publicfunction beforeControllerAction($controller, $action) { 89returntrue; 90 } 91publicfunction afterControllerAction($controller, $action) { 92 } 93protectedfunction init() { 94 parent::init (); 95 } 96 }

    没有构造方法,构造方法在父类CApplication里面。

     1 php  2abstractclass CApplication extends CModule {  3private$_id;  4private$_basePath;  5abstractpublicfunction processRequest();  6publicfunction __construct($config = null) {  7if (is_string ( $config ))  8$config = require ($config);  9 Yii::setApplication ( $this );//保存整个app实例10if (isset ( $config ['basePath'] )) { 11$this->setBasePath ( $config ['basePath'] ); 12unset ( $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 } 27protectedfunction registerCoreComponents() { 28$components = array ( 29 'request' => array ( 30 'class' => 'CHttpRequest' 31 ), 32 'urlManager' => array ( 33 'class' => 'CUrlManager' 34 ) 35 ); 3637$this->setComponents ( $components );//父类实现38 } 39publicfunction run() { 40$this->processRequest (); 41 } 42publicfunction getId() { 43if ($this->_id !== null) 44return$this->_id; 45else46return$this->_id = sprintf ( '%x', crc32 ( $this->getBasePath () . $this->name ) ); 47 } 48publicfunction setId($id) { 49$this->_id = $id; 50 } 51publicfunction getBasePath() { 52return$this->_basePath; 53 } 54publicfunction setBasePath($path) { 55if (($this->_basePath = realpath ( $path )) === false || ! is_dir ( $this->_basePath )) 56return; 57 } 58publicfunction getDb() { 59return$this->getComponent ( 'db' );//父类实现60 } 61publicfunction getUrlManager() { 62return$this->getComponent ( 'urlManager' ); 63 } 64publicfunction getController() { 65returnnull; 66 } 67publicfunction getBaseUrl($absolute = false) { 68return$this->getRequest ()->getBaseUrl ( $absolute ); 69 } 70 }

    __construct里面注释写的很详细了,值得注意的是registerCoreComponents ()。前面说了那么多,那么Yii::createWebApplication ( $config )到底是做什么的。

    其实最终目的对这个裁剪过的yii而言就是注册核心组件。就这么简单.

    setComponents ( $components )在父类CModule里面.

     1 php  2abstractclass CModule extends CComponent {  3public$preload = array ();  4public$behaviors = array ();  5private$_id;  6private$_parentModule;  7private$_basePath;  8private$_modulePath;  9private$_params; 10private$_modules = array (); 11private$_moduleConfig = array (); 12private$_components = array (); 13private$_componentConfig = array (); 14//重写是为了方便直接用 application.组件 这种方式直接获取组件15publicfunction __get($name) { 16if ($this->hasComponent ( $name )) 17return$this->getComponent ( $name ); 18else19return parent::__get ( $name ); 20 } 21publicfunction __isset($name) { 22if ($this->hasComponent ( $name )) 23return$this->getComponent ( $name ) !== null; 24else25return parent::__isset ( $name ); 26 } 27publicfunction hasComponent($id) { 28returnisset ( $this->_components [$id] ) || isset ( $this->_componentConfig [$id] ); 29 } 30// 31publicfunction getComponent($id, $createIfNull = true) { 32if (isset ( $this->_components [$id] )) 33return$this->_components [$id]; 34elseif (isset ( $this->_componentConfig [$id] ) && $createIfNull) { 35$config = $this->_componentConfig [$id]; 36$component = Yii::createComponent ( $config );//YiiBase,返回组件实例37$component->init ();//钩子,调用子类重写的init方法 38 //将组件写入数组保存,并返回39return$this->_components [$id] = $component; 40 } 41 } 42publicfunction setComponent($id, $component, $merge = true) { 43//组件写入数组保存44if (isset ( $this->_componentConfig [$id] ) && $merge) { 4546$this->_componentConfig [$id] = self::mergeArray ( $this->_componentConfig [$id], $component ); 47 } else { 4849$this->_componentConfig [$id] = $component; 50 } 51 } 52publicstaticfunction mergeArray($a, $b) { 53$args = func_get_args (); 54$res = array_shift ( $args ); 55while ( ! empty ( $args ) ) { 56$next = array_shift ( $args ); 57foreach ( $nextas$k => $v ) { 58if (is_integer ( $k )) 59isset ( $res [$k] ) ? $res [] = $v : $res [$k] = $v; 60elseif (is_array ( $v ) && isset ( $res [$k] ) && is_array ( $res [$k] )) 61$res [$k] = self::mergeArray ( $res [$k], $v ); 62else63$res [$k] = $v; 64 } 65 } 66return$res; 67 } 68publicfunction setComponents($components, $merge = true) { 69foreach ( $componentsas$id => $component ) 70$this->setComponent ( $id, $component, $merge ); 71 } 72//子类CApplication调用,用来为模块指定配置73publicfunction configure($config) { 74if (is_array ( $config )) { 75foreach ( $configas$key => $value ) 76$this->$key = $value; 77 } 78 } 79protectedfunction preloadComponents() { 80foreach ( $this->preload as$id ) 81$this->getComponent ( $id ); 82 } 83//又是两个钩子84protectedfunction preinit() { 85 } 86protectedfunction init() { 87 } 88 }

    看到所谓的注册组件就是写入数组保存,getComponent()的时候就是用前面讲到的YiiBase里面的createComponent($config)返回组件实例。就这么简单。

    而CApplication里面的什么getDb(),getUrlManager()也是在调用getComponent()。

    最后附上,裁剪的yii http://files.cnblogs.com/TheViper/framework.zip

    下一篇

    以上就介绍了yii框架源码分析(一),包括了yii框架源码方面的内容,希望对PHP教程有兴趣的朋友有所帮助。

    声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。

    前端(VUE)零基础到就业课程:点击学习

    清晰的学习路线+老师随时辅导答疑

    自己动手写 PHP MVC 框架:点击学习

    快速了解MVC架构、了解框架底层运行原理

    上一篇:真正面向对象编程:PHP5.01 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • ❤️‍🔥共22门课程,总价3725元,会员免费学• ❤️‍🔥接口自动化测试不想写代码?• 工具包分享:PHP实现滑块验证图片• PHP中的SimpleXML处理_PHP教程• 通过PHP修改Linux或Unix口令的方法分享_PHP教程• PHP header函数分析详解_PHP教程• php学习笔记 面向对象的构造与析构方法_PHP教程
    1/1

    PHP中文网