无聊,决定水一把。
CI(CodeIgniter)是我最早接触的一个框架,到现在也只是用了其中一点零碎的方法。一直想对其流程做个小结,却总是因各种各样的“理由”挨着。看见别人图表齐上阵,没那耐心,就从代码说起吧,权当做个笔记,纪念一下。
看在线的用户手册,也知道,将CI下载下来(最新版本2.2.1),解压到机子上,比如www目录,可改个根目录名(原名CodeIgniter-2.2-stable太长),初步目录文件如下,当然这在是windows下面。
访问下,如localhost/ci/index.php,就进入CI默认的Welcome页面
如何一步步加载这个页面的?首先访问的是index.php脚本

1 <?php 2 3 /* 4 *--------------------------------------------------------------- 5 * APPLICATION ENVIRONMENT 6 *--------------------------------------------------------------- 7 * 8 * You can load different configurations depending on your 9 * current environment. Setting the environment also influences 10 * things like logging and error reporting. 11 * 12 * This can be set to anything, but default usage is: 13 * 14 * development 15 * testing 16 * production 17 * 18 * NOTE: If you change these, also change the error_reporting() code below 19 * 20 */ 21 define('ENVIRONMENT', 'development'); 22 /* 23 *--------------------------------------------------------------- 24 * ERROR REPORTING 25 *--------------------------------------------------------------- 26 * 27 * Different environments will require different levels of error reporting. 28 * By default development will show errors but testing and live will hide them. 29 */ 30 31 if (defined('ENVIRONMENT')) 32 { 33 switch (ENVIRONMENT) 34 { 35 case 'development': 36 error_reporting(E_ALL); 37 break; 38 39 case 'testing': 40 case 'production': 41 error_reporting(0); 42 break; 43 44 default: 45 exit('The application environment is not set correctly.'); 46 } 47 } 48 49 /* 50 *--------------------------------------------------------------- 51 * SYSTEM FOLDER NAME 52 *--------------------------------------------------------------- 53 * 54 * This variable must contain the name of your "system" folder. 55 * Include the path if the folder is not in the same directory 56 * as this file. 57 * 58 */ 59 $system_path = 'system'; 60 61 /* 62 *--------------------------------------------------------------- 63 * APPLICATION FOLDER NAME 64 *--------------------------------------------------------------- 65 * 66 * If you want this front controller to use a different "application" 67 * folder then the default one you can set its name here. The folder 68 * can also be renamed or relocated anywhere on your server. If 69 * you do, use a full server path. For more info please see the user guide: 70 * http://codeigniter.com/user_guide/general/managing_apps.html 71 * 72 * NO TRAILING SLASH! 73 * 74 */ 75 $application_folder = 'application'; 76 77 /* 78 * -------------------------------------------------------------------- 79 * DEFAULT CONTROLLER 80 * -------------------------------------------------------------------- 81 * 82 * Normally you will set your default controller in the routes.php file. 83 * You can, however, force a custom routing by hard-coding a 84 * specific controller class/function here. For most applications, you 85 * WILL NOT set your routing here, but it's an option for those 86 * special instances where you might want to override the standard 87 * routing in a specific front controller that shares a common CI installation. 88 * 89 * IMPORTANT: If you set the routing here, NO OTHER controller will be 90 * callable. In essence, this preference limits your application to ONE 91 * specific controller. Leave the function name blank if you need 92 * to call functions dynamically via the URI. 93 * 94 * Un-comment the $routing array below to use this feature 95 * 96 */ 97 // The directory name, relative to the "controllers" folder. Leave blank 98 // if your controller is not in a sub-folder within the "controllers" folder 99 // $routing['directory'] = ''; 100 101 // The controller class file name. Example: Mycontroller 102 // $routing['controller'] = ''; 103 104 // The controller function you wish to be called. 105 // $routing['function'] = ''; 106 107 108 /* 109 * ------------------------------------------------------------------- 110 * CUSTOM CONFIG VALUES 111 * ------------------------------------------------------------------- 112 * 113 * The $assign_to_config array below will be passed dynamically to the 114 * config class when initialized. This allows you to set custom config 115 * items or override any default config values found in the config.php file. 116 * This can be handy as it permits you to share one application between 117 * multiple front controller files, with each file containing different 118 * config values. 119 * 120 * Un-comment the $assign_to_config array below to use this feature 121 * 122 */ 123 // $assign_to_config['name_of_config_item'] = 'value of config item'; 124 125 126 127 // -------------------------------------------------------------------- 128 // END OF USER CONFIGURABLE SETTINGS. DO NOT EDIT BELOW THIS LINE 129 // -------------------------------------------------------------------- 130 131 /* 132 * --------------------------------------------------------------- 133 * Resolve the system path for increased reliability 134 * --------------------------------------------------------------- 135 */ 136 137 // Set the current directory correctly for CLI requests 138 if (defined('STDIN')) 139 { 140 chdir(dirname(__FILE__)); 141 } 142 143 if (realpath($system_path) !== FALSE) 144 { 145 $system_path = realpath($system_path).'/'; 146 } 147 148 // ensure there's a trailing slash 149 $system_path = rtrim($system_path, '/').'/'; 150 151 // Is the system path correct? 152 if ( ! is_dir($system_path)) 153 { 154 exit("Your system folder path does not appear to be set correctly. Please open the following file and correct this: ".pathinfo(__FILE__, PATHINFO_BASENAME)); 155 } 156 157 /* 158 * ------------------------------------------------------------------- 159 * Now that we know the path, set the main path constants 160 * ------------------------------------------------------------------- 161 */ 162 // The name of THIS file 163 define('SELF', pathinfo(__FILE__, PATHINFO_BASENAME)); 164 165 // The PHP file extension 166 // this global constant is deprecated. 167 define('EXT', '.php'); 168 169 // Path to the system folder 170 define('BASEPATH', str_replace("\\", "/", $system_path)); 171 172 // Path to the front controller (this file) 173 define('FCPATH', str_replace(SELF, '', __FILE__)); 174 175 // Name of the "system folder" 176 define('SYSDIR', trim(strrchr(trim(BASEPATH, '/'), '/'), '/')); 177 178 179 // The path to the "application" folder 180 if (is_dir($application_folder)) 181 { 182 define('APPPATH', $application_folder.'/'); 183 } 184 else 185 { 186 if ( ! is_dir(BASEPATH.$application_folder.'/')) 187 { 188 exit("Your application folder path does not appear to be set correctly. Please open the following file and correct this: ".SELF); 189 } 190 191 define('APPPATH', BASEPATH.$application_folder.'/'); 192 } 193 194 /* 195 * -------------------------------------------------------------------- 196 * LOAD THE BOOTSTRAP FILE 197 * -------------------------------------------------------------------- 198 * 199 * And away we go... 200 * 201 */ 202 require_once BASEPATH.'core/CodeIgniter.php'; 203 204 /* End of file index.php */ 205 /* Location: ./index.php */
21行:首先定义一个ENVIRONMENT常量为development,即开发环境。
31-47行:switch语句,由于当前环境是development,所以是设置报告所有级别的错误。
49-59行:$system_path变量定义CI的默认的系统脚本目录是 system,61-75行定义当前默认的供我们主要开发用的目录为 application。
77-105行:全部注释掉了,这里是我们可以强制设置系统加载时默认的目录名($routing['directory'])、控制器名($routing['directory'])和方法名($routing['directory']),虽然一般这些是设置在application\config\routes.php中(下图),访问的Welcome页面也是通过这个默认控制器Welcome类进行的,这里只是作为一个选择性的方式,其实没必要弄
108-129行:全部注释掉,用于自定义配置变量(CUSTOM CONFIG VALUES),前一篇说过,任何后端project中,总有些配置信息,只是各个项目或框架加载方式不同,这个$assign_to_config数组就存放我们的自定义配置信息,如$assign_to_config['home'] = 'localhost'; ,之所以注释掉,又是因为这只是一个可选的操作,CI的用户自定义配置信息,一般放在application\config目录下边,以自动加载信息(autoload.php),普通配置信息(config.php)、常量(constants.php)、数据库(database.php)等分开文件存储,所以一般不会在这里的去配置一个要用到的变量,$assign_to_config默认是没有定义的。
从131行到index.php文件末尾主要是对一些路径变量的定义。
137-141行:是为CLI(Command-Interface Line)的调用方式准备的,是直接在Mac/Linux系统上通过终端命令运行脚本,这个在CI中文官网(http://codeigniter.org.cn/user_guide/general/cli.html)也有介绍,如果定义了名为STDIN的常量,则将执行目录改为当前文件所在目录,当然前面没有出现过STDIN这个常量的定义,这里就不会执行了。
143-155行:确定框架存放系统脚本的目录变量$system_path,也就是前面图中的system目录,这里会检测它的有效性,无效的话程序就挂在这里了。
157-192行:定义若干主要目录常量,分别是SELF:当前脚本的文件名、EXT:脚本扩展名、BASEPATH:system目录的路径、FCPATH:当前脚本所在的目录、SYSDIR:system目录的目录名,不改动的话就是system。
179-194行:定义APPPATH常量,确定application所在的目录,就是以后我们主要开发的地方,使用is_dir检测,稍微注意的是is_dir可以检测相对目录,所以实际运行的是if里边的代码,APPPATH得到的是相对路径。
最后打印看看这些变(常)量的值都是啥,有的与存放目录相关:
202行:加载BASEPATH.'core/CodeIgniter.php'脚本,就是system目录下的核心类文件目录下的文件,进入到CI的核心类目录下的文件了。
=====================================================================================================

1 <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 2 /** 3 * CodeIgniter 4 * 5 * An open source application development framework for PHP 5.1.6 or newer 6 * 7 * @package CodeIgniter 8 * @author EllisLab Dev Team 9 * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. 10 * @copyright Copyright (c) 2014 - 2015, British Columbia Institute of Technology (http://bcit.ca/) 11 * @license http://codeigniter.com/user_guide/license.html 12 * @link http://codeigniter.com 13 * @since Version 1.0 14 * @filesource 15 */ 16 17 // ------------------------------------------------------------------------ 18 19 /** 20 * System Initialization File 21 * 22 * Loads the base classes and executes the request. 23 * 24 * @package CodeIgniter 25 * @subpackage codeigniter 26 * @category Front-controller 27 * @author EllisLab Dev Team 28 * @link http://codeigniter.com/user_guide/ 29 */ 30 31 /** 32 * CodeIgniter Version 33 * 34 * @var string 35 * 36 */ 37 define('CI_VERSION', '2.2.1'); 38 39 /** 40 * CodeIgniter Branch (Core = TRUE, Reactor = FALSE) 41 * 42 * @var boolean 43 * 44 */ 45 define('CI_CORE', FALSE); 46 47 /* 48 * ------------------------------------------------------ 49 * Load the global functions 50 * ------------------------------------------------------ 51 */ 52 require(BASEPATH.'core/Common.php'); 53 54 /* 55 * ------------------------------------------------------ 56 * Load the framework constants 57 * ------------------------------------------------------ 58 */ 59 if (defined('ENVIRONMENT') AND file_exists(APPPATH.'config/'.ENVIRONMENT.'/constants.php')) 60 { 61 require(APPPATH.'config/'.ENVIRONMENT.'/constants.php'); 62 } 63 else 64 { 65 require(APPPATH.'config/constants.php'); 66 } 67 68 /* 69 * ------------------------------------------------------ 70 * Define a custom error handler so we can log PHP errors 71 * ------------------------------------------------------ 72 */ 73 set_error_handler('_exception_handler'); 74 75 if ( ! is_php('5.3')) 76 { 77 @set_magic_quotes_runtime(0); // Kill magic quotes 78 } 79 80 /* 81 * ------------------------------------------------------ 82 * Set the subclass_prefix 83 * ------------------------------------------------------ 84 * 85 * Normally the "subclass_prefix" is set in the config file. 86 * The subclass prefix allows CI to know if a core class is 87 * being extended via a library in the local application 88 * "libraries" folder. Since CI allows config items to be 89 * overriden via data set in the main index. php file, 90 * before proceeding we need to know if a subclass_prefix 91 * override exists. If so, we will set this value now, 92 * before any classes are loaded 93 * Note: Since the config file data is cached it doesn't 94 * hurt to load it here. 95 */ 96 if (isset($assign_to_config['subclass_prefix']) AND $assign_to_config['subclass_prefix'] != '') 97 { 98 get_config(array('subclass_prefix' => $assign_to_config['subclass_prefix'])); 99 } 100 101 /* 102 * ------------------------------------------------------ 103 * Set a liberal script execution time limit 104 * ------------------------------------------------------ 105 */ 106 if (function_exists("set_time_limit") == TRUE AND @ini_get("safe_mode") == 0) 107 { 108 @set_time_limit(300); 109 } 110 111 /* 112 * ------------------------------------------------------ 113 * Start the timer... tick tock tick tock... 114 * ------------------------------------------------------ 115 */ 116 $BM =& load_class('Benchmark', 'core'); 117 $BM->mark('total_execution_time_start'); 118 $BM->mark('loading_time:_base_classes_start'); 119 120 /* 121 * ------------------------------------------------------ 122 * Instantiate the hooks class 123 * ------------------------------------------------------ 124 */ 125 $EXT =& load_class('Hooks', 'core'); 126 127 /* 128 * ------------------------------------------------------ 129 * Is there a "pre_system" hook? 130 * ------------------------------------------------------ 131 */ 132 $EXT->_call_hook('pre_system'); 133 134 /* 135 * ------------------------------------------------------ 136 * Instantiate the config class 137 * ------------------------------------------------------ 138 */ 139 $CFG =& load_class('Config', 'core'); 140 141 // Do we have any manually set config items in the index.php file? 142 if (isset($assign_to_config)) 143 { 144 $CFG->_assign_to_config($assign_to_config); 145 } 146 147 /* 148 * ------------------------------------------------------ 149 * Instantiate the UTF-8 class 150 * ------------------------------------------------------ 151 * 152 * Note: Order here is rather important as the UTF-8 153 * class needs to be used very early on, but it cannot 154 * properly determine if UTf-8 can be supported until 155 * after the Config class is instantiated. 156 * 157 */ 158 159 $UNI =& load_class('Utf8', 'core'); 160 161 /* 162 * ------------------------------------------------------ 163 * Instantiate the URI class 164 * ------------------------------------------------------ 165 */ 166 $URI =& load_class('URI', 'core'); 167 168 /* 169 * ------------------------------------------------------ 170 * Instantiate the routing class and set the routing 171 * ------------------------------------------------------ 172 */ 173 $RTR =& load_class('Router', 'core'); 174 $RTR->_set_routing(); 175 176 // Set any routing overrides that may exist in the main index file 177 if (isset($routing)) 178 { 179 $RTR->_set_overrides($routing); 180 } 181 182 /* 183 * ------------------------------------------------------ 184 * Instantiate the output class 185 * ------------------------------------------------------ 186 */ 187 $OUT =& load_class('Output', 'core'); 188 189 /* 190 * ------------------------------------------------------ 191 * Is there a valid cache file? If so, we're done... 192 * ------------------------------------------------------ 193 */ 194 if ($EXT->_call_hook('cache_override') === FALSE) 195 { 196 if ($OUT->_display_cache($CFG, $URI) == TRUE) 197 { 198 exit; 199 } 200 } 201 202 /* 203 * ----------------------------------------------------- 204 * Load the security class for xss and csrf support 205 * ----------------------------------------------------- 206 */ 207 $SEC =& load_class('Security', 'core'); 208 209 /* 210 * ------------------------------------------------------ 211 * Load the Input class and sanitize globals 212 * ------------------------------------------------------ 213 */ 214 $IN =& load_class('Input', 'core'); 215 216 /* 217 * ------------------------------------------------------ 218 * Load the Language class 219 * ------------------------------------------------------ 220 */ 221 $LANG =& load_class('Lang', 'core'); 222 223 /* 224 * ------------------------------------------------------ 225 * Load the app controller and local controller 226 * ------------------------------------------------------ 227 * 228 */ 229 // Load the base controller class 230 require BASEPATH.'core/Controller.php'; 231 232 function &get_instance() 233 { 234 return CI_Controller::get_instance(); 235 } 236 237 238 if (file_exists(APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php')) 239 { 240 require APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php'; 241 } 242 243 // Load the local application controller 244 // Note: The Router class automatically validates the controller path using the router->_validate_request(). 245 // If this include fails it means that the default controller in the Routes.php file is not resolving to something valid. 246 if ( ! file_exists(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->fetch_class().'.php')) 247 { 248 show_error('Unable to load your default controller. Please make sure the controller specified in your Routes.php file is valid.'); 249 } 250 251 include(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->fetch_class().'.php'); 252 253 // Set a mark point for benchmarking 254 $BM->mark('loading_time:_base_classes_end'); 255 256 /* 257 * ------------------------------------------------------ 258 * Security check 259 * ------------------------------------------------------ 260 * 261 * None of the functions in the app controller or the 262 * loader class can be called via the URI, nor can 263 * controller functions that begin with an underscore 264 */ 265 $class = $RTR->fetch_class(); 266 $method = $RTR->fetch_method(); 267 268 if ( ! class_exists($class) 269 OR strncmp($method, '_', 1) == 0 270 OR in_array(strtolower($method), array_map('strtolower', get_class_methods('CI_Controller'))) 271 ) 272 { 273 if ( ! empty($RTR->routes['404_override'])) 274 { 275 $x = explode('/', $RTR->routes['404_override']); 276 $class = $x[0]; 277 $method = (isset($x[1]) ? $x[1] : 'index'); 278 if ( ! class_exists($class)) 279 { 280 if ( ! file_exists(APPPATH.'controllers/'.$class.'.php')) 281 { 282 show_404("{$class}/{$method}"); 283 } 284 285 include_once(APPPATH.'controllers/'.$class.'.php'); 286 } 287 } 288 else 289 { 290 show_404("{$class}/{$method}"); 291 } 292 } 293 294 /* 295 * ------------------------------------------------------ 296 * Is there a "pre_controller" hook? 297 * ------------------------------------------------------ 298 */ 299 $EXT->_call_hook('pre_controller'); 300 301 /* 302 * ------------------------------------------------------ 303 * Instantiate the requested controller 304 * ------------------------------------------------------ 305 */ 306 // Mark a start point so we can benchmark the controller 307 $BM->mark('controller_execution_time_( '.$class.' / '.$method.' )_start'); 308 309 $CI = new $class(); 310 311 /* 312 * ------------------------------------------------------ 313 * Is there a "post_controller_constructor" hook? 314 * ------------------------------------------------------ 315 */ 316 $EXT->_call_hook('post_controller_constructor'); 317 318 /* 319 * ------------------------------------------------------ 320 * Call the requested method 321 * ------------------------------------------------------ 322 */ 323 // Is there a "remap" function? If so, we call it instead 324 if (method_exists($CI, '_remap')) 325 { 326 $CI->_remap($method, array_slice($URI->rsegments, 2)); 327 } 328 else 329 { 330 // is_callable() returns TRUE on some versions of PHP 5 for private and protected 331 // methods, so we'll use this workaround for consistent behavior 332 if ( ! in_array(strtolower($method), array_map('strtolower', get_class_methods($CI)))) 333 { 334 // Check and see if we are using a 404 override and use it. 335 if ( ! empty($RTR->routes['404_override'])) 336 { 337 $x = explode('/', $RTR->routes['404_override']); 338 $class = $x[0]; 339 $method = (isset($x[1]) ? $x[1] : 'index'); 340 if ( ! class_exists($class)) 341 { 342 if ( ! file_exists(APPPATH.'controllers/'.$class.'.php')) 343 { 344 show_404("{$class}/{$method}"); 345 } 346 347 include_once(APPPATH.'controllers/'.$class.'.php'); 348 unset($CI); 349 $CI = new $class(); 350 } 351 } 352 else 353 { 354 show_404("{$class}/{$method}"); 355 } 356 } 357 358 // Call the requested method. 359 // Any URI segments present (besides the class/function) will be passed to the method for convenience 360 call_user_func_array(array(&$CI, $method), array_slice($URI->rsegments, 2)); 361 } 362 363 364 // Mark a benchmark end point 365 $BM->mark('controller_execution_time_( '.$class.' / '.$method.' )_end'); 366 367 /* 368 * ------------------------------------------------------ 369 * Is there a "post_controller" hook? 370 * ------------------------------------------------------ 371 */ 372 $EXT->_call_hook('post_controller'); 373 374 /* 375 * ------------------------------------------------------ 376 * Send the final rendered output to the browser 377 * ------------------------------------------------------ 378 */ 379 if ($EXT->_call_hook('display_override') === FALSE) 380 { 381 $OUT->_display(); 382 } 383 384 /* 385 * ------------------------------------------------------ 386 * Is there a "post_system" hook? 387 * ------------------------------------------------------ 388 */ 389 $EXT->_call_hook('post_system'); 390 391 /* 392 * ------------------------------------------------------ 393 * Close the DB connection if one exists 394 * ------------------------------------------------------ 395 */ 396 if (class_exists('CI_DB') AND isset($CI->db)) 397 { 398 $CI->db->close(); 399 } 400 401 402 /* End of file CodeIgniter.php */ 403 /* Location: ./system/core/CodeIgniter.php */
在CodeIgniter中,可以看到开头的英文描述,该脚本时系统初始化文件,主要作用是装载基类和执行请求。
31-45行:定义了CI_VERSION常量,描述当前框架版本,CI_CORE常量,目前我也不清楚没探究过,注释是CI的分支,啥意思?
52行:加载系统核心目录下的Common.php文件,Load the global functions,记得前一篇中说到,一般一个项目会将很多公共方法放在一个脚本中加载进来,通常取名Utilities.php,也可是Common.php,这里的Common.php也是这个意思,如它的解释是“加载全局函数”,即这里的函数都是后边直接拿来用的。在这个脚本中有两个重要的方法(目前来说)一个是get_config,单独拿出来如下

1 <?php 2 /** 3 * Loads the main config.php file 4 * 5 * This function lets us grab the config file even if the Config class 6 * hasn't been instantiated yet 7 * 8 * @access private 9 * @return array 10 */ 11 if ( ! function_exists('get_config')) 12 { 13 function &get_config($replace = array()) 14 { 15 static $_config; 16 17 if (isset($_config)) 18 { 19 return $_config[0]; 20 } 21 22 // Is the config file in the environment folder? 23 if ( ! defined('ENVIRONMENT') OR ! file_exists($file_path = APPPATH.'config/'.ENVIRONMENT.'/config.php')) 24 { 25 $file_path = APPPATH.'config/config.php'; 26 } 27 28 // Fetch the config file 29 if ( ! file_exists($file_path)) 30 { 31 exit('The configuration file does not exist.'); 32 } 33 34 require($file_path); 35 36 // Does the $config array exist in the file? 37 if ( ! isset($config) OR ! is_array($config)) 38 { 39 exit('Your config file does not appear to be formatted correctly.'); 40 } 41 42 // Are any values being dynamically replaced? 43 if (count($replace) > 0) 44 { 45 foreach ($replace as $key => $val) 46 { 47 if (isset($config[$key])) 48 { 49 $config[$key] = $val; 50 } 51 } 52 } 53 54 $_config[0] =& $config; 55 return $_config[0]; 56 } 57 }
注释说它加载主要的config.php文件,它使得我们能抓取到配置文件,即便配置类还未被实例化。在CI中,有专门的核心配置类CI_Config来加载配置信息,而这里的get_config方法也能获得主要配置信息,注意是主要配置信息,在application/config目录下有很多其他的配置信息文件(前面在自定义配置变量时也说过CI将配置信息分为了很多文件),其中有一个config.php文件就是get_config能获取到的,这个文件存放的就是基本信息,如果你还想获取其他的配置信息,貌似就要用配置类了。所以如果想添加节本配置信息就在这个里边。
如果是第一次调用get_config方法,先声明静态变量$_config,如果已定义则直接返回它的索引为0的子数组。然后查看APPPATH/config/ENVIRONMENT/config.php文件是否存在(前面打印已知ENVIRONMENT常量值,未改动就是development,原始的框架中没有这个目录,所以这里加载的是application/config/config.php(只加载了这一个,其他的配置文件没有),可以打开看看config.php中定义了一个$config数组,一些基本定义如基础链接、链接后缀、编码、语言、缓存、日志、钩子等等。如果传入一个关联数组,它会将键-值(临时)加入$_config中。总之,get_config方法主要得到的是config.php中定义的数组变量。
与get_config相关的config_item方法则是得到这个数组变量中的某一项。
另一个比较重要的方法是load_class:

1 <?php 2 /** 3 * Class registry 4 * 5 * This function acts as a singleton. If the requested class does not 6 * exist it is instantiated and set to a static variable. If it has 7 * previously been instantiated the variable is returned. 8 * 9 * @access public 10 * @param string the class name being requested 11 * @param string the directory where the class should be found 12 * @param string the class name prefix 13 * @return object 14 */ 15 if ( ! function_exists('load_class')) 16 { 17 function &load_class($class, $directory = 'libraries', $prefix = 'CI_') 18 { 19 static $_classes = array(); 20 21 // Does the class exist? If so, we're done... 22 if (isset($_classes[$class])) 23 { 24 return $_classes[$class]; 25 } 26 27 $name = FALSE; 28 29 // Look for the class first in the local application/libraries folder 30 // then in the native system/libraries folder 31 foreach (array(APPPATH, BASEPATH) as $path) 32 { 33 if (file_exists($path.$directory.'/'.$class.'.php')) 34 { 35 $name = $prefix.$class; 36 37 if (class_exists($name) === FALSE) 38 { 39 require($path.$directory.'/'.$class.'.php'); 40 } 41 42 break; 43 } 44 } 45 46 // Is the request a class extension? If so we load it too 47 if (file_exists(APPPATH.$directory.'/'.config_item('subclass_prefix').$class.'.php')) 48 { 49 $name = config_item('subclass_prefix').$class; 50 51 if (class_exists($name) === FALSE) 52 { 53 require(APPPATH.$directory.'/'.config_item('subclass_prefix').$class.'.php'); 54 } 55 } 56 57 // Did we find the class? 58 if ($name === FALSE) 59 { 60 // Note: We use exit() rather then show_error() in order to avoid a 61 // self-referencing loop with the Excptions class 62 exit('Unable to locate the specified class: '.$class.'.php'); 63 } 64 65 // Keep track of what we just loaded 66 is_loaded($class); 67 68 $_classes[$class] = new $name(); 69 return $_classes[$class]; 70 } 71 }
先看它的注释:这个方法作为一个单例,如果被请求的类没有出现过,则该类会被实例化为一个static variable,如果先前被实例化过则直接返回它。它的三个参数分别是请求的类名、所在目录,类名前缀。可以看到,目录默认是libraries,在application和system中均有它,它就是存放我们自定义的类库或者CI自带的类库的地方,就是自定义工具和CI提供的工具,如日历类、加密类、Ftp类、日志类、Session会话类、Email邮件收发类、JavaScript类、ZIP压缩类等等。或许你已经注意到这里返回的是引用而非值,就像它将加载的类作为静态变量一样,这些细节地方最终提高了整个系统的访问速度。
大致流程:先定义一个静态数组,若数组中已有该类直接返回。先后扫描APPPATH和BASEPATH(前面已知这俩常量值)文件夹下的$directory(默认值是libraries)目录下的$class.php文件是否存在,存在则加上CI的标准类前缀CI_(第三个参数的默认值),在检查类存在与否,存在则require该文件(class_exists()先判断类是否存,存在时就不加载该类文件),一旦文件出现则加载它,并break跳出。注意扫描顺序,先APPPATH后BASEPATH,假如只传第一个参数类名,则优先在我们自己开发的application目录libraries中寻找,然后才去system目录的libraries下边。
由于我们可以对CI的核心类进行扩展(继承它们),所以在扫描完APPPATH和BASEPATH的核心类(名称以CI_为前缀)目录后,还要扫描APPPATH的libraries下边是否有自定义的扩展类(默认以MY_为前缀),有的话也要加载它们,然后实例化一个对应对象(有扩展类是扩展类)存入$_classes静态数组并返回该对象。
对Common.php有大致了解后回到CodeIgniter.php脚本。
54-66行:加载APPPATH.'config/constants.php'脚本,constants.php如同名字一样放的是framework constants,集中定义了一些常量,所以我们在添加常量时就可以放到这里边来定义。
68-78行:首先定义了一个自定义错误处理方法_exception_handler。判断php版本,非5.3关闭magic_quotes引用,这个配置在5.3版本已弃用,提高安全性。
80-99行:这里就是将前面说过的$assign_to_config自定义配置信息数组临时加到$_config数组中,通过get_config方法实现,前面说过$assign_to_config默认是没有定义的,这里的if语句也不会运行。
101-109行:设置自定义脚本最大执行时间为300秒(略长,跑日志的话得更长)
111-118行:加载核心类Benchmark,设置两个标记点。Benchmark基准测试类,就是测试某个开始标记到结束标记之间占用的内存大小、执行时间等信息,测试嘛,当然它要结合CI中一个叫分析器的东西使用。
120-132行:加载核心类Hooks,钩子,设置了一个系统开始执行的钩子(实际未执行,因为application/config/config.php关于它的配置信息默认设置为false,即不启用钩子)。它就就相当于一个触发器,在某个东西要执行前开始执行某些代码,比如控制器加载前、加载后等,一旦控制器加载就运行指定的代码。在这里,它尝试调用一个pre_system(系统执行前)的扩展,默认不执行。
134-145行:加载核心类Config,配置类,它用来加载其他需需要的配置信息,并且它再次加载$assign_to_config数组中配置信息如果该数组定义了的话。
147-159行:加载核心类Utf8,编码类。
161-166行:加载核心类URI,路由。
168-180行:加载核心类Router,路径处理类,_set_routing方法设置好访问路径。如果路径配置数组$routing(前面提到默认是注释掉的)定义了的话,将覆盖默认的路由配置。如果你输入了不存在的脚本路径,在这一步就停住,开始报404了,当然还得Router里边的方法处理。
Router类里面,URI作为它的一个成员存在,实际处理方法在URI类中,熟悉点的都知道CI的访问方式默认是段(segment)的形式,据说更有利于搜索引擎。一个简单的访问方式是这样的localhost/ci/index.php/Controller/Function/Arguments,它们将访问的形式解析为需要的控制器,调用的方法,以及提供的参数列表,当然也可启用传统的查询字符串形式。具体方法略复杂。
187行:加载核心类Output。
189-200行:通过Hooks类和Output类检测有无缓存,有的话直接输出缓存页面,跳出脚本了。这也是在CI的介绍中应用程序流程图部分,当路径处理完后,若有缓存直接输出的原因。
207行:加载核心类Security。
214行:加载核心类Input。
221行:加载核心类Lang,语言处理。
229-235行:加载核心类Controller,它是所有控制器的基类,而get_instance全局方法也能得到它的实例,Controller的牛逼之处在于,它将前面所有通过load_calss载入的libraries(默认)目录(APPPATH和BASEPATH)中的工具库全部实例化为对象,并作为它的属性成员。所以这里的get_instance方法得到的实例也被CI称为超级对象(super object),因为通过这个对象就可以获取所有通过前面加载的对象实例。
238-242行:加载自定义的,对上一步的核心类CI_Controller的扩展类的文件,默认就是MY_Controller,当然前提是如果你扩展了的的话。
243-251行:通过核心类Router的实例,提取当前访问的控制器所在的目录和类名,不存在则报错,存在则加载它,这里就加载了默认的welcome控制器文件。当然如果你自己定义了控制器类文件并访问,也是在这里被include进来的(通过Router类提取子目录$RTR->fetch_directory(),若存在,提取类名$RTR->fetch_class()来找),大概在246行的if语句块,就是检查这个类文件是否存在。
252行:设置一个基准测试结束标记,标记加载基本核心类结束(这些测试默认不会执行)。
256-292行:安全检查。先通过Router类取得类名和要执行的方法名,if条件检查3项内容。1. 上面的243-251行是找到了控制器对应的脚本,并且加载了它,但是假如这只是一个名字匹配的空脚本呢?里边什么都没写就不行了,于是要检查类的定义是否存在(class_exists),2. 以下划线_开头的方法名不能执行,直接报错,当然这是CI自己的的规则,也就是说无论你的类定义的以_开头的方法即使是公有访问属性也不行(除了一个_remap),3. 当类中的方法根控制器核心类中的方法同名时也不行。定义方法名时有个印象就行了。进入if中就很可能会404了。
298行:Hooks类尝试调用一个pre_controller(控制器执行前)的扩展,默认没有。
301-309行:基准测试类设置一个起点标记,目的在于测试控制器执行的时长(默认不显示测试信息),并且实例化前面加载的控制器类,默认的就是Welcome。
315行:Hooks尝试执行post_controller_constructor(所调用的控制器类构造完成后)的扩展,默认没有。
317-364行:开始调用指定的控制器类的指定方法(当然这里是默认控制器Welcome的默认方法index)。看看这个流程,首先一个if判断,如果你的控制器类中有方法_remap,只调用它了,所以前面说以下划线开头的方法除了_remap,这也是CI的一个类的方法的规则,有了这个重映射方法,只调它。默认的Welcome控制器中没有_remap方法,进入else,else中还有个if,再次判断,我们调用的方法是否在这个控制器类中,如果不在的话注定要404了,只是404的调用脚本稍有不同。假如我们得application/config/routes.php文件中的routes['404_override']选项不为空(它就是我们自定义的404错误页脚本路径),将去解析它指定的目录中类名与方法名,如果类定义不存在且文件(自定义404文件)也不存在,就直接调调show_404展示CI默认的404页,只是类定义不存在的话就加载该文件,删除原对象,再new一个新的404对象(348行),当然因类定义不存在,这里理论上是要报错的;假如routes['404_override']选项为空,那么直接启用show_404方法。这个show_404是公用方法,自然是在system/core目录下的Common.php脚本里定义的。
如果我们调用的方法在这个控制器定义中,就要运行这行了:call_user_func_array(array(&$CI, $method), array_slice($URI->rsegments, 2));,调用$CI实例的$method方法,参数就是后边的数组(URI核心类对象的成员rsegments,它被重新索引,从下标2开始是解析的所调用方法的各个参数),$CI就是我们得控制器类实例,$method是对应调用方法。至此,才真正的调用了一个控制器的方法(默认Welcome的index方法),而这还是最简单的情况>3<
它然后就是进入Welcome控制器类调用index方法加载一个默认的页面了,就是开头的欢迎页。在index加载欢迎页($this->load->view(...))又加载了核心类
CodeIgniter.php后面剩下的几行
364行:设置一个基准测试标记点,控制器执行结束标记。
378-381行:如果调用Hooks钩子在输出覆盖(display_override)的扩展失败的话,做最后到浏览器的信息输出(这个输出主要做一些写入缓存,整个方法执行、页面加载等的时间、内存等的统计,头信息的设置、日志的记录等等...)。调用默认方法的话实际上从这开始到CodeIgniter.php结束没执行。
388行:尝试调用Hooks钩子扩展的,在系统执行结束时。
390-398行:如果还有数据库类实例的,关闭掉它的连接。
CodeIgniter.php结束。
OK,来看看调用一个默认的Welcome控制器默认方法都间接加载了哪些文件
可以看到有CI介绍的系统类清单全在里边。
但是最后的Loader类好像没有在CodeIgniter中明确加载,确实,它是在实例化Welcome类时,调它的父类CI_Controller的构造函数里边通过load_class加载的。
如果输入一个错误的链接访问如localhost/ci/index.php/welcome/func,404是当然的
多加载了一个Exception类,这个小细节就体现了CI的原则,“组件的导入和函数的执行只有在被要求的时候才执行,而不是在全局范围”。所以CI还是很不错的一个框架。
时间又被拉长了。。。
日后再补下其他的,主要是数据库和缓存文件的加载麻烦点,其他的还行。
====================================================================================================
补充下CI_Controller核心类,前面,说过在加载我们自己的控制器类时,首先就要加载核心控制器类并会调用它的构造函数初始化它,还是看看它,复习下

1 <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 2 /** 3 * CodeIgniter 4 * 5 * An open source application development framework for PHP 5.1.6 or newer 6 * 7 * @package CodeIgniter 8 * @author EllisLab Dev Team 9 * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. 10 * @copyright Copyright (c) 2014 - 2015, British Columbia Institute of Technology (http://bcit.ca/) 11 * @license http://codeigniter.com/user_guide/license.html 12 * @link http://codeigniter.com 13 * @since Version 1.0 14 * @filesource 15 */ 16 17 // ------------------------------------------------------------------------ 18 19 /** 20 * CodeIgniter Application Controller Class 21 * 22 * This class object is the super class that every library in 23 * CodeIgniter will be assigned to. 24 * 25 * @package CodeIgniter 26 * @subpackage Libraries 27 * @category Libraries 28 * @author EllisLab Dev Team 29 * @link http://codeigniter.com/user_guide/general/controllers.html 30 */ 31 class CI_Controller { 32 33 private static $instance; 34 35 /** 36 * Constructor 37 */ 38 public function __construct() 39 { 40 self::$instance =& $this; 41 42 // Assign all the class objects that were instantiated by the 43 // bootstrap file (CodeIgniter.php) to local class variables 44 // so that CI can run as one big super object. 45 foreach (is_loaded() as $var => $class) 46 { 47 $this->$var =& load_class($class); 48 } 49 50 $this->load =& load_class('Loader', 'core'); 51 52 $this->load->initialize(); 53 54 log_message('debug', "Controller Class Initialized"); 55 } 56 57 public static function &get_instance() 58 { 59 return self::$instance; 60 } 61 } 62 // END Controller class 63 64 /* End of file Controller.php */ 65 /* Location: ./system/core/Controller.php */
看它的构造函数,先循环is_loaded方法返回的数组(参见核心类文件夹system/core/Common.php),前面说过每当通过load_class加载一个核心类时,load_class内部调用is_loaded,传递类名将加载过的核心类对象放入一个静态数组,并返回该静态数组,所以这里调用is_loaded返回加载的核心类数组并循环它,将它们一一置为CI_Controller类的属性成员($this->$var =& load_class($class);),然后直接$this->load =& load_class('Loader', 'core');将加载类Loader也置为它的成员。
下边的get_instance方法返回本类实例,而它是被全局get_instance方法调用的(system/core/CodeIgniter.php),因此全局方法get_instance直接获得了CI_Controller的实例,因此可以调用这些核心类的方法,所以get_instance才有超级方法的称呼。
在CI_Controller构造的方法的倒数第二句:$this->load->initialize();,那些库、辅助函数、语言、第三方包(package)、配置文件等等,如果是需要自动加载的,就是在这里初始化加载的,当然它们虽是自动加载,还是通过我们手动加载比如$this->load->model('someModel')的形式,调用的是model方法加载Model类,自动加载的Model类最后也是通过它来加载的。
回忆下,CI有一个这样的自动加载形式,即不是通过临时手动来调用的,而是当这个框架来加载时就已经加载或初始化的一些类或者文件,在application/config可以找到一个autoload.php(第一次看我还以为是放的__autoload()方法的文件,名字类似),打开它可以看到就是定义一个$autoload数组,以要加载的文件类型名作为键,对应的子数组元素存放文件名(是类的话根类名相关),要加载几个则向该数组中添加几个,典型的方式是:
添加之后,将需要自动加载的文件放在指定目录就给自动加载了。
知道这么一种机制后,再来看看CI_Controller中的$this->load->initialize();,来到Loder核心类,展(tui)开(dao)看看,在Loader中,有这么一些属性成员,典型的如这么两种
protected $_ci_model_paths = array();
protected $_ci_models = array();
前一个是要加载的Model类所在的路径,后一个是存放已加载的Model类,它们不一定是对应的,即有一个加载变量的数组就要有个路径数组,也可能名字不是对应命名的。

1 <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 2 /** 3 * CodeIgniter 4 * 5 * An open source application development framework for PHP 5.1.6 or newer 6 * 7 * @package CodeIgniter 8 * @author EllisLab Dev Team 9 * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. 10 * @copyright Copyright (c) 2014 - 2015, British Columbia Institute of Technology (http://bcit.ca/) 11 * @license http://codeigniter.com/user_guide/license.html 12 * @link http://codeigniter.com 13 * @since Version 1.0 14 * @filesource 15 */ 16 17 // ------------------------------------------------------------------------ 18 19 /** 20 * Loader Class 21 * 22 * Loads views and files 23 * 24 * @package CodeIgniter 25 * @subpackage Libraries 26 * @author EllisLab Dev Team 27 * @category Loader 28 * @link http://codeigniter.com/user_guide/libraries/loader.html 29 */ 30 class CI_Loader { 31 32 // All these are set automatically. Don't mess with them. 33 /** 34 * Nesting level of the output buffering mechanism 35 * 36 * @var int 37 * @access protected 38 */ 39 protected $_ci_ob_level; 40 /** 41 * List of paths to load views from 42 * 43 * @var array 44 * @access protected 45 */ 46 protected $_ci_view_paths = array(); 47 /** 48 * List of paths to load libraries from 49 * 50 * @var array 51 * @access protected 52 */ 53 protected $_ci_library_paths = array(); 54 /** 55 * List of paths to load models from 56 * 57 * @var array 58 * @access protected 59 */ 60 protected $_ci_model_paths = array(); 61 /** 62 * List of paths to load helpers from 63 * 64 * @var array 65 * @access protected 66 */ 67 protected $_ci_helper_paths = array(); 68 /** 69 * List of loaded base classes 70 * Set by the controller class 71 * 72 * @var array 73 * @access protected 74 */ 75 protected $_base_classes = array(); // Set by the controller class 76 /** 77 * List of cached variables 78 * 79 * @var array 80 * @access protected 81 */ 82 protected $_ci_cached_vars = array(); 83 /** 84 * List of loaded classes 85 * 86 * @var array 87 * @access protected 88 */ 89 protected $_ci_classes = array(); 90 /** 91 * List of loaded files 92 * 93 * @var array 94 * @access protected 95 */ 96 protected $_ci_loaded_files = array(); 97 /** 98 * List of loaded models 99 * 100 * @var array 101 * @access protected 102 */ 103 protected $_ci_models = array(); 104 /** 105 * List of loaded helpers 106 * 107 * @var array 108 * @access protected 109 */ 110 protected $_ci_helpers = array(); 111 /** 112 * List of class name mappings 113 * 114 * @var array 115 * @access protected 116 */ 117 protected $_ci_varmap = array('unit_test' => 'unit', 118 'user_agent' => 'agent'); 119 120 /** 121 * Constructor 122 * 123 * Sets the path to the view files and gets the initial output buffering level 124 */ 125 public function __construct() 126 { 127 $this->_ci_ob_level = ob_get_level(); 128 $this->_ci_library_paths = array(APPPATH, BASEPATH); 129 $this->_ci_helper_paths = array(APPPATH, BASEPATH); 130 $this->_ci_model_paths = array(APPPATH); 131 $this->_ci_view_paths = array(APPPATH.'views/' => TRUE); 132 133 log_message('debug', "Loader Class Initialized"); 134 } 135 136 // -------------------------------------------------------------------- 137 138 /** 139 * Initialize the Loader 140 * 141 * This method is called once in CI_Controller. 142 * 143 * @param array 144 * @return object 145 */ 146 public function initialize() 147 { 148 $this->_ci_classes = array(); 149 $this->_ci_loaded_files = array(); 150 $this->_ci_models = array(); 151 $this->_base_classes =& is_loaded(); 152 153 $this->_ci_autoloader(); 154 155 return $this; 156 } 157 158 // -------------------------------------------------------------------- 159 160 /** 161 * Is Loaded 162 * 163 * A utility function to test if a class is in the self::$_ci_classes array. 164 * This function returns the object name if the class tested for is loaded, 165 * and returns FALSE if it isn't. 166 * 167 * It is mainly used in the form_helper -> _get_validation_object() 168 * 169 * @param string class being checked for 170 * @return mixed class object name on the CI SuperObject or FALSE 171 */ 172 public function is_loaded($class) 173 { 174 if (isset($this->_ci_classes[$class])) 175 { 176 return $this->_ci_classes[$class]; 177 } 178 179 return FALSE; 180 } 181 182 // -------------------------------------------------------------------- 183 184 /** 185 * Class Loader 186 * 187 * This function lets users load and instantiate classes. 188 * It is designed to be called from a user's app controllers. 189 * 190 * @param string the name of the class 191 * @param mixed the optional parameters 192 * @param string an optional object name 193 * @return void 194 */ 195 public function library($library = '', $params = NULL, $object_name = NULL) 196 { 197 if (is_array($library)) 198 { 199 foreach ($library as $class) 200 { 201 $this->library($class, $params); 202 } 203 204 return; 205 } 206 207 if ($library == '' OR isset($this->_base_classes[$library])) 208 { 209 return FALSE; 210 } 211 212 if ( ! is_null($params) && ! is_array($params)) 213 { 214 $params = NULL; 215 } 216 217 $this->_ci_load_class($library, $params, $object_name); 218 } 219 220 // -------------------------------------------------------------------- 221 222 /** 223 * Model Loader 224 * 225 * This function lets users load and instantiate models. 226 * 227 * @param string the name of the class 228 * @param string name for the model 229 * @param bool database connection 230 * @return void 231 */ 232 public function model($model, $name = '', $db_conn = FALSE) 233 { 234 if (is_array($model)) 235 { 236 foreach ($model as $babe) 237 { 238 $this->model($babe); 239 } 240 return; 241 } 242 243 if ($model == '') 244 { 245 return; 246 } 247 248 $path = ''; 249 250 // Is the model in a sub-folder? If so, parse out the filename and path. 251 if (($last_slash = strrpos($model, '/')) !== FALSE) 252 { 253 // The path is in front of the last slash 254 $path = substr($model, 0, $last_slash + 1); 255 256 // And the model name behind it 257 $model = substr($model, $last_slash + 1); 258 } 259 260 if ($name == '') 261 { 262 $name = $model; 263 } 264 265 if (in_array($name, $this->_ci_models, TRUE)) 266 { 267 return; 268 } 269 270 $CI =& get_instance(); 271 if (isset($CI->$name)) 272 { 273 show_error('The model name you are loading is the name of a resource that is already being used: '.$name); 274 } 275 276 $model = strtolower($model); 277 278 foreach ($this->_ci_model_paths as $mod_path) 279 { 280 if ( ! file_exists($mod_path.'models/'.$path.$model.'.php')) 281 { 282 continue; 283 } 284 285 if ($db_conn !== FALSE AND ! class_exists('CI_DB')) 286 { 287 if ($db_conn === TRUE) 288 { 289 $db_conn = ''; 290 } 291 292 $CI->load->database($db_conn, FALSE, TRUE); 293 } 294 295 if ( ! class_exists('CI_Model')) 296 { 297 load_class('Model', 'core'); 298 } 299 300 require_once($mod_path.'models/'.$path.$model.'.php'); 301 302 $model = ucfirst($model); 303 304 $CI->$name = new $model(); 305 306 $this->_ci_models[] = $name; 307 return; 308 } 309 310 // couldn't find the model 311 show_error('Unable to locate the model you have specified: '.$model); 312 } 313 314 // -------------------------------------------------------------------- 315 316 /** 317 * Database Loader 318 * 319 * @param string the DB credentials 320 * @param bool whether to return the DB object 321 * @param bool whether to enable active record (this allows us to override the config setting) 322 * @return object 323 */ 324 public function database($params = '', $return = FALSE, $active_record = NULL) 325 { 326 // Grab the super object 327 $CI =& get_instance(); 328 329 // Do we even need to load the database class? 330 if (class_exists('CI_DB') AND $return == FALSE AND $active_record == NULL AND isset($CI->db) AND is_object($CI->db)) 331 { 332 return FALSE; 333 } 334 335 require_once(BASEPATH.'database/DB.php'); 336 337 if ($return === TRUE) 338 { 339 return DB($params, $active_record); 340 } 341 342 // Initialize the db variable. Needed to prevent 343 // reference errors with some configurations 344 $CI->db = ''; 345 346 // Load the DB class 347 $CI->db =& DB($params, $active_record); 348 } 349 350 // -------------------------------------------------------------------- 351 352 /** 353 * Load the Utilities Class 354 * 355 * @return string 356 */ 357 public function dbutil() 358 { 359 if ( ! class_exists('CI_DB')) 360 { 361 $this->database(); 362 } 363 364 $CI =& get_instance(); 365 366 // for backwards compatibility, load dbforge so we can extend dbutils off it 367 // this use is deprecated and strongly discouraged 368 $CI->load->dbforge(); 369 370 require_once(BASEPATH.'database/DB_utility.php'); 371 require_once(BASEPATH.'database/drivers/'.$CI->db->dbdriver.'/'.$CI->db->dbdriver.'_utility.php'); 372 $class = 'CI_DB_'.$CI->db->dbdriver.'_utility'; 373 374 $CI->dbutil = new $class(); 375 } 376 377 // -------------------------------------------------------------------- 378 379 /** 380 * Load the Database Forge Class 381 * 382 * @return string 383 */ 384 public function dbforge() 385 { 386 if ( ! class_exists('CI_DB')) 387 { 388 $this->database(); 389 } 390 391 $CI =& get_instance(); 392 393 require_once(BASEPATH.'database/DB_forge.php'); 394 require_once(BASEPATH.'database/drivers/'.$CI->db->dbdriver.'/'.$CI->db->dbdriver.'_forge.php'); 395 $class = 'CI_DB_'.$CI->db->dbdriver.'_forge'; 396 397 $CI->dbforge = new $class(); 398 } 399 400 // -------------------------------------------------------------------- 401 402 /** 403 * Load View 404 * 405 * This function is used to load a "view" file. It has three parameters: 406 * 407 * 1. The name of the "view" file to be included. 408 * 2. An associative array of data to be extracted for use in the view. 409 * 3. TRUE/FALSE - whether to return the data or load it. In 410 * some cases it's advantageous to be able to return data so that 411 * a developer can process it in some way. 412 * 413 * @param string 414 * @param array 415 * @param bool 416 * @return void 417 */ 418 public function view($view, $vars = array(), $return = FALSE) 419 { 420 return $this->_ci_load(array('_ci_view' => $view, '_ci_vars' => $this->_ci_object_to_array($vars), '_ci_return' => $return)); 421 } 422 423 // -------------------------------------------------------------------- 424 425 /** 426 * Load File 427 * 428 * This is a generic file loader 429 * 430 * @param string 431 * @param bool 432 * @return string 433 */ 434 public function file($path, $return = FALSE) 435 { 436 return $this->_ci_load(array('_ci_path' => $path, '_ci_return' => $return)); 437 } 438 439 // -------------------------------------------------------------------- 440 441 /** 442 * Set Variables 443 * 444 * Once variables are set they become available within 445 * the controller class and its "view" files. 446 * 447 * @param array 448 * @param string 449 * @return void 450 */ 451 public function vars($vars = array(), $val = '') 452 { 453 if ($val != '' AND is_string($vars)) 454 { 455 $vars = array($vars => $val); 456 } 457 458 $vars = $this->_ci_object_to_array($vars); 459 460 if (is_array($vars) AND count($vars) > 0) 461 { 462 foreach ($vars as $key => $val) 463 { 464 $this->_ci_cached_vars[$key] = $val; 465 } 466 } 467 } 468 469 // -------------------------------------------------------------------- 470 471 /** 472 * Get Variable 473 * 474 * Check if a variable is set and retrieve it. 475 * 476 * @param array 477 * @return void 478 */ 479 public function get_var($key) 480 { 481 return isset($this->_ci_cached_vars[$key]) ? $this->_ci_cached_vars[$key] : NULL; 482 } 483 484 // -------------------------------------------------------------------- 485 486 /** 487 * Load Helper 488 * 489 * This function loads the specified helper file. 490 * 491 * @param mixed 492 * @return void 493 */ 494 public function helper($helpers = array()) 495 { 496 foreach ($this->_ci_prep_filename($helpers, '_helper') as $helper) 497 { 498 if (isset($this->_ci_helpers[$helper])) 499 { 500 continue; 501 } 502 503 $ext_helper = APPPATH.'helpers/'.config_item('subclass_prefix').$helper.'.php'; 504 505 // Is this a helper extension request? 506 if (file_exists($ext_helper)) 507 { 508 $base_helper = BASEPATH.'helpers/'.$helper.'.php'; 509 510 if ( ! file_exists($base_helper)) 511 { 512 show_error('Unable to load the requested file: helpers/'.$helper.'.php'); 513 } 514 515 include_once($ext_helper); 516 include_once($base_helper); 517 518 $this->_ci_helpers[$helper] = TRUE; 519 log_message('debug', 'Helper loaded: '.$helper); 520 continue; 521 } 522 523 // Try to load the helper 524 foreach ($this->_ci_helper_paths as $path) 525 { 526 if (file_exists($path.'helpers/'.$helper.'.php')) 527 { 528 include_once($path.'helpers/'.$helper.'.php'); 529 530 $this->_ci_helpers[$helper] = TRUE; 531 log_message('debug', 'Helper loaded: '.$helper); 532 break; 533 } 534 } 535 536 // unable to load the helper 537 if ( ! isset($this->_ci_helpers[$helper])) 538 { 539 show_error('Unable to load the requested file: helpers/'.$helper.'.php'); 540 } 541 } 542 } 543 544 // -------------------------------------------------------------------- 545 546 /** 547 * Load Helpers 548 * 549 * This is simply an alias to the above function in case the 550 * user has written the plural form of this function. 551 * 552 * @param array 553 * @return void 554 */ 555 public function helpers($helpers = array()) 556 { 557 $this->helper($helpers); 558 } 559 560 // -------------------------------------------------------------------- 561 562 /** 563 * Loads a language file 564 * 565 * @param array 566 * @param string 567 * @return void 568 */ 569 public function language($file = array(), $lang = '') 570 { 571 $CI =& get_instance(); 572 573 if ( ! is_array($file)) 574 { 575 $file = array($file); 576 } 577 578 foreach ($file as $langfile) 579 { 580 $CI->lang->load($langfile, $lang); 581 } 582 } 583 584 // -------------------------------------------------------------------- 585 586 /** 587 * Loads a config file 588 * 589 * @param string 590 * @param bool 591 * @param bool 592 * @return void 593 */ 594 public function config($file = '', $use_sections = FALSE, $fail_gracefully = FALSE) 595 { 596 $CI =& get_instance(); 597 $CI->config->load($file, $use_sections, $fail_gracefully); 598 } 599 600 // -------------------------------------------------------------------- 601 602 /** 603 * Driver 604 * 605 * Loads a driver library 606 * 607 * @param string the name of the class 608 * @param mixed the optional parameters 609 * @param string an optional object name 610 * @return void 611 */ 612 public function driver($library = '', $params = NULL, $object_name = NULL) 613 { 614 if ( ! class_exists('CI_Driver_Library')) 615 { 616 // we aren't instantiating an object here, that'll be done by the Library itself 617 require BASEPATH.'libraries/Driver.php'; 618 } 619 620 if ($library == '') 621 { 622 return FALSE; 623 } 624 625 // We can save the loader some time since Drivers will *always* be in a subfolder, 626 // and typically identically named to the library 627 if ( ! strpos($library, '/')) 628 { 629 $library = ucfirst($library).'/'.$library; 630 } 631 632 return $this->library($library, $params, $object_name); 633 } 634 635 // -------------------------------------------------------------------- 636 637 /** 638 * Add Package Path 639 * 640 * Prepends a parent path to the library, model, helper, and config path arrays 641 * 642 * @param string 643 * @param boolean 644 * @return void 645 */ 646 public function add_package_path($path, $view_cascade=TRUE) 647 { 648 $path = rtrim($path, '/').'/'; 649 650 array_unshift($this->_ci_library_paths, $path); 651 array_unshift($this->_ci_model_paths, $path); 652 array_unshift($this->_ci_helper_paths, $path); 653 654 $this->_ci_view_paths = array($path.'views/' => $view_cascade) + $this->_ci_view_paths; 655 656 // Add config file path 657 $config =& $this->_ci_get_component('config'); 658 array_unshift($config->_config_paths, $path); 659 } 660 661 // -------------------------------------------------------------------- 662 663 /** 664 * Get Package Paths 665 * 666 * Return a list of all package paths, by default it will ignore BASEPATH. 667 * 668 * @param string 669 * @return void 670 */ 671 public function get_package_paths($include_base = FALSE) 672 { 673 return $include_base === TRUE ? $this->_ci_library_paths : $this->_ci_model_paths; 674 } 675 676 // -------------------------------------------------------------------- 677 678 /** 679 * Remove Package Path 680 * 681 * Remove a path from the library, model, and helper path arrays if it exists 682 * If no path is provided, the most recently added path is removed. 683 * 684 * @param type 685 * @param bool 686 * @return type 687 */ 688 public function remove_package_path($path = '', $remove_config_path = TRUE) 689 { 690 $config =& $this->_ci_get_component('config'); 691 692 if ($path == '') 693 { 694 $void = array_shift($this->_ci_library_paths); 695 $void = array_shift($this->_ci_model_paths); 696 $void = array_shift($this->_ci_helper_paths); 697 $void = array_shift($this->_ci_view_paths); 698 $void = array_shift($config->_config_paths); 699 } 700 else 701 { 702 $path = rtrim($path, '/').'/'; 703 foreach (array('_ci_library_paths', '_ci_model_paths', '_ci_helper_paths') as $var) 704 { 705 if (($key = array_search($path, $this->{$var})) !== FALSE) 706 { 707 unset($this->{$var}[$key]); 708 } 709 } 710 711 if (isset($this->_ci_view_paths[$path.'views/'])) 712 { 713 unset($this->_ci_view_paths[$path.'views/']); 714 } 715 716 if (($key = array_search($path, $config->_config_paths)) !== FALSE) 717 { 718 unset($config->_config_paths[$key]); 719 } 720 } 721 722 // make sure the application default paths are still in the array 723 $this->_ci_library_paths = array_unique(array_merge($this->_ci_library_paths, array(APPPATH, BASEPATH))); 724 $this->_ci_helper_paths = array_unique(array_merge($this->_ci_helper_paths, array(APPPATH, BASEPATH))); 725 $this->_ci_model_paths = array_unique(array_merge($this->_ci_model_paths, array(APPPATH))); 726 $this->_ci_view_paths = array_merge($this->_ci_view_paths, array(APPPATH.'views/' => TRUE)); 727 $config->_config_paths = array_unique(array_merge($config->_config_paths, array(APPPATH))); 728 } 729 730 // -------------------------------------------------------------------- 731 732 /** 733 * Loader 734 * 735 * This function is used to load views and files. 736 * Variables are prefixed with _ci_ to avoid symbol collision with 737 * variables made available to view files 738 * 739 * @param array 740 * @return void 741 */ 742 protected function _ci_load($_ci_data) 743 { 744 // Set the default data variables 745 foreach (array('_ci_view', '_ci_vars', '_ci_path', '_ci_return') as $_ci_val) 746 { 747 $$_ci_val = ( ! isset($_ci_data[$_ci_val])) ? FALSE : $_ci_data[$_ci_val]; 748 } 749 750 $file_exists = FALSE; 751 752 // Set the path to the requested file 753 if ($_ci_path != '') 754 { 755 $_ci_x = explode('/', $_ci_path); 756 $_ci_file = end($_ci_x); 757 } 758 else 759 { 760 $_ci_ext = pathinfo($_ci_view, PATHINFO_EXTENSION); 761 $_ci_file = ($_ci_ext == '') ? $_ci_view.'.php' : $_ci_view; 762 763 foreach ($this->_ci_view_paths as $view_file => $cascade) 764 { 765 if (file_exists($view_file.$_ci_file)) 766 { 767 $_ci_path = $view_file.$_ci_file; 768 $file_exists = TRUE; 769 break; 770 } 771 772 if ( ! $cascade) 773 { 774 break; 775 } 776 } 777 } 778 779 if ( ! $file_exists && ! file_exists($_ci_path)) 780 { 781 show_error('Unable to load the requested file: '.$_ci_file); 782 } 783 784 // This allows anything loaded using $this->load (views, files, etc.) 785 // to become accessible from within the Controller and Model functions. 786 787 $_ci_CI =& get_instance(); 788 foreach (get_object_vars($_ci_CI) as $_ci_key => $_ci_var) 789 { 790 if ( ! isset($this->$_ci_key)) 791 { 792 $this->$_ci_key =& $_ci_CI->$_ci_key; 793 } 794 } 795 796 /* 797 * Extract and cache variables 798 * 799 * You can either set variables using the dedicated $this->load_vars() 800 * function or via the second parameter of this function. We'll merge 801 * the two types and cache them so that views that are embedded within 802 * other views can have access to these variables. 803 */ 804 if (is_array($_ci_vars)) 805 { 806 $this->_ci_cached_vars = array_merge($this->_ci_cached_vars, $_ci_vars); 807 } 808 extract($this->_ci_cached_vars); 809 810 /* 811 * Buffer the output 812 * 813 * We buffer the output for two reasons: 814 * 1. Speed. You get a significant speed boost. 815 * 2. So that the final rendered template can be 816 * post-processed by the output class. Why do we 817 * need post processing? For one thing, in order to 818 * show the elapsed page load time. Unless we 819 * can intercept the content right before it's sent to 820 * the browser and then stop the timer it won't be accurate. 821 */ 822 ob_start(); 823 824 // If the PHP installation does not support short tags we'll 825 // do a little string replacement, changing the short tags 826 // to standard PHP echo statements. 827 828 if ((bool) @ini_get('short_open_tag') === FALSE AND config_item('rewrite_short_tags') == TRUE) 829 { 830 echo eval('?>'.preg_replace("/;*\s*\?>/", "; ?>", str_replace('<?=', '<?php echo ', file_get_contents($_ci_path)))); 831 } 832 else 833 { 834 include($_ci_path); // include() vs include_once() allows for multiple views with the same name 835 } 836 837 log_message('debug', 'File loaded: '.$_ci_path); 838 839 // Return the file data if requested 840 if ($_ci_return === TRUE) 841 { 842 $buffer = ob_get_contents(); 843 @ob_end_clean(); 844 return $buffer; 845 } 846 847 /* 848 * Flush the buffer... or buff the flusher? 849 * 850 * In order to permit views to be nested within 851 * other views, we need to flush the content back out whenever 852 * we are beyond the first level of output buffering so that 853 * it can be seen and included properly by the first included 854 * template and any subsequent ones. Oy! 855 * 856 */ 857 if (ob_get_level() > $this->_ci_ob_level + 1) 858 { 859 ob_end_flush(); 860 } 861 else 862 { 863 $_ci_CI->output->append_output(ob_get_contents()); 864 @ob_end_clean(); 865 } 866 } 867 868 // -------------------------------------------------------------------- 869 870 /** 871 * Load class 872 * 873 * This function loads the requested class. 874 * 875 * @param string the item that is being loaded 876 * @param mixed any additional parameters 877 * @param string an optional object name 878 * @return void 879 */ 880 protected function _ci_load_class($class, $params = NULL, $object_name = NULL) 881 { 882 // Get the class name, and while we're at it trim any slashes. 883 // The directory path can be included as part of the class name, 884 // but we don't want a leading slash 885 $class = str_replace('.php', '', trim($class, '/')); 886 887 // Was the path included with the class name? 888 // We look for a slash to determine this 889 $subdir = ''; 890 if (($last_slash = strrpos($class, '/')) !== FALSE) 891 { 892 // Extract the path 893 $subdir = substr($class, 0, $last_slash + 1); 894 895 // Get the filename from the path 896 $class = substr($class, $last_slash + 1); 897 } 898 899 // We'll test for both lowercase and capitalized versions of the file name 900 foreach (array(ucfirst($class), strtolower($class)) as $class) 901 { 902 $subclass = APPPATH.'libraries/'.$subdir.config_item('subclass_prefix').$class.'.php'; 903 904 // Is this a class extension request? 905 if (file_exists($subclass)) 906 { 907 $baseclass = BASEPATH.'libraries/'.ucfirst($class).'.php'; 908 909 if ( ! file_exists($baseclass)) 910 { 911 log_message('error', "Unable to load the requested class: ".$class); 912 show_error("Unable to load the requested class: ".$class); 913 } 914 915 // Safety: Was the class already loaded by a previous call? 916 if (in_array($subclass, $this->_ci_loaded_files)) 917 { 918 // Before we deem this to be a duplicate request, let's see 919 // if a custom object name is being supplied. If so, we'll 920 // return a new instance of the object 921 if ( ! is_null($object_name)) 922 { 923 $CI =& get_instance(); 924 if ( ! isset($CI->$object_name)) 925 { 926 return $this->_ci_init_class($class, config_item('subclass_prefix'), $params, $object_name); 927 } 928 } 929 930 $is_duplicate = TRUE; 931 log_message('debug', $class." class already loaded. Second attempt ignored."); 932 return; 933 } 934 935 include_once($baseclass); 936 include_once($subclass); 937 $this->_ci_loaded_files[] = $subclass; 938 939 return $this->_ci_init_class($class, config_item('subclass_prefix'), $params, $object_name); 940 } 941 942 // Lets search for the requested library file and load it. 943 $is_duplicate = FALSE; 944 foreach ($this->_ci_library_paths as $path) 945 { 946 $filepath = $path.'libraries/'.$subdir.$class.'.php'; 947 948 // Does the file exist? No? Bummer... 949 if ( ! file_exists($filepath)) 950 { 951 continue; 952 } 953 954 // Safety: Was the class already loaded by a previous call? 955 if (in_array($filepath, $this->_ci_loaded_files)) 956 { 957 // Before we deem this to be a duplicate request, let's see 958 // if a custom object name is being supplied. If so, we'll 959 // return a new instance of the object 960 if ( ! is_null($object_name)) 961 { 962 $CI =& get_instance(); 963 if ( ! isset($CI->$object_name)) 964 { 965 return $this->_ci_init_class($class, '', $params, $object_name); 966 } 967 } 968 969 $is_duplicate = TRUE; 970 log_message('debug', $class." class already loaded. Second attempt ignored."); 971 return; 972 } 973 974 include_once($filepath); 975 $this->_ci_loaded_files[] = $filepath; 976 return $this->_ci_init_class($class, '', $params, $object_name); 977 } 978 979 } // END FOREACH 980 981 // One last attempt. Maybe the library is in a subdirectory, but it wasn't specified? 982 if ($subdir == '') 983 { 984 $path = strtolower($class).'/'.$class; 985 return $this->_ci_load_class($path, $params); 986 } 987 988 // If we got this far we were unable to find the requested class. 989 // We do not issue errors if the load call failed due to a duplicate request 990 if ($is_duplicate == FALSE) 991 { 992 log_message('error', "Unable to load the requested class: ".$class); 993 show_error("Unable to load the requested class: ".$class); 994 } 995 } 996 997 // -------------------------------------------------------------------- 998 999 /** 1000 * Instantiates a class 1001 * 1002 * @param string 1003 * @param string 1004 * @param bool 1005 * @param string an optional object name 1006 * @return null 1007 */ 1008 protected function _ci_init_class($class, $prefix = '', $config = FALSE, $object_name = NULL) 1009 { 1010 // Is there an associated config file for this class? Note: these should always be lowercase 1011 if ($config === NULL) 1012 { 1013 // Fetch the config paths containing any package paths 1014 $config_component = $this->_ci_get_component('config'); 1015 1016 if (is_array($config_component->_config_paths)) 1017 { 1018 // Break on the first found file, thus package files 1019 // are not overridden by default paths 1020 foreach ($config_component->_config_paths as $path) 1021 { 1022 // We test for both uppercase and lowercase, for servers that 1023 // are case-sensitive with regard to file names. Check for environment 1024 // first, global next 1025 if (defined('ENVIRONMENT') AND file_exists($path .'config/'.ENVIRONMENT.'/'.strtolower($class).'.php')) 1026 { 1027 include($path .'config/'.ENVIRONMENT.'/'.strtolower($class).'.php'); 1028 break; 1029 } 1030 elseif (defined('ENVIRONMENT') AND file_exists($path .'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php')) 1031 { 1032 include($path .'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php'); 1033 break; 1034 } 1035 elseif (file_exists($path .'config/'.strtolower($class).'.php')) 1036 { 1037 include($path .'config/'.strtolower($class).'.php'); 1038 break; 1039 } 1040 elseif (file_exists($path .'config/'.ucfirst(strtolower($class)).'.php')) 1041 { 1042 include($path .'config/'.ucfirst(strtolower($class)).'.php'); 1043 break; 1044 } 1045 } 1046 } 1047 } 1048 1049 if ($prefix == '') 1050 { 1051 if (class_exists('CI_'.$class)) 1052 { 1053 $name = 'CI_'.$class; 1054 } 1055 elseif (class_exists(config_item('subclass_prefix').$class)) 1056 { 1057 $name = config_item('subclass_prefix').$class; 1058 } 1059 else 1060 { 1061 $name = $class; 1062 } 1063 } 1064 else 1065 { 1066 $name = $prefix.$class; 1067 } 1068 1069 // Is the class name valid? 1070 if ( ! class_exists($name)) 1071 { 1072 log_message('error', "Non-existent class: ".$name); 1073 show_error("Non-existent class: ".$class); 1074 } 1075 1076 // Set the variable name we will assign the class to 1077 // Was a custom class name supplied? If so we'll use it 1078 $class = strtolower($class); 1079 1080 if (is_null($object_name)) 1081 { 1082 $classvar = ( ! isset($this->_ci_varmap[$class])) ? $class : $this->_ci_varmap[$class]; 1083 } 1084 else 1085 { 1086 $classvar = $object_name; 1087 } 1088 1089 // Save the class name and object name 1090 $this->_ci_classes[$class] = $classvar; 1091 1092 // Instantiate the class 1093 $CI =& get_instance(); 1094 if ($config !== NULL) 1095 { 1096 $CI->$classvar = new $name($config); 1097 } 1098 else 1099 { 1100 $CI->$classvar = new $name; 1101 } 1102 } 1103 1104 // -------------------------------------------------------------------- 1105 1106 /** 1107 * Autoloader 1108 * 1109 * The config/autoload.php file contains an array that permits sub-systems, 1110 * libraries, and helpers to be loaded automatically. 1111 * 1112 * @param array 1113 * @return void 1114 */ 1115 private function _ci_autoloader() 1116 { 1117 if (defined('ENVIRONMENT') AND file_exists(APPPATH.'config/'.ENVIRONMENT.'/autoload.php')) 1118 { 1119 include(APPPATH.'config/'.ENVIRONMENT.'/autoload.php'); 1120 } 1121 else 1122 { 1123 include(APPPATH.'config/autoload.php'); 1124 } 1125 1126 if ( ! isset($autoload)) 1127 { 1128 return FALSE; 1129 } 1130 1131 // Autoload packages 1132 if (isset($autoload['packages'])) 1133 { 1134 foreach ($autoload['packages'] as $package_path) 1135 { 1136 $this->add_package_path($package_path); 1137 } 1138 } 1139 1140 // Load any custom config file 1141 if (count($autoload['config']) > 0) 1142 { 1143 $CI =& get_instance(); 1144 foreach ($autoload['config'] as $key => $val) 1145 { 1146 $CI->config->load($val); 1147 } 1148 } 1149 1150 // Autoload helpers and languages 1151 foreach (array('helper', 'language') as $type) 1152 { 1153 if (isset($autoload[$type]) AND count($autoload[$type]) > 0) 1154 { 1155 $this->$type($autoload[$type]); 1156 } 1157 } 1158 1159 // A little tweak to remain backward compatible 1160 // The $autoload['core'] item was deprecated 1161 if ( ! isset($autoload['libraries']) AND isset($autoload['core'])) 1162 { 1163 $autoload['libraries'] = $autoload['core']; 1164 } 1165 1166 // Load libraries 1167 if (isset($autoload['libraries']) AND count($autoload['libraries']) > 0) 1168 { 1169 // Load the database driver. 1170 if (in_array('database', $autoload['libraries'])) 1171 { 1172 $this->database(); 1173 $autoload['libraries'] = array_diff($autoload['libraries'], array('database')); 1174 } 1175 1176 // Load all other libraries 1177 foreach ($autoload['libraries'] as $item) 1178 { 1179 $this->library($item); 1180 } 1181 } 1182 1183 // Autoload models 1184 if (isset($autoload['model'])) 1185 { 1186 $this->model($autoload['model']); 1187 } 1188 } 1189 1190 // -------------------------------------------------------------------- 1191 1192 /** 1193 * Object to Array 1194 * 1195 * Takes an object as input and converts the class variables to array key/vals 1196 * 1197 * @param object 1198 * @return array 1199 */ 1200 protected function _ci_object_to_array($object) 1201 { 1202 return (is_object($object)) ? get_object_vars($object) : $object; 1203 } 1204 1205 // -------------------------------------------------------------------- 1206 1207 /** 1208 * Get a reference to a specific library or model 1209 * 1210 * @param string 1211 * @return bool 1212 */ 1213 protected function &_ci_get_component($component) 1214 { 1215 $CI =& get_instance(); 1216 return $CI->$component; 1217 } 1218 1219 // -------------------------------------------------------------------- 1220 1221 /** 1222 * Prep filename 1223 * 1224 * This function preps the name of various items to make loading them more reliable. 1225 * 1226 * @param mixed 1227 * @param string 1228 * @return array 1229 */ 1230 protected function _ci_prep_filename($filename, $extension) 1231 { 1232 if ( ! is_array($filename)) 1233 { 1234 return array(strtolower(str_replace('.php', '', str_replace($extension, '', $filename)).$extension)); 1235 } 1236 else 1237 { 1238 foreach ($filename as $key => $val) 1239 { 1240 $filename[$key] = strtolower(str_replace('.php', '', str_replace($extension, '', $val)).$extension); 1241 } 1242 1243 return $filename; 1244 } 1245 } 1246 } 1247 1248 /* End of file Loader.php */ 1249 /* Location: ./system/core/Loader.php */
125行:它的构造函数,主要是对几个类型文件的路径变量初始化,如library库文件的是array(APPPATH, BASEPATH),预示着它会到这两个目下取搜索,model数组是array(APPPATH),就只在供我们开发的APPPATH下边去搜索加载。
146行:就是它的initialize()函数,主要做两件事,前面几行将即将放要加载的类或文件的数组置空,如$this->_ci_models = array();,对装载基础类的变量填满核心类,如$this->_base_classes =& is_loaded();(前面说过is_loaded可返回所有加载过的核心类),后面的 $this->_ci_autoloader(); 则开始执行自动CI需要自动加载的文件的流程了。
1115行:转到_ci_autoloader()的定义,看看它的注释,意思是config/autoload.php包含了一个数组,允许子系统(sub-systems)、库、辅助函数等的自动加载。
它先试图加载APPPATH.'config/'.ENVIRONMENT.'/autoload.php'(这两个变量参考前面),默认CI的文件目录中是没有development这个目录的, 所以无论你把他放在哪个目录下,再在它下面寻找autoload文件肯定是不存在的,然后,它加载的是APPPATH.'config/autoload.php',供我们开发的application目录下的配置信息目录下的autoload.php。
1131行:成功找到autoload.php并include后,第一个加载的脚本类型是package(包),package这个键的元素应该是目录,例如APPPATH.'third_party',也就是第三方的扩展包,如果要用这个,那么就得在application下新建一个third_party目录,里边存放这些东西,,在$autoload['package']中添加对应目录字符串,名字可以改。foreach调用了add_package_path方法,看看它在干啥。
646行:先将传进来的$path变量,即存放package的目录,分别添加到_ci_library_paths(存放库文件目录的数组,后面类似)、_ci_model_paths、_ci_helper_paths的开头,包括存放视图的数组也加进去了在第三方包目录下的对应的视图变量array($path.'view'=>TRUE),关于这个存放视图的数组成员,还没看过它是干啥的。然后通过_ci_get_component得到Config核心类的对象,将$path加入到它的成员_config_paths数组中,这个数组原有的元素也是个路径:APPPATH。$path变量是“第三方”目录(APPPATH.'third_party'),从这里连续的将这个$path变量加到Model、Helper等数组中,这个第三方的package大有独立系统的意思,原本我们自己开发的话只是加载application目录下边的models、helpers、views等,这里好像是这些文件也要到$path目录下再去找一遍,当然咯,前提第你的application下面要有个third_party目录,并且里面也放了一些文件。回到1131行 // Autoload packages 下边的if就是将路径$path加到Loader类的路径成员变量中。
1140行:载入任何自定义的配置文件。检查$autoload['config']是否有元素,通过超级方法get_instance来调用核心类Config,并通过它的成员函数load加载这些配置文件。注意它主要是加载application/config下的文件。
1150行:加载helper和language文件。分别调用了helper和language成员方法,就相当于我们在控制器中这样用:$this->load->helper(...),所以这个_ci_autoloader方法实际调用还是最后、公共的加载方法。
494行:以helper为例,看它加载时都干了啥。辅助函数用到的几率也算多的,比如你自己单独就数组的一些特殊处理作为一个辅助函数文件。首先加载辅助函数传过来的参数可以是单个文件名字符串或多个名称组成的数组变量,调用_ci_prep_filename这个方法返回的数组进行foreach,1230行的_ci_prep_filename函数功能是对传入的辅助函数文件名去掉末尾的.php扩展名,加上_helper后缀。所以当我们加载辅助函数时直接是这样$this->load->helper('array');就行,传全名反而错了。
回到helper函数,如果已经加载过,continue至下一个helper变量。首先找的是这样的:$ext_helper = APPPATH.'helpers/'.config_item('subclass_prefix').$helper.'.php',子类前缀默认是MY_,这里的$helper已经是经过上面处理过的,而看变量名猜得到就是对已有的辅助函数的扩展,如上边的array_helper.php是在system/helpers目录下,我现在要对它扩展,就定义个MY_array_helper.php就行了,注意辅助函数只是定义一些函数,不是单个类。如果辅助函数扩展出现了,就要先加载原有的基础辅助函数文件(BASEPATH.'helpers/'.$helper.'.php',在系统目录下),因此518-513行对基础文件进行检查。只有基础文件也出现了才都include(实际用include_once)它们。假如我没有扩展,就要走到524行,循环_ci_helper_paths这个属性成员来寻找,前面提到CI的Loader核心类的一些成员,是已经加载过得类弄到一个数组中,这是一种类型变量,然后是另外一种将各种类型文件即将出现的目录弄到一起,是另外一种类型的,毫无疑问,它里面就是helper可能存在的目录集合。它初始化是这样的:
前面在看到加载package时也看到,如果存在package且$autoload写入了对应的目录名,这些目录是会加到_ci_helper_paths数组前面的,当然默认没有这个。所以在524行foreach这个变量时,首先找APPPATH.'helpers/'.$helper.'.php',再找BATHPATH.'helpers/'.$helper.'.php',所以就是说,在application/helpers目录下边不都是扩展原来的,新写的也可以。找到了就include,并且加入到装载已经加载过的辅助函数的数组_ci_helpers中(再次加载时就直接返回),没找到就show_error展示错误页面。
569行:回到自动加载函数_ci_autoloader的1151行,加载language脚本。同理在$autoload['language']下面如果定义有一些需要加载的语言脚本名称,则foreach每一个元素,然后跳到569行执行language函数。在language函数里面。先通过get_instance()函数获得超级对象,也就是控制器基类CI_Controller,直接调用核心类Lang对象成员(前面的打印结果中可以到,语言核心类作为基础类已经被加载完毕)的load方法加载,传入数组就扫描加载它们,那就进入到CI_Lang类这种看看这个load方法的大致流程。
传送到system/core/Lang.php里边,66行:首先如果有.php扩展名就去掉,然后给文件名加上_lang后缀,所以写语言脚本时在命名时就要加上_lang,而加载时传入参数你也不必写比如eng_lang,它会给你加,有也会去掉再加,所以完全没必要。然后连接上.php后缀,在CI_Lang核心类中有一个成员变量is_loaded会记录下所有已经加载过的语言文件,所以接下来先检查是否被加载过,若加载直接返回。然后获得config文件中的$config变量(Common.php中的get_config全局方法,加载application/config/config.php),然后根据$config['language']的值(默认是english)确定$deft_lang和$idiom的值,先不管它们来干什么,接下来就是寻找language file并加载它们。如果传入第二个参数$idiom,它将在比如application/language/$idiom这个目录下去寻找语言文件,所以这个参数决定了你将加载什么类型的文件,当然你不穿默认就是$config['language']的值。
注意到CI_Lang类的load方法的最后一个参数$alt_path表示一个可选的路径,所以接下来会以它来拼一个路径寻找语言文件(file_exists($alt_path.'language/'.$idiom.'/'.$langfile),$alt_path为空,$idom默认为english),这里不存在这个路径文件,走else,调用Loader类的get_package_paths方法返回一个路径数组,这个数组默认是array(APPPATH,BASEPATH),转了一圈还是先找application/language/下的语言文件,再找system/language/下的,所以如果自定义语言文件,大可以放在前者目录下就行了,可以少找一步。一旦找到了就加载并break退出循环,如果需要返回变量(122行)则返回$lang (这是定义语言文件时所用),否则将记录已加载过得语言文件(127),将新加入的$lang变量merge到Lang类的language成员中。
Lang还有个方法line,这个就是返回$lang这个数组中的某一个元素对应的值,比如传入'lovely',它将返回$lang['lovely']的值。
综上,语言文件的加载就是在找application/language和system/language两个目录,定义文件时比如现在定义一个汉语的放在迁移个目录下,可以这样:现在application/language下新建一个目录chinese,我现在翻译湖北话,文件名定义为hubei_lang.php,里边可以是$lang['have a meal'] = '七饭';,将该文件放在application/language/chinese目录下,然后在控制器某方法中,载入语言文件$this->lang->load('hubei', 'chinese'); echo $this->lang->line('have a meal');。一个湖北方言版的网页就此诞生-_-#。这就是CI所谓的“国际化”,使用不同的语言呈现同一页面。
这就是Loader中的language方法,对语言文件加载和使用的简单流程。
回到Loader核心类,1152行到1158行,就是加载辅助函数和语言脚本。
1162行:检查$autoload中是否有core为键名的元素,如果有,将它赋给libraries为键名的元素,这大概是CI以前将工具库的类的键名称为core,后来弃用,改为libraries。
1168行:开始加载library,先检查$autoload['libraries']是否有值database,即自动加载数据库,这里就调用了Loader类的database方法,然后将database元素从$autoload['libraries']去除。数据库加载单说。
1178行:开始加载所有需要自动加载的library。开始调用Loader的library方法。
195行:library函数的定义。第一个参数是待加载的库的名称,第二、三个参数默认为null。库名参数可以是单个string或者包含多个元素的数组(多维也行)。如果是数组,则要加载多个,这里使用了递归,如果是数组扫描数组继续调用该方法。以单个string库名为例。如果第一个$library参数为空或者已经存在于Loader的数据成员_base_classes(它记录着Loader类通过load_class加载过的类,所以library实际上和core文件夹中的核心类在这儿都归为了基础类)中,则直接返回false。第二个参数默认空,走217行,调用成员方法_ci_load_class执行加载。
880行:_ci_load_class函数定义。
首先,去.php扩展名。然后解析子目录,从890行到898行。关于子目录,就是允许在传递给library方法的类文件名时可以包含路径,例如现在A公司针对XML开发了几个类,针对Mail也开发了有,B公司也做了这些,它们各自使用方式稍有差别,各有好坏,把它们均放在application/libraries下面时,我们就会先建一个A目录,里边放A的各个类,再建个B目录,再放B的各个类,因此可以在加载时这样$this->load->library('A/Xml_1');,这里的A就是子目录。890行到898行做的工作就是提取子目录赋给$subdir变量,提取类名赋给$class变量。
从901行的foreach开始就开始加载了。首先将你传入的名称转为全小写或首字母大写,来查找指定目录下的文件,所以你的library文件名称要么全小写或首字母大写,最好跟CI原有方式保持一致。先是形成一个子类文件路径:$subclass = APPPATH.'libraries/'.$subdir.config_item('subclass_prefix').$class.'.php';,这个子类是在application/libraries下边的,之所以叫子类是因为它是CI自己的library类的扩展,即CI允许我们队原有的工具库进行扩展继承,添加我们自己想要的内容。如果这个子类文件存在的话,形成一个基类文件路径$baseclass = BASEPATH.'libraries/'.ucfirst($class).'.php';,基类就没有什么子目录了,CI的基类不允许你这么干,扩展或改变library类只能放在application/libraries下面,人家是这么规定的。接下来检测基类出现的可能性,如果不存在就报错,子类有父类没有当然错了。如果存在就要来一次安全检查:如果这个文件以前加载过,library方法传入了第三个参数$object_name,这个对象名如果不为空,先调get_instance获取超级对象控制器基类,并且$object_name不是超级对象的成员,就调用Loader类另一个方法_ci_init_class来实例化一次这个类$class(实例化后会加入到控制器基类的属性成员),然后就return了。如果这个文件以前没有加载过,鲜藕加载父类文件和子类文件,并将该文件路径加入到Loader类的一个属性成员中,以记录这个路径的文件被加载过一次(所以前面有检查这个文件是否以前加载过)。
子类、父类文件均加载成功,接下来就调_ci_init_class实例化该类,返回。
944行:注意前面是子类存在的流程,如果不存在呢,刚开始下载一个CI程序,允许默认方法当然没有子类。当然也不会有子目录,$subdir就为空。
扫描Loader类属性成员_ci_library_paths(默认array(APPPATH,BASEPATH))来形成文件路径加载library类,这里的文件路径就正常多了$filepath = $path.'libraries/'.$subdir.$class.'.php';,$subdir默认空,这仍是在找application/libraries和system/libraries里边的文件。如果这个文件以前被加载过则又一次进行安全检查,然后继续在include_once这个文件并且实例化该类,返回。
982行:这是一个比较人性化的处理,它假设你在传参时少传了文件名,子目录不存在,就给深入一层再加载一次(递归),返回。就是加入传入$this->load->library('Xml');,加载的是system/libraries/xml/Xml.php,大致是这样。
如果还能往下走,那就不正常了,报错,996行结束。
回到_ci_autoloader函数,即将加载的是Model模型类,这也是_ci_autoloader自动加载的最后一种类型,它调用的也是model函数,就像我们在控制器类中加载Model一样:$this->load->model('info_model')。
====================================================================================
既然是加载Model,前面又打算把database数据库的加载单说,那我索性就最平常的,一个普通的控制器类,里边加载一个Model,在Model里边加载数据库,并读取数据返回,再加载view视图,这个过程何不就此回忆、梳理一下~
一个简单的自定义控制器类
对应的Model类
设置好数据库选项,一遍连接数据库
视图
控制器类的加载前面已经说过,就从Model类的加载说起。需要注意的一点是,在我们自己定义的,无论是Controller或Model类时,如果有构造函数需要强制(最好是)调用下父类的构造函数(parent::__construct()),前面说过CI_Controller的构造函数都要做些重要的准备工作,如果没有执行极可能出问题(好像不是可能而是一定会),比如CI_Controller会将核心类全部设置为控制器的属性成员,因此我们才可以$this->load->....这样去调用。
上面的自定义控制类中,最先加载的是staff_model模型类,回到system/core/Loader.php中看看model方法。
从大概223行起就是model方法的定义,第一个参数$model就是即将加载的Model类的文件名,它可能包含子路径(在application/models下面的子目录,视图多了我们可能会把它们分类存放),第二个参数$name允许为传入的类取一个别名,默认为空则表示按照CI默认对Model类的命名规则来,即设置为Controller类的成员后将名称全部转为小写,第三个参数$db_conn确定要不要在加载Model时连接数据库,默认为false则不连接,如果为true则会连接默认的数据库配置选项所指向的数据库,这个默认的数据库配置选项就是application/config/database.php中的default数组元素(下图),而上面自定义是我们指定其他的数据库连接信息。
初看到这个model的定义是不是很眼熟,是的,在前面说library的加载时也是这样的。首先检查$model是不是数组,若是数组递归调用该方法,$model为空直接return,250-258行就是解析出子目录(放在application/models的子文件夹下)和只包含类名的文件名,这根library基本一样,$path是子目录,$model是文件名。如果未指定$name则与$model一样。如果这个类已存在于CI_Loader类的成员变量_ci_models中,则直接返回,这个_ci_models是个数组,Loader类每加载一个Model类,均会在_ci_models中有相应的记录,当然这只是针对一次独立的访问而言,就像静态变量一样。
接下来270行调用超级方法,看看它是否跟CI_Controller中已存在的核心类重名,重名报错。接着会将类名转为全小写,从278行开始扫描成员变量_ci_model_paths加载Model类文件。
在Loader的构造函数中_ci_model_paths被初始化为array(APPPATH),意味着是在application下面去寻找的。首先检查$mod_path.'models/'.$path.$model.'.php'(APPPATH.'models/'.$path.$model.'php')是否有该文件,有的话检查$db_conn参数真假,为真则要连接数据库了,调用Loader的database方法,这个还是下面单说。再次检查CI_Model类的定义是否存在,走到这里就是基类CI_Model定义也在,自定义Model类文件也在,于是require_once正式加载这个Model文件。
然后类名变量转为首字母大写,实例化并作为控制器的属性成员,注意这个属性成员的变量名是$name,意味着model方法的第二个参数$name为空时,我们在如$this->a_model调用时a_model名字一定是全小写的,当然在实例化时类名是首字母大写的($CI->$name = new $model();),然后将这个类加入到已加载的类名称的集合数组中($this->_ci_models[] = $name;),加载完了直接return。如果跳到foreach循环外边去了,只有一个结果:error。
model方法就是以上。
回到自定义的控制器类staff_info中,调用staff_model类的getStaffsInfo方法,就是对数据库的简单查询,生成结果返回,看看这个$this->load->database...方法对数据库的加载如何执行。
定位到Loader.php文件大约324行,database方法的定义。
先调get_instance方法获取超级对象,然后检查CI_DB这个类是否出现过(联合一些条件)以判断是否需要加载数据库类,符合if条件则直接return。然后require文件BASEPATH.'database/DB.php',如果传入的第二个参数$return为真的话就返回一个由调用DB构造函数的构造的对象(直接return DB(...),还不是return new DB(...)),所以这里的DB是个方法,举的例子中第二个参数为TRUE,所以返回一个对象。如果$return为假的话,就实例化某个对象,并且作为核心控制器类对象的成员了,正是这样我们才可以$this->db->***这样用。这个数据库对象的实例化就根这个BASEPATH.'database/DB.php'脚本里的方法相关了。
打开system/database/DB.php,只有一个DB方法。

1 <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 2 /** 3 * CodeIgniter 4 * 5 * An open source application development framework for PHP 5.1.6 or newer 6 * 7 * @package CodeIgniter 8 * @author EllisLab Dev Team 9 * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. 10 * @copyright Copyright (c) 2014 - 2015, British Columbia Institute of Technology (http://bcit.ca/) 11 * @license http://codeigniter.com/user_guide/license.html 12 * @link http://codeigniter.com 13 * @since Version 1.0 14 * @filesource 15 */ 16 17 // ------------------------------------------------------------------------ 18 19 /** 20 * Initialize the database 21 * 22 * @category Database 23 * @author EllisLab Dev Team 24 * @link http://codeigniter.com/user_guide/database/ 25 * @param string 26 * @param bool Determines if active record should be used or not 27 */ 28 function &DB($params = '', $active_record_override = NULL) 29 { 30 // Load the DB config file if a DSN string wasn't passed 31 if (is_string($params) AND strpos($params, '://') === FALSE) 32 { 33 // Is the config file in the environment folder? 34 if ( ! defined('ENVIRONMENT') OR ! file_exists($file_path = APPPATH.'config/'.ENVIRONMENT.'/database.php')) 35 { 36 if ( ! file_exists($file_path = APPPATH.'config/database.php')) 37 { 38 show_error('The configuration file database.php does not exist.'); 39 } 40 } 41 42 include($file_path); 43 44 if ( ! isset($db) OR count($db) == 0) 45 { 46 show_error('No database connection settings were found in the database config file.'); 47 } 48 49 if ($params != '') 50 { 51 $active_group = $params; 52 } 53 54 if ( ! isset($active_group) OR ! isset($db[$active_group])) 55 { 56 show_error('You have specified an invalid database connection group.'); 57 } 58 59 $params = $db[$active_group]; 60 } 61 elseif (is_string($params)) 62 { 63 64 /* parse the URL from the DSN string 65 * Database settings can be passed as discreet 66 * parameters or as a data source name in the first 67 * parameter. DSNs must have this prototype: 68 * $dsn = 'driver://username:password@hostname/database'; 69 */ 70 71 if (($dns = @parse_url($params)) === FALSE) 72 { 73 show_error('Invalid DB Connection String'); 74 } 75 76 $params = array( 77 'dbdriver' => $dns['scheme'], 78 'hostname' => (isset($dns['host'])) ? rawurldecode($dns['host']) : '', 79 'username' => (isset($dns['user'])) ? rawurldecode($dns['user']) : '', 80 'password' => (isset($dns['pass'])) ? rawurldecode($dns['pass']) : '', 81 'database' => (isset($dns['path'])) ? rawurldecode(substr($dns['path'], 1)) : '' 82 ); 83 84 // were additional config items set? 85 if (isset($dns['query'])) 86 { 87 parse_str($dns['query'], $extra); 88 89 foreach ($extra as $key => $val) 90 { 91 // booleans please 92 if (strtoupper($val) == "TRUE") 93 { 94 $val = TRUE; 95 } 96 elseif (strtoupper($val) == "FALSE") 97 { 98 $val = FALSE; 99 } 100 101 $params[$key] = $val; 102 } 103 } 104 } 105 106 // No DB specified yet? Beat them senseless... 107 if ( ! isset($params['dbdriver']) OR $params['dbdriver'] == '') 108 { 109 show_error('You have not selected a database type to connect to.'); 110 } 111 112 // Load the DB classes. Note: Since the active record class is optional 113 // we need to dynamically create a class that extends proper parent class 114 // based on whether we're using the active record class or not. 115 // Kudos to Paul for discovering this clever use of eval() 116 117 if ($active_record_override !== NULL) 118 { 119 $active_record = $active_record_override; 120 } 121 122 require_once(BASEPATH.'database/DB_driver.php'); 123 124 if ( ! isset($active_record) OR $active_record == TRUE) 125 { 126 require_once(BASEPATH.'database/DB_active_rec.php'); 127 128 if ( ! class_exists('CI_DB')) 129 { 130 eval('class CI_DB extends CI_DB_active_record { }'); 131 } 132 } 133 else 134 { 135 if ( ! class_exists('CI_DB')) 136 { 137 eval('class CI_DB extends CI_DB_driver { }'); 138 } 139 } 140 141 require_once(BASEPATH.'database/drivers/'.$params['dbdriver'].'/'.$params['dbdriver'].'_driver.php'); 142 143 // Instantiate the DB adapter 144 $driver = 'CI_DB_'.$params['dbdriver'].'_driver'; 145 $DB = new $driver($params); 146 147 if ($DB->autoinit == TRUE) 148 { 149 $DB->initialize(); 150 } 151 152 if (isset($params['stricton']) && $params['stricton'] == TRUE) 153 { 154 $DB->query('SET SESSION sql_mode="STRICT_ALL_TABLES"'); 155 } 156 157 return $DB; 158 } 159 160 161 162 /* End of file DB.php */ 163 /* Location: ./system/database/DB.php */
大约跟着DB方法走一遍,首先判断传入的第一个参数是字符串且其中没有'//'这样的字符,然后判断虽然ENVIRONMENT这个常量定义了,但默认情况下APPPATH.'config/'.ENVIRONMENT.'/database.php'这个路径的文件是不存在的。于是不出意外的话会加载APPPATH.'config/database.php'这个脚本,这是数据库配置信息的脚本,也就是前面举的例子中配置数据库连接选项所在的文件。检验$db变量是否存在,database.php这个配置文件中就是定义的$db这个数组。然后判断传入database方法的第一个参数不为空的话赋给$active_group(该变量定义在数据库配置文件中)。然后判断database.php脚本是否确实定义了$db[$active_group]这个元素,所谓的$active_group就是我们配置好的,可以用来连接需要的数据库的一套变量,规定为数组形式,整个数组的键名是$active_group,例如上边的'test',我连接的是test数组。最后将我们的配置信息数组赋给$params参数。
如果第一个if不成立,进入elseif,前提是传入的第一个参数是字符串,接着使用parse_url方法解析这个字符串,一看就知道parse_url专门解析类似于http://www.****的url,,为何在这儿解析传入的参数?看到注释里说DSN的时候就明白了,第一个参数还可以传入DSN连接字符串,这个可以看看PDO驱动的构造函数传入参数情况,很像,也可看看手册中parse_url解析后都返回些啥。
在这里,解析完后,CI得到的是scheme、host、user、pass、path这些选项,于是这个DSN字符串可能是这样的:mysql://root:password@localhost/test,重点是我们要通过这个字符串解析后得到dbdriver(驱动类型)、hostname(主机名)、username(用户名)、password(密码多少)、database(连的哪个数据库),测试下
右边是结果,注意path选项在CI中去掉了/。这样也得到了想要的信息,而不是通过加载数据库配置文件信息得到的,效果一样,不用也无所谓,反正配置文件信息更清楚嘛。
再来看这个elseif,首先parse_url解析$params,结果为false直接报错。然后解析相应选项,然后检索解析出的数组$dsn中是否有查询字符串($dsn['query']),因为是用解析url的方法,URL中当然可以有以get形式存在的查询字符串,在这里再使用解析查询字符串的方法parse_str,将查询字符串转化为数组形式,是bool型的转为字符串值"TRUE"和"FALSE",其他照旧。然后检查dbdriver选项的有效性,要使用哪种驱动?mysql?mssql?odbc?在这里就决定了,不通过的话当然就show_error了。
接下来从大约112行起,就是加载数据库类了。看看英文注释:因为active record类是可选的($active_record_override这个参数是非必传),我们需要动态的扩展一个合理的父类,基于是否用到active record类,团队中的人发现了eval这个巧妙的方法。
先检查$active_record_override这个参数(一般是不传这个参数的),存在的话将赋给$active_record。如果稍微用过CI的话,这个名字应该不陌生,在CI的数据库中有个玩意儿叫Active Record类(或者叫模式),CI就是靠它来最终对各个对应的数据库类里边的方法调用从而进行读写操作的。
加载system/database/DB_driver.php脚本(require_once(BASEPATH.'database/DB_driver.php')),这是一个总的驱动类文件,看看它的简略注释:这是一个基于平台的数据库基类,这个类不会被直接调用,当然,它是作为一个具体数据库类的适配器,由这个具体的数据库类来扩展和实例化它。看到这儿也还不怎么清楚它要干啥。
接着一个if-else语句,如果$active_record未定义或者为真,就加载system/database/DB_active_rec.php脚本,然后不出意外的话会运行这行 :eval('class CI_DB extends CI_DB_active_record { }')。eval这个函数的妙处就是直接把字符串当做php代码来执行,看它会发生什么直接去掉参数两边的引号就行,就是这样:
class CI_DB extends CI_DB_active_record
{ }
这是定义了一个CI_DB类,它继承自CI_DB_active_record,就是刚加载的文件,而CI_DB_active_record是继承自CI_DB_driver(DB_driver.php中定义)的,再看看system/database下面还有其他的文件,定义了其他的类。
然后会加载system/database/drivers/mysql/mysql_driver.php(BASEPATH.'database/drivers/'.$params['dbdriver'].'/'.$params['dbdriver'].'_driver.php')。
接着实例化mysql驱动类,$driver = 'CI_DB_'.$params['dbdriver'].'_driver'; $DB = new $driver($params);
147行是执行一个初始化方法,这个autoinit属性和initialize方法在CI_DB_driver类中(继承了几个类,得找一下在哪个里边~),做的主要是连接数据库。
OK,这里有几个类显得有点乱,以最常用的mysql为例,箭头所指为子类(UML懒得画了):
在system/database目录下面,可以看到几个文件DB_active_rec.php、DB_driver.php、DB_forge.php、DB_result.php、DB_utility.php,分别对应上图上靠上面的类,其中一个CI_DB类只是个空壳,过渡一下。我们最常用的就是最左边那条线下来的,而我们用到的mysql的驱动方法分布在这几个继承的类和它本身中(如query定义是在DB_driver.php中),当然也可能是子类override了父类的方法,我没全部看过。在database文件夹下边有个drivers子文件夹,里边放的就是各种数据库驱动对应的类文件,其中的mysql子目录就是上图中最下面的一排类。
这些所有的驱动都从system/database/DB.php中的DB方法开始加载和构造,比如例子中的查询,调用Loader类中的database方法后,加载DB.php,加载数据库配置文件,然后依次按照继承顺序加载DB_driver.php、DB_active_rec.php、mysql_driver.php。
=====================================================================================
回到最初举的例子的staff_model中,load玩数据库后,我执行了query方法,它的定义在DB_driver.php中大约251行,接着调用了query方法,你会发现mysql_driver等几个脚本中均没有这个方法,足够耐心的话,你可以看一下这个query方法。在query方法中调用了一个load_rdriver方法
估计你能看出来它加载了CI_DB_result类和CI_DB_mysql_result类,这是对父子类,最后query返回的就是这个CI_DB_mysql_result类,即query生成的结果实际上是一个单独的result类在处理,result_array(定义在DB_result.php中),free_result两个类都有,CI_DB_mysql_result中是真正的释放操作。释放结后就返回结果集,数据库类的调用结束。
对于其他类型数据库驱动,基本跟这一样。
在staff_model中获取完数据后,返回staff_info控制器类中,调用Loader核心类的view方法加载、展现视图。回到Loader.php文件,大约418行,第一个参数是需加载视图的文件名称(可包含子目录),第二个参数是传递给视图脚本的变量数组,第三个参数默认为false,传递true的话,CI将不会加载视图(所以看不到),而是将视图生成字符串内容返回。然后调用Loader类的_ci_load方法。在传递给_ci_load参数时,将前三个参数整合到了一个数组中,视图文件与键_ci_view对应,要使用的变量与键_ci_vars对应(若是对象将转为数组),是否只返回视图与键_ci_return对应。
Loader.php大约742行,则是一个protected访问属性的方法,只能在继承类中使用,还要注意一点带有下划线的前缀的方法,即便是公有访问属性的,也不能通过对象调用,这是CI的规则。一步步来看_ci_load方法。
首先是一个foreach扫描,将对应的键和值,定义为变量的名和值。然后是if检查$_ci_path这个变量是否有值,CI的view方法里传入_ci_load参数时是没有以_ci_path为键的元素的,因此走else。$_ci_ext变量获取传入视图文件名的扩展名,不写扩展名的话这里就是空串了,$_ci_file获取这个视图文件名,如果你传入视图时没有写.php后缀,我上面也没写,这里会给加上该扩展,所以完全不必加扩展名。
接下来是else中的一个foreach扫描,所做的工作就是寻找需要加载的视图,并确定其存在性。可以看到它扫描的是CI_Loader类的_ci_view_paths变量成员,这个成员在初始化是:array(APPPATH.'views/' => TRUE),约莫可以知道它是到application目录里边去找了。通过对这个数组的循环,$_ci_path = $view_file.$_ci_file;拼接一个视图的相对路径出来,确定存在则break跳出循环。如果不存在的话就走到779行的if报错了。
接着从787-794行,获取超级对象CI_Controller实例,扫描它所有的属性成员变量,把它们全部增加为CI_Loader类的属性成员,所以,即便是在view视图中,我们仍然能以$this->load->的形式调用控制器类所拥有的一切成员变量,当然也包括CI_Loader类自身的成员变量和成员方法,这是个非常方便的举措。
805-809行:当视图不是只由一个简单的脚本组成时,比如为了扩展性,前端可能会把一个页面分成head(头部)、中间实体、foot(尾部)三部分,头部和尾部基本相同,变化的是中间实体页面,这时就要保证在中间实体中加载的变量,在尾部中也能使用,这几行代码实现的就是这个效果。将我们传入的一些变量$_ci_vars,与Loader核心类的属性成员_ci_cached_vars进行合并,合并后的数组使用extract方法将键值提取出来,作为定义在当前脚本中的变量名和值,比如头部中有一个$title变量,它已经假如到_ci_cached_vars属性成员中了,当我们再 $this->load->view('body', array(‘var’ => 'val')) 时,这个var键对应的值又会合并到_ci_cached_vars中,再全部定义一遍,反正重复定义没什么影响。这样在中间实体部分仍然可以使用那个在头部中就加载了的$title变量,也可使用后来新加载进来的$var变量。
大约822行到方法结束,就是打开缓冲,加载视图脚本,然后有条件的清空或输出缓冲到页面了。
822行:打开缓存。
828-835行:检查服务器的配置短标记(短标记就是允许php脚本允许以<?开始,而不是最通用的<?php)是否开启,配置文件短标记选项($config['rewrite_short_tags'])是否允许,符合条件的话,将视图内容中的<?=(短标记时这个直接输出)替换为<?php echo ,正则将诸如 ;空格...空格?> 替换为 ;空格; (只有一个空格)的形式,默认是不走if条件的,走else就加载该脚本,呈现视图页面了,在这儿就是真正对视图脚本进行加载。
840行:如果当初传入$this->load->view方法的第三个参数为TRUE的话,就要走这个if语句,就是将整个页面内容返回,ob_get_contents获取这个文本,ob_end_clean清除缓存内容。注意:上面的加载并不能立即呈现页面,因为已经开启了缓存,除非脚本已经结束或输出缓冲。
857-结束:输出/清空缓存,展示页面。如果因为缓冲缓冲区是可以嵌套的,所以有个嵌套层次一说,最外面是1(未打开缓冲时),越嵌入里面层次值越深。如果当前缓冲区层次比Loader核心类的嵌套层次加1还大,就输出缓冲区,否写就附加到Output核心类的一个属性成员上(调用CI_Output类的append_output方法),这就是当有多个脚本共同组成一个页面时,把页面内容进行衔接,然后清除缓冲区。
当然,如果只是ob_start开启缓存了,没有手动关闭,到脚本结束时也会给你关闭掉,页面还是能展示出来滴。
~哎,就到这儿,得结合代码看,否则就一团糟。果然图表就简单明了。