无聊,决定水一把。
CI(CodeIgniter)是我最早接触的一个框架,到现在也只是用了其中一点零碎的方法。一直想对其流程做个小结,却总是因各种各样的“理由”挨着。看见别人图表齐上阵,没那耐心,就从代码说起吧,权当做个笔记,纪念一下。
看在线的用户手册,也知道,将CI下载下来(最新版本2.2.1),解压到机子上,比如www目录,可改个根目录名(原名CodeIgniter-2.2-stable太长),初步目录文件如下,当然这在是windows下面。
访问下,如localhost/ci/index.php,就进入CI默认的Welcome页面
如何一步步加载这个页面的?首先访问的是index.php脚本
<?php /*
*---------------------------------------------------------------
* APPLICATION ENVIRONMENT
*---------------------------------------------------------------
*
* You can load different configurations depending on your
* current environment. Setting the environment also influences
* things like logging and error reporting.
*
* This can be set to anything, but default usage is:
*
* development
* testing
* production
*
* NOTE: If you change these, also change the error_reporting() code below
*
*/
define('ENVIRONMENT', 'development');
/*
*---------------------------------------------------------------
* ERROR REPORTING
*---------------------------------------------------------------
*
* Different environments will require different levels of error reporting.
* By default development will show errors but testing and live will hide them.
*/ if (defined('ENVIRONMENT'))
{
switch (ENVIRONMENT)
{
case 'development':
error_reporting(E_ALL);
break; case 'testing':
case 'production':
error_reporting(0);
break; default:
exit('The application environment is not set correctly.');
}
} /*
*---------------------------------------------------------------
* SYSTEM FOLDER NAME
*---------------------------------------------------------------
*
* This variable must contain the name of your "system" folder.
* Include the path if the folder is not in the same directory
* as this file.
*
*/
$system_path = 'system'; /*
*---------------------------------------------------------------
* APPLICATION FOLDER NAME
*---------------------------------------------------------------
*
* If you want this front controller to use a different "application"
* folder then the default one you can set its name here. The folder
* can also be renamed or relocated anywhere on your server. If
* you do, use a full server path. For more info please see the user guide:
* http://codeigniter.com/user_guide/general/managing_apps.html
*
* NO TRAILING SLASH!
*
*/
$application_folder = 'application'; /*
* --------------------------------------------------------------------
* DEFAULT CONTROLLER
* --------------------------------------------------------------------
*
* Normally you will set your default controller in the routes.php file.
* You can, however, force a custom routing by hard-coding a
* specific controller class/function here. For most applications, you
* WILL NOT set your routing here, but it's an option for those
* special instances where you might want to override the standard
* routing in a specific front controller that shares a common CI installation.
*
* IMPORTANT: If you set the routing here, NO OTHER controller will be
* callable. In essence, this preference limits your application to ONE
* specific controller. Leave the function name blank if you need
* to call functions dynamically via the URI.
*
* Un-comment the $routing array below to use this feature
*
*/
// The directory name, relative to the "controllers" folder. Leave blank
// if your controller is not in a sub-folder within the "controllers" folder
// $routing['directory'] = ''; // The controller class file name. Example: Mycontroller
// $routing['controller'] = ''; // The controller function you wish to be called.
// $routing['function'] = ''; /*
* -------------------------------------------------------------------
* CUSTOM CONFIG VALUES
* -------------------------------------------------------------------
*
* The $assign_to_config array below will be passed dynamically to the
* config class when initialized. This allows you to set custom config
* items or override any default config values found in the config.php file.
* This can be handy as it permits you to share one application between
* multiple front controller files, with each file containing different
* config values.
*
* Un-comment the $assign_to_config array below to use this feature
*
*/
// $assign_to_config['name_of_config_item'] = 'value of config item'; // --------------------------------------------------------------------
// END OF USER CONFIGURABLE SETTINGS. DO NOT EDIT BELOW THIS LINE
// -------------------------------------------------------------------- /*
* ---------------------------------------------------------------
* Resolve the system path for increased reliability
* ---------------------------------------------------------------
*/ // Set the current directory correctly for CLI requests
if (defined('STDIN'))
{
chdir(dirname(__FILE__));
} if (realpath($system_path) !== FALSE)
{
$system_path = realpath($system_path).'/';
} // ensure there's a trailing slash
$system_path = rtrim($system_path, '/').'/'; // Is the system path correct?
if ( ! is_dir($system_path))
{
exit("Your system folder path does not appear to be set correctly. Please open the following file and correct this: ".pathinfo(__FILE__, PATHINFO_BASENAME));
} /*
* -------------------------------------------------------------------
* Now that we know the path, set the main path constants
* -------------------------------------------------------------------
*/
// The name of THIS file
define('SELF', pathinfo(__FILE__, PATHINFO_BASENAME)); // The PHP file extension
// this global constant is deprecated.
define('EXT', '.php'); // Path to the system folder
define('BASEPATH', str_replace("\\", "/", $system_path)); // Path to the front controller (this file)
define('FCPATH', str_replace(SELF, '', __FILE__)); // Name of the "system folder"
define('SYSDIR', trim(strrchr(trim(BASEPATH, '/'), '/'), '/')); // The path to the "application" folder
if (is_dir($application_folder))
{
define('APPPATH', $application_folder.'/');
}
else
{
if ( ! is_dir(BASEPATH.$application_folder.'/'))
{
exit("Your application folder path does not appear to be set correctly. Please open the following file and correct this: ".SELF);
} define('APPPATH', BASEPATH.$application_folder.'/');
} /*
* --------------------------------------------------------------------
* LOAD THE BOOTSTRAP FILE
* --------------------------------------------------------------------
*
* And away we go...
*
*/
require_once BASEPATH.'core/CodeIgniter.php'; /* End of file index.php */
/* 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的核心类目录下的文件了。
=====================================================================================================
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
* CodeIgniter
*
* An open source application development framework for PHP 5.1.6 or newer
*
* @package CodeIgniter
* @author EllisLab Dev Team
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc.
* @copyright Copyright (c) 2014 - 2015, British Columbia Institute of Technology (http://bcit.ca/)
* @license http://codeigniter.com/user_guide/license.html
* @link http://codeigniter.com
* @since Version 1.0
* @filesource
*/ // ------------------------------------------------------------------------ /**
* System Initialization File
*
* Loads the base classes and executes the request.
*
* @package CodeIgniter
* @subpackage codeigniter
* @category Front-controller
* @author EllisLab Dev Team
* @link http://codeigniter.com/user_guide/
*/ /**
* CodeIgniter Version
*
* @var string
*
*/
define('CI_VERSION', '2.2.1'); /**
* CodeIgniter Branch (Core = TRUE, Reactor = FALSE)
*
* @var boolean
*
*/
define('CI_CORE', FALSE); /*
* ------------------------------------------------------
* Load the global functions
* ------------------------------------------------------
*/
require(BASEPATH.'core/Common.php'); /*
* ------------------------------------------------------
* Load the framework constants
* ------------------------------------------------------
*/
if (defined('ENVIRONMENT') AND file_exists(APPPATH.'config/'.ENVIRONMENT.'/constants.php'))
{
require(APPPATH.'config/'.ENVIRONMENT.'/constants.php');
}
else
{
require(APPPATH.'config/constants.php');
} /*
* ------------------------------------------------------
* Define a custom error handler so we can log PHP errors
* ------------------------------------------------------
*/
set_error_handler('_exception_handler'); if ( ! is_php('5.3'))
{
@set_magic_quotes_runtime(0); // Kill magic quotes
} /*
* ------------------------------------------------------
* Set the subclass_prefix
* ------------------------------------------------------
*
* Normally the "subclass_prefix" is set in the config file.
* The subclass prefix allows CI to know if a core class is
* being extended via a library in the local application
* "libraries" folder. Since CI allows config items to be
* overriden via data set in the main index. php file,
* before proceeding we need to know if a subclass_prefix
* override exists. If so, we will set this value now,
* before any classes are loaded
* Note: Since the config file data is cached it doesn't
* hurt to load it here.
*/
if (isset($assign_to_config['subclass_prefix']) AND $assign_to_config['subclass_prefix'] != '')
{
get_config(array('subclass_prefix' => $assign_to_config['subclass_prefix']));
} /*
* ------------------------------------------------------
* Set a liberal script execution time limit
* ------------------------------------------------------
*/
if (function_exists("set_time_limit") == TRUE AND @ini_get("safe_mode") == 0)
{
@set_time_limit(300);
} /*
* ------------------------------------------------------
* Start the timer... tick tock tick tock...
* ------------------------------------------------------
*/
$BM =& load_class('Benchmark', 'core');
$BM->mark('total_execution_time_start');
$BM->mark('loading_time:_base_classes_start'); /*
* ------------------------------------------------------
* Instantiate the hooks class
* ------------------------------------------------------
*/
$EXT =& load_class('Hooks', 'core'); /*
* ------------------------------------------------------
* Is there a "pre_system" hook?
* ------------------------------------------------------
*/
$EXT->_call_hook('pre_system'); /*
* ------------------------------------------------------
* Instantiate the config class
* ------------------------------------------------------
*/
$CFG =& load_class('Config', 'core'); // Do we have any manually set config items in the index.php file?
if (isset($assign_to_config))
{
$CFG->_assign_to_config($assign_to_config);
} /*
* ------------------------------------------------------
* Instantiate the UTF-8 class
* ------------------------------------------------------
*
* Note: Order here is rather important as the UTF-8
* class needs to be used very early on, but it cannot
* properly determine if UTf-8 can be supported until
* after the Config class is instantiated.
*
*/ $UNI =& load_class('Utf8', 'core'); /*
* ------------------------------------------------------
* Instantiate the URI class
* ------------------------------------------------------
*/
$URI =& load_class('URI', 'core'); /*
* ------------------------------------------------------
* Instantiate the routing class and set the routing
* ------------------------------------------------------
*/
$RTR =& load_class('Router', 'core');
$RTR->_set_routing(); // Set any routing overrides that may exist in the main index file
if (isset($routing))
{
$RTR->_set_overrides($routing);
} /*
* ------------------------------------------------------
* Instantiate the output class
* ------------------------------------------------------
*/
$OUT =& load_class('Output', 'core'); /*
* ------------------------------------------------------
* Is there a valid cache file? If so, we're done...
* ------------------------------------------------------
*/
if ($EXT->_call_hook('cache_override') === FALSE)
{
if ($OUT->_display_cache($CFG, $URI) == TRUE)
{
exit;
}
} /*
* -----------------------------------------------------
* Load the security class for xss and csrf support
* -----------------------------------------------------
*/
$SEC =& load_class('Security', 'core'); /*
* ------------------------------------------------------
* Load the Input class and sanitize globals
* ------------------------------------------------------
*/
$IN =& load_class('Input', 'core'); /*
* ------------------------------------------------------
* Load the Language class
* ------------------------------------------------------
*/
$LANG =& load_class('Lang', 'core'); /*
* ------------------------------------------------------
* Load the app controller and local controller
* ------------------------------------------------------
*
*/
// Load the base controller class
require BASEPATH.'core/Controller.php'; function &get_instance()
{
return CI_Controller::get_instance();
} if (file_exists(APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php'))
{
require APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php';
} // Load the local application controller
// Note: The Router class automatically validates the controller path using the router->_validate_request().
// If this include fails it means that the default controller in the Routes.php file is not resolving to something valid.
if ( ! file_exists(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->fetch_class().'.php'))
{
show_error('Unable to load your default controller. Please make sure the controller specified in your Routes.php file is valid.');
} include(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->fetch_class().'.php'); // Set a mark point for benchmarking
$BM->mark('loading_time:_base_classes_end'); /*
* ------------------------------------------------------
* Security check
* ------------------------------------------------------
*
* None of the functions in the app controller or the
* loader class can be called via the URI, nor can
* controller functions that begin with an underscore
*/
$class = $RTR->fetch_class();
$method = $RTR->fetch_method(); if ( ! class_exists($class)
OR strncmp($method, '_', 1) == 0
OR in_array(strtolower($method), array_map('strtolower', get_class_methods('CI_Controller')))
)
{
if ( ! empty($RTR->routes['404_override']))
{
$x = explode('/', $RTR->routes['404_override']);
$class = $x[0];
$method = (isset($x[1]) ? $x[1] : 'index');
if ( ! class_exists($class))
{
if ( ! file_exists(APPPATH.'controllers/'.$class.'.php'))
{
show_404("{$class}/{$method}");
} include_once(APPPATH.'controllers/'.$class.'.php');
}
}
else
{
show_404("{$class}/{$method}");
}
} /*
* ------------------------------------------------------
* Is there a "pre_controller" hook?
* ------------------------------------------------------
*/
$EXT->_call_hook('pre_controller'); /*
* ------------------------------------------------------
* Instantiate the requested controller
* ------------------------------------------------------
*/
// Mark a start point so we can benchmark the controller
$BM->mark('controller_execution_time_( '.$class.' / '.$method.' )_start'); $CI = new $class(); /*
* ------------------------------------------------------
* Is there a "post_controller_constructor" hook?
* ------------------------------------------------------
*/
$EXT->_call_hook('post_controller_constructor'); /*
* ------------------------------------------------------
* Call the requested method
* ------------------------------------------------------
*/
// Is there a "remap" function? If so, we call it instead
if (method_exists($CI, '_remap'))
{
$CI->_remap($method, array_slice($URI->rsegments, 2));
}
else
{
// is_callable() returns TRUE on some versions of PHP 5 for private and protected
// methods, so we'll use this workaround for consistent behavior
if ( ! in_array(strtolower($method), array_map('strtolower', get_class_methods($CI))))
{
// Check and see if we are using a 404 override and use it.
if ( ! empty($RTR->routes['404_override']))
{
$x = explode('/', $RTR->routes['404_override']);
$class = $x[0];
$method = (isset($x[1]) ? $x[1] : 'index');
if ( ! class_exists($class))
{
if ( ! file_exists(APPPATH.'controllers/'.$class.'.php'))
{
show_404("{$class}/{$method}");
} include_once(APPPATH.'controllers/'.$class.'.php');
unset($CI);
$CI = new $class();
}
}
else
{
show_404("{$class}/{$method}");
}
} // Call the requested method.
// Any URI segments present (besides the class/function) will be passed to the method for convenience
call_user_func_array(array(&$CI, $method), array_slice($URI->rsegments, 2));
} // Mark a benchmark end point
$BM->mark('controller_execution_time_( '.$class.' / '.$method.' )_end'); /*
* ------------------------------------------------------
* Is there a "post_controller" hook?
* ------------------------------------------------------
*/
$EXT->_call_hook('post_controller'); /*
* ------------------------------------------------------
* Send the final rendered output to the browser
* ------------------------------------------------------
*/
if ($EXT->_call_hook('display_override') === FALSE)
{
$OUT->_display();
} /*
* ------------------------------------------------------
* Is there a "post_system" hook?
* ------------------------------------------------------
*/
$EXT->_call_hook('post_system'); /*
* ------------------------------------------------------
* Close the DB connection if one exists
* ------------------------------------------------------
*/
if (class_exists('CI_DB') AND isset($CI->db))
{
$CI->db->close();
} /* End of file CodeIgniter.php */
/* 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,单独拿出来如下
<?php
/**
* Loads the main config.php file
*
* This function lets us grab the config file even if the Config class
* hasn't been instantiated yet
*
* @access private
* @return array
*/
if ( ! function_exists('get_config'))
{
function &get_config($replace = array())
{
static $_config; if (isset($_config))
{
return $_config[0];
} // Is the config file in the environment folder?
if ( ! defined('ENVIRONMENT') OR ! file_exists($file_path = APPPATH.'config/'.ENVIRONMENT.'/config.php'))
{
$file_path = APPPATH.'config/config.php';
} // Fetch the config file
if ( ! file_exists($file_path))
{
exit('The configuration file does not exist.');
} require($file_path); // Does the $config array exist in the file?
if ( ! isset($config) OR ! is_array($config))
{
exit('Your config file does not appear to be formatted correctly.');
} // Are any values being dynamically replaced?
if (count($replace) > 0)
{
foreach ($replace as $key => $val)
{
if (isset($config[$key]))
{
$config[$key] = $val;
}
}
} $_config[0] =& $config;
return $_config[0];
}
}
注释说它加载主要的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:
<?php
/**
* Class registry
*
* This function acts as a singleton. If the requested class does not
* exist it is instantiated and set to a static variable. If it has
* previously been instantiated the variable is returned.
*
* @access public
* @param string the class name being requested
* @param string the directory where the class should be found
* @param string the class name prefix
* @return object
*/
if ( ! function_exists('load_class'))
{
function &load_class($class, $directory = 'libraries', $prefix = 'CI_')
{
static $_classes = array(); // Does the class exist? If so, we're done...
if (isset($_classes[$class]))
{
return $_classes[$class];
} $name = FALSE; // Look for the class first in the local application/libraries folder
// then in the native system/libraries folder
foreach (array(APPPATH, BASEPATH) as $path)
{
if (file_exists($path.$directory.'/'.$class.'.php'))
{
$name = $prefix.$class; if (class_exists($name) === FALSE)
{
require($path.$directory.'/'.$class.'.php');
} break;
}
} // Is the request a class extension? If so we load it too
if (file_exists(APPPATH.$directory.'/'.config_item('subclass_prefix').$class.'.php'))
{
$name = config_item('subclass_prefix').$class; if (class_exists($name) === FALSE)
{
require(APPPATH.$directory.'/'.config_item('subclass_prefix').$class.'.php');
}
} // Did we find the class?
if ($name === FALSE)
{
// Note: We use exit() rather then show_error() in order to avoid a
// self-referencing loop with the Excptions class
exit('Unable to locate the specified class: '.$class.'.php');
} // Keep track of what we just loaded
is_loaded($class); $_classes[$class] = new $name();
return $_classes[$class];
}
}
先看它的注释:这个方法作为一个单例,如果被请求的类没有出现过,则该类会被实例化为一个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核心类,前面,说过在加载我们自己的控制器类时,首先就要加载核心控制器类并会调用它的构造函数初始化它,还是看看它,复习下
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
* CodeIgniter
*
* An open source application development framework for PHP 5.1.6 or newer
*
* @package CodeIgniter
* @author EllisLab Dev Team
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc.
* @copyright Copyright (c) 2014 - 2015, British Columbia Institute of Technology (http://bcit.ca/)
* @license http://codeigniter.com/user_guide/license.html
* @link http://codeigniter.com
* @since Version 1.0
* @filesource
*/ // ------------------------------------------------------------------------ /**
* CodeIgniter Application Controller Class
*
* This class object is the super class that every library in
* CodeIgniter will be assigned to.
*
* @package CodeIgniter
* @subpackage Libraries
* @category Libraries
* @author EllisLab Dev Team
* @link http://codeigniter.com/user_guide/general/controllers.html
*/
class CI_Controller { private static $instance; /**
* Constructor
*/
public function __construct()
{
self::$instance =& $this; // Assign all the class objects that were instantiated by the
// bootstrap file (CodeIgniter.php) to local class variables
// so that CI can run as one big super object.
foreach (is_loaded() as $var => $class)
{
$this->$var =& load_class($class);
} $this->load =& load_class('Loader', 'core'); $this->load->initialize(); log_message('debug', "Controller Class Initialized");
} public static function &get_instance()
{
return self::$instance;
}
}
// END Controller class /* End of file Controller.php */
/* 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类,它们不一定是对应的,即有一个加载变量的数组就要有个路径数组,也可能名字不是对应命名的。
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
* CodeIgniter
*
* An open source application development framework for PHP 5.1.6 or newer
*
* @package CodeIgniter
* @author EllisLab Dev Team
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc.
* @copyright Copyright (c) 2014 - 2015, British Columbia Institute of Technology (http://bcit.ca/)
* @license http://codeigniter.com/user_guide/license.html
* @link http://codeigniter.com
* @since Version 1.0
* @filesource
*/ // ------------------------------------------------------------------------ /**
* Loader Class
*
* Loads views and files
*
* @package CodeIgniter
* @subpackage Libraries
* @author EllisLab Dev Team
* @category Loader
* @link http://codeigniter.com/user_guide/libraries/loader.html
*/
class CI_Loader { // All these are set automatically. Don't mess with them.
/**
* Nesting level of the output buffering mechanism
*
* @var int
* @access protected
*/
protected $_ci_ob_level;
/**
* List of paths to load views from
*
* @var array
* @access protected
*/
protected $_ci_view_paths = array();
/**
* List of paths to load libraries from
*
* @var array
* @access protected
*/
protected $_ci_library_paths = array();
/**
* List of paths to load models from
*
* @var array
* @access protected
*/
protected $_ci_model_paths = array();
/**
* List of paths to load helpers from
*
* @var array
* @access protected
*/
protected $_ci_helper_paths = array();
/**
* List of loaded base classes
* Set by the controller class
*
* @var array
* @access protected
*/
protected $_base_classes = array(); // Set by the controller class
/**
* List of cached variables
*
* @var array
* @access protected
*/
protected $_ci_cached_vars = array();
/**
* List of loaded classes
*
* @var array
* @access protected
*/
protected $_ci_classes = array();
/**
* List of loaded files
*
* @var array
* @access protected
*/
protected $_ci_loaded_files = array();
/**
* List of loaded models
*
* @var array
* @access protected
*/
protected $_ci_models = array();
/**
* List of loaded helpers
*
* @var array
* @access protected
*/
protected $_ci_helpers = array();
/**
* List of class name mappings
*
* @var array
* @access protected
*/
protected $_ci_varmap = array('unit_test' => 'unit',
'user_agent' => 'agent'); /**
* Constructor
*
* Sets the path to the view files and gets the initial output buffering level
*/
public function __construct()
{
$this->_ci_ob_level = ob_get_level();
$this->_ci_library_paths = array(APPPATH, BASEPATH);
$this->_ci_helper_paths = array(APPPATH, BASEPATH);
$this->_ci_model_paths = array(APPPATH);
$this->_ci_view_paths = array(APPPATH.'views/' => TRUE); log_message('debug', "Loader Class Initialized");
} // -------------------------------------------------------------------- /**
* Initialize the Loader
*
* This method is called once in CI_Controller.
*
* @param array
* @return object
*/
public function initialize()
{
$this->_ci_classes = array();
$this->_ci_loaded_files = array();
$this->_ci_models = array();
$this->_base_classes =& is_loaded(); $this->_ci_autoloader(); return $this;
} // -------------------------------------------------------------------- /**
* Is Loaded
*
* A utility function to test if a class is in the self::$_ci_classes array.
* This function returns the object name if the class tested for is loaded,
* and returns FALSE if it isn't.
*
* It is mainly used in the form_helper -> _get_validation_object()
*
* @param string class being checked for
* @return mixed class object name on the CI SuperObject or FALSE
*/
public function is_loaded($class)
{
if (isset($this->_ci_classes[$class]))
{
return $this->_ci_classes[$class];
} return FALSE;
} // -------------------------------------------------------------------- /**
* Class Loader
*
* This function lets users load and instantiate classes.
* It is designed to be called from a user's app controllers.
*
* @param string the name of the class
* @param mixed the optional parameters
* @param string an optional object name
* @return void
*/
public function library($library = '', $params = NULL, $object_name = NULL)
{
if (is_array($library))
{
foreach ($library as $class)
{
$this->library($class, $params);
} return;
} if ($library == '' OR isset($this->_base_classes[$library]))
{
return FALSE;
} if ( ! is_null($params) && ! is_array($params))
{
$params = NULL;
} $this->_ci_load_class($library, $params, $object_name);
} // -------------------------------------------------------------------- /**
* Model Loader
*
* This function lets users load and instantiate models.
*
* @param string the name of the class
* @param string name for the model
* @param bool database connection
* @return void
*/
public function model($model, $name = '', $db_conn = FALSE)
{
if (is_array($model))
{
foreach ($model as $babe)
{
$this->model($babe);
}
return;
} if ($model == '')
{
return;
} $path = ''; // Is the model in a sub-folder? If so, parse out the filename and path.
if (($last_slash = strrpos($model, '/')) !== FALSE)
{
// The path is in front of the last slash
$path = substr($model, 0, $last_slash + 1); // And the model name behind it
$model = substr($model, $last_slash + 1);
} if ($name == '')
{
$name = $model;
} if (in_array($name, $this->_ci_models, TRUE))
{
return;
} $CI =& get_instance();
if (isset($CI->$name))
{
show_error('The model name you are loading is the name of a resource that is already being used: '.$name);
} $model = strtolower($model); foreach ($this->_ci_model_paths as $mod_path)
{
if ( ! file_exists($mod_path.'models/'.$path.$model.'.php'))
{
continue;
} if ($db_conn !== FALSE AND ! class_exists('CI_DB'))
{
if ($db_conn === TRUE)
{
$db_conn = '';
} $CI->load->database($db_conn, FALSE, TRUE);
} if ( ! class_exists('CI_Model'))
{
load_class('Model', 'core');
} require_once($mod_path.'models/'.$path.$model.'.php'); $model = ucfirst($model); $CI->$name = new $model(); $this->_ci_models[] = $name;
return;
} // couldn't find the model
show_error('Unable to locate the model you have specified: '.$model);
} // -------------------------------------------------------------------- /**
* Database Loader
*
* @param string the DB credentials
* @param bool whether to return the DB object
* @param bool whether to enable active record (this allows us to override the config setting)
* @return object
*/
public function database($params = '', $return = FALSE, $active_record = NULL)
{
// Grab the super object
$CI =& get_instance(); // Do we even need to load the database class?
if (class_exists('CI_DB') AND $return == FALSE AND $active_record == NULL AND isset($CI->db) AND is_object($CI->db))
{
return FALSE;
} require_once(BASEPATH.'database/DB.php'); if ($return === TRUE)
{
return DB($params, $active_record);
} // Initialize the db variable. Needed to prevent
// reference errors with some configurations
$CI->db = ''; // Load the DB class
$CI->db =& DB($params, $active_record);
} // -------------------------------------------------------------------- /**
* Load the Utilities Class
*
* @return string
*/
public function dbutil()
{
if ( ! class_exists('CI_DB'))
{
$this->database();
} $CI =& get_instance(); // for backwards compatibility, load dbforge so we can extend dbutils off it
// this use is deprecated and strongly discouraged
$CI->load->dbforge(); require_once(BASEPATH.'database/DB_utility.php');
require_once(BASEPATH.'database/drivers/'.$CI->db->dbdriver.'/'.$CI->db->dbdriver.'_utility.php');
$class = 'CI_DB_'.$CI->db->dbdriver.'_utility'; $CI->dbutil = new $class();
} // -------------------------------------------------------------------- /**
* Load the Database Forge Class
*
* @return string
*/
public function dbforge()
{
if ( ! class_exists('CI_DB'))
{
$this->database();
} $CI =& get_instance(); require_once(BASEPATH.'database/DB_forge.php');
require_once(BASEPATH.'database/drivers/'.$CI->db->dbdriver.'/'.$CI->db->dbdriver.'_forge.php');
$class = 'CI_DB_'.$CI->db->dbdriver.'_forge'; $CI->dbforge = new $class();
} // -------------------------------------------------------------------- /**
* Load View
*
* This function is used to load a "view" file. It has three parameters:
*
* 1. The name of the "view" file to be included.
* 2. An associative array of data to be extracted for use in the view.
* 3. TRUE/FALSE - whether to return the data or load it. In
* some cases it's advantageous to be able to return data so that
* a developer can process it in some way.
*
* @param string
* @param array
* @param bool
* @return void
*/
public function view($view, $vars = array(), $return = FALSE)
{
return $this->_ci_load(array('_ci_view' => $view, '_ci_vars' => $this->_ci_object_to_array($vars), '_ci_return' => $return));
} // -------------------------------------------------------------------- /**
* Load File
*
* This is a generic file loader
*
* @param string
* @param bool
* @return string
*/
public function file($path, $return = FALSE)
{
return $this->_ci_load(array('_ci_path' => $path, '_ci_return' => $return));
} // -------------------------------------------------------------------- /**
* Set Variables
*
* Once variables are set they become available within
* the controller class and its "view" files.
*
* @param array
* @param string
* @return void
*/
public function vars($vars = array(), $val = '')
{
if ($val != '' AND is_string($vars))
{
$vars = array($vars => $val);
} $vars = $this->_ci_object_to_array($vars); if (is_array($vars) AND count($vars) > 0)
{
foreach ($vars as $key => $val)
{
$this->_ci_cached_vars[$key] = $val;
}
}
} // -------------------------------------------------------------------- /**
* Get Variable
*
* Check if a variable is set and retrieve it.
*
* @param array
* @return void
*/
public function get_var($key)
{
return isset($this->_ci_cached_vars[$key]) ? $this->_ci_cached_vars[$key] : NULL;
} // -------------------------------------------------------------------- /**
* Load Helper
*
* This function loads the specified helper file.
*
* @param mixed
* @return void
*/
public function helper($helpers = array())
{
foreach ($this->_ci_prep_filename($helpers, '_helper') as $helper)
{
if (isset($this->_ci_helpers[$helper]))
{
continue;
} $ext_helper = APPPATH.'helpers/'.config_item('subclass_prefix').$helper.'.php'; // Is this a helper extension request?
if (file_exists($ext_helper))
{
$base_helper = BASEPATH.'helpers/'.$helper.'.php'; if ( ! file_exists($base_helper))
{
show_error('Unable to load the requested file: helpers/'.$helper.'.php');
} include_once($ext_helper);
include_once($base_helper); $this->_ci_helpers[$helper] = TRUE;
log_message('debug', 'Helper loaded: '.$helper);
continue;
} // Try to load the helper
foreach ($this->_ci_helper_paths as $path)
{
if (file_exists($path.'helpers/'.$helper.'.php'))
{
include_once($path.'helpers/'.$helper.'.php'); $this->_ci_helpers[$helper] = TRUE;
log_message('debug', 'Helper loaded: '.$helper);
break;
}
} // unable to load the helper
if ( ! isset($this->_ci_helpers[$helper]))
{
show_error('Unable to load the requested file: helpers/'.$helper.'.php');
}
}
} // -------------------------------------------------------------------- /**
* Load Helpers
*
* This is simply an alias to the above function in case the
* user has written the plural form of this function.
*
* @param array
* @return void
*/
public function helpers($helpers = array())
{
$this->helper($helpers);
} // -------------------------------------------------------------------- /**
* Loads a language file
*
* @param array
* @param string
* @return void
*/
public function language($file = array(), $lang = '')
{
$CI =& get_instance(); if ( ! is_array($file))
{
$file = array($file);
} foreach ($file as $langfile)
{
$CI->lang->load($langfile, $lang);
}
} // -------------------------------------------------------------------- /**
* Loads a config file
*
* @param string
* @param bool
* @param bool
* @return void
*/
public function config($file = '', $use_sections = FALSE, $fail_gracefully = FALSE)
{
$CI =& get_instance();
$CI->config->load($file, $use_sections, $fail_gracefully);
} // -------------------------------------------------------------------- /**
* Driver
*
* Loads a driver library
*
* @param string the name of the class
* @param mixed the optional parameters
* @param string an optional object name
* @return void
*/
public function driver($library = '', $params = NULL, $object_name = NULL)
{
if ( ! class_exists('CI_Driver_Library'))
{
// we aren't instantiating an object here, that'll be done by the Library itself
require BASEPATH.'libraries/Driver.php';
} if ($library == '')
{
return FALSE;
} // We can save the loader some time since Drivers will *always* be in a subfolder,
// and typically identically named to the library
if ( ! strpos($library, '/'))
{
$library = ucfirst($library).'/'.$library;
} return $this->library($library, $params, $object_name);
} // -------------------------------------------------------------------- /**
* Add Package Path
*
* Prepends a parent path to the library, model, helper, and config path arrays
*
* @param string
* @param boolean
* @return void
*/
public function add_package_path($path, $view_cascade=TRUE)
{
$path = rtrim($path, '/').'/'; array_unshift($this->_ci_library_paths, $path);
array_unshift($this->_ci_model_paths, $path);
array_unshift($this->_ci_helper_paths, $path); $this->_ci_view_paths = array($path.'views/' => $view_cascade) + $this->_ci_view_paths; // Add config file path
$config =& $this->_ci_get_component('config');
array_unshift($config->_config_paths, $path);
} // -------------------------------------------------------------------- /**
* Get Package Paths
*
* Return a list of all package paths, by default it will ignore BASEPATH.
*
* @param string
* @return void
*/
public function get_package_paths($include_base = FALSE)
{
return $include_base === TRUE ? $this->_ci_library_paths : $this->_ci_model_paths;
} // -------------------------------------------------------------------- /**
* Remove Package Path
*
* Remove a path from the library, model, and helper path arrays if it exists
* If no path is provided, the most recently added path is removed.
*
* @param type
* @param bool
* @return type
*/
public function remove_package_path($path = '', $remove_config_path = TRUE)
{
$config =& $this->_ci_get_component('config'); if ($path == '')
{
$void = array_shift($this->_ci_library_paths);
$void = array_shift($this->_ci_model_paths);
$void = array_shift($this->_ci_helper_paths);
$void = array_shift($this->_ci_view_paths);
$void = array_shift($config->_config_paths);
}
else
{
$path = rtrim($path, '/').'/';
foreach (array('_ci_library_paths', '_ci_model_paths', '_ci_helper_paths') as $var)
{
if (($key = array_search($path, $this->{$var})) !== FALSE)
{
unset($this->{$var}[$key]);
}
} if (isset($this->_ci_view_paths[$path.'views/']))
{
unset($this->_ci_view_paths[$path.'views/']);
} if (($key = array_search($path, $config->_config_paths)) !== FALSE)
{
unset($config->_config_paths[$key]);
}
} // make sure the application default paths are still in the array
$this->_ci_library_paths = array_unique(array_merge($this->_ci_library_paths, array(APPPATH, BASEPATH)));
$this->_ci_helper_paths = array_unique(array_merge($this->_ci_helper_paths, array(APPPATH, BASEPATH)));
$this->_ci_model_paths = array_unique(array_merge($this->_ci_model_paths, array(APPPATH)));
$this->_ci_view_paths = array_merge($this->_ci_view_paths, array(APPPATH.'views/' => TRUE));
$config->_config_paths = array_unique(array_merge($config->_config_paths, array(APPPATH)));
} // -------------------------------------------------------------------- /**
* Loader
*
* This function is used to load views and files.
* Variables are prefixed with _ci_ to avoid symbol collision with
* variables made available to view files
*
* @param array
* @return void
*/
protected function _ci_load($_ci_data)
{
// Set the default data variables
foreach (array('_ci_view', '_ci_vars', '_ci_path', '_ci_return') as $_ci_val)
{
$$_ci_val = ( ! isset($_ci_data[$_ci_val])) ? FALSE : $_ci_data[$_ci_val];
} $file_exists = FALSE; // Set the path to the requested file
if ($_ci_path != '')
{
$_ci_x = explode('/', $_ci_path);
$_ci_file = end($_ci_x);
}
else
{
$_ci_ext = pathinfo($_ci_view, PATHINFO_EXTENSION);
$_ci_file = ($_ci_ext == '') ? $_ci_view.'.php' : $_ci_view; foreach ($this->_ci_view_paths as $view_file => $cascade)
{
if (file_exists($view_file.$_ci_file))
{
$_ci_path = $view_file.$_ci_file;
$file_exists = TRUE;
break;
} if ( ! $cascade)
{
break;
}
}
} if ( ! $file_exists && ! file_exists($_ci_path))
{
show_error('Unable to load the requested file: '.$_ci_file);
} // This allows anything loaded using $this->load (views, files, etc.)
// to become accessible from within the Controller and Model functions. $_ci_CI =& get_instance();
foreach (get_object_vars($_ci_CI) as $_ci_key => $_ci_var)
{
if ( ! isset($this->$_ci_key))
{
$this->$_ci_key =& $_ci_CI->$_ci_key;
}
} /*
* Extract and cache variables
*
* You can either set variables using the dedicated $this->load_vars()
* function or via the second parameter of this function. We'll merge
* the two types and cache them so that views that are embedded within
* other views can have access to these variables.
*/
if (is_array($_ci_vars))
{
$this->_ci_cached_vars = array_merge($this->_ci_cached_vars, $_ci_vars);
}
extract($this->_ci_cached_vars); /*
* Buffer the output
*
* We buffer the output for two reasons:
* 1. Speed. You get a significant speed boost.
* 2. So that the final rendered template can be
* post-processed by the output class. Why do we
* need post processing? For one thing, in order to
* show the elapsed page load time. Unless we
* can intercept the content right before it's sent to
* the browser and then stop the timer it won't be accurate.
*/
ob_start(); // If the PHP installation does not support short tags we'll
// do a little string replacement, changing the short tags
// to standard PHP echo statements. if ((bool) @ini_get('short_open_tag') === FALSE AND config_item('rewrite_short_tags') == TRUE)
{
echo eval('?>'.preg_replace("/;*\s*\?>/", "; ?>", str_replace('<?=', '<?php echo ', file_get_contents($_ci_path))));
}
else
{
include($_ci_path); // include() vs include_once() allows for multiple views with the same name
} log_message('debug', 'File loaded: '.$_ci_path); // Return the file data if requested
if ($_ci_return === TRUE)
{
$buffer = ob_get_contents();
@ob_end_clean();
return $buffer;
} /*
* Flush the buffer... or buff the flusher?
*
* In order to permit views to be nested within
* other views, we need to flush the content back out whenever
* we are beyond the first level of output buffering so that
* it can be seen and included properly by the first included
* template and any subsequent ones. Oy!
*
*/
if (ob_get_level() > $this->_ci_ob_level + 1)
{
ob_end_flush();
}
else
{
$_ci_CI->output->append_output(ob_get_contents());
@ob_end_clean();
}
} // -------------------------------------------------------------------- /**
* Load class
*
* This function loads the requested class.
*
* @param string the item that is being loaded
* @param mixed any additional parameters
* @param string an optional object name
* @return void
*/
protected function _ci_load_class($class, $params = NULL, $object_name = NULL)
{
// Get the class name, and while we're at it trim any slashes.
// The directory path can be included as part of the class name,
// but we don't want a leading slash
$class = str_replace('.php', '', trim($class, '/')); // Was the path included with the class name?
// We look for a slash to determine this
$subdir = '';
if (($last_slash = strrpos($class, '/')) !== FALSE)
{
// Extract the path
$subdir = substr($class, 0, $last_slash + 1); // Get the filename from the path
$class = substr($class, $last_slash + 1);
} // We'll test for both lowercase and capitalized versions of the file name
foreach (array(ucfirst($class), strtolower($class)) as $class)
{
$subclass = APPPATH.'libraries/'.$subdir.config_item('subclass_prefix').$class.'.php'; // Is this a class extension request?
if (file_exists($subclass))
{
$baseclass = BASEPATH.'libraries/'.ucfirst($class).'.php'; if ( ! file_exists($baseclass))
{
log_message('error', "Unable to load the requested class: ".$class);
show_error("Unable to load the requested class: ".$class);
} // Safety: Was the class already loaded by a previous call?
if (in_array($subclass, $this->_ci_loaded_files))
{
// Before we deem this to be a duplicate request, let's see
// if a custom object name is being supplied. If so, we'll
// return a new instance of the object
if ( ! is_null($object_name))
{
$CI =& get_instance();
if ( ! isset($CI->$object_name))
{
return $this->_ci_init_class($class, config_item('subclass_prefix'), $params, $object_name);
}
} $is_duplicate = TRUE;
log_message('debug', $class." class already loaded. Second attempt ignored.");
return;
} include_once($baseclass);
include_once($subclass);
$this->_ci_loaded_files[] = $subclass; return $this->_ci_init_class($class, config_item('subclass_prefix'), $params, $object_name);
} // Lets search for the requested library file and load it.
$is_duplicate = FALSE;
foreach ($this->_ci_library_paths as $path)
{
$filepath = $path.'libraries/'.$subdir.$class.'.php'; // Does the file exist? No? Bummer...
if ( ! file_exists($filepath))
{
continue;
} // Safety: Was the class already loaded by a previous call?
if (in_array($filepath, $this->_ci_loaded_files))
{
// Before we deem this to be a duplicate request, let's see
// if a custom object name is being supplied. If so, we'll
// return a new instance of the object
if ( ! is_null($object_name))
{
$CI =& get_instance();
if ( ! isset($CI->$object_name))
{
return $this->_ci_init_class($class, '', $params, $object_name);
}
} $is_duplicate = TRUE;
log_message('debug', $class." class already loaded. Second attempt ignored.");
return;
} include_once($filepath);
$this->_ci_loaded_files[] = $filepath;
return $this->_ci_init_class($class, '', $params, $object_name);
} } // END FOREACH // One last attempt. Maybe the library is in a subdirectory, but it wasn't specified?
if ($subdir == '')
{
$path = strtolower($class).'/'.$class;
return $this->_ci_load_class($path, $params);
} // If we got this far we were unable to find the requested class.
// We do not issue errors if the load call failed due to a duplicate request
if ($is_duplicate == FALSE)
{
log_message('error', "Unable to load the requested class: ".$class);
show_error("Unable to load the requested class: ".$class);
}
} // -------------------------------------------------------------------- /**
* Instantiates a class
*
* @param string
* @param string
* @param bool
* @param string an optional object name
* @return null
*/
protected function _ci_init_class($class, $prefix = '', $config = FALSE, $object_name = NULL)
{
// Is there an associated config file for this class? Note: these should always be lowercase
if ($config === NULL)
{
// Fetch the config paths containing any package paths
$config_component = $this->_ci_get_component('config'); if (is_array($config_component->_config_paths))
{
// Break on the first found file, thus package files
// are not overridden by default paths
foreach ($config_component->_config_paths as $path)
{
// We test for both uppercase and lowercase, for servers that
// are case-sensitive with regard to file names. Check for environment
// first, global next
if (defined('ENVIRONMENT') AND file_exists($path .'config/'.ENVIRONMENT.'/'.strtolower($class).'.php'))
{
include($path .'config/'.ENVIRONMENT.'/'.strtolower($class).'.php');
break;
}
elseif (defined('ENVIRONMENT') AND file_exists($path .'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php'))
{
include($path .'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).'.php');
break;
}
elseif (file_exists($path .'config/'.strtolower($class).'.php'))
{
include($path .'config/'.strtolower($class).'.php');
break;
}
elseif (file_exists($path .'config/'.ucfirst(strtolower($class)).'.php'))
{
include($path .'config/'.ucfirst(strtolower($class)).'.php');
break;
}
}
}
} if ($prefix == '')
{
if (class_exists('CI_'.$class))
{
$name = 'CI_'.$class;
}
elseif (class_exists(config_item('subclass_prefix').$class))
{
$name = config_item('subclass_prefix').$class;
}
else
{
$name = $class;
}
}
else
{
$name = $prefix.$class;
} // Is the class name valid?
if ( ! class_exists($name))
{
log_message('error', "Non-existent class: ".$name);
show_error("Non-existent class: ".$class);
} // Set the variable name we will assign the class to
// Was a custom class name supplied? If so we'll use it
$class = strtolower($class); if (is_null($object_name))
{
$classvar = ( ! isset($this->_ci_varmap[$class])) ? $class : $this->_ci_varmap[$class];
}
else
{
$classvar = $object_name;
} // Save the class name and object name
$this->_ci_classes[$class] = $classvar; // Instantiate the class
$CI =& get_instance();
if ($config !== NULL)
{
$CI->$classvar = new $name($config);
}
else
{
$CI->$classvar = new $name;
}
} // -------------------------------------------------------------------- /**
* Autoloader
*
* The config/autoload.php file contains an array that permits sub-systems,
* libraries, and helpers to be loaded automatically.
*
* @param array
* @return void
*/
private function _ci_autoloader()
{
if (defined('ENVIRONMENT') AND file_exists(APPPATH.'config/'.ENVIRONMENT.'/autoload.php'))
{
include(APPPATH.'config/'.ENVIRONMENT.'/autoload.php');
}
else
{
include(APPPATH.'config/autoload.php');
} if ( ! isset($autoload))
{
return FALSE;
} // Autoload packages
if (isset($autoload['packages']))
{
foreach ($autoload['packages'] as $package_path)
{
$this->add_package_path($package_path);
}
} // Load any custom config file
if (count($autoload['config']) > 0)
{
$CI =& get_instance();
foreach ($autoload['config'] as $key => $val)
{
$CI->config->load($val);
}
} // Autoload helpers and languages
foreach (array('helper', 'language') as $type)
{
if (isset($autoload[$type]) AND count($autoload[$type]) > 0)
{
$this->$type($autoload[$type]);
}
} // A little tweak to remain backward compatible
// The $autoload['core'] item was deprecated
if ( ! isset($autoload['libraries']) AND isset($autoload['core']))
{
$autoload['libraries'] = $autoload['core'];
} // Load libraries
if (isset($autoload['libraries']) AND count($autoload['libraries']) > 0)
{
// Load the database driver.
if (in_array('database', $autoload['libraries']))
{
$this->database();
$autoload['libraries'] = array_diff($autoload['libraries'], array('database'));
} // Load all other libraries
foreach ($autoload['libraries'] as $item)
{
$this->library($item);
}
} // Autoload models
if (isset($autoload['model']))
{
$this->model($autoload['model']);
}
} // -------------------------------------------------------------------- /**
* Object to Array
*
* Takes an object as input and converts the class variables to array key/vals
*
* @param object
* @return array
*/
protected function _ci_object_to_array($object)
{
return (is_object($object)) ? get_object_vars($object) : $object;
} // -------------------------------------------------------------------- /**
* Get a reference to a specific library or model
*
* @param string
* @return bool
*/
protected function &_ci_get_component($component)
{
$CI =& get_instance();
return $CI->$component;
} // -------------------------------------------------------------------- /**
* Prep filename
*
* This function preps the name of various items to make loading them more reliable.
*
* @param mixed
* @param string
* @return array
*/
protected function _ci_prep_filename($filename, $extension)
{
if ( ! is_array($filename))
{
return array(strtolower(str_replace('.php', '', str_replace($extension, '', $filename)).$extension));
}
else
{
foreach ($filename as $key => $val)
{
$filename[$key] = strtolower(str_replace('.php', '', str_replace($extension, '', $val)).$extension);
} return $filename;
}
}
} /* End of file Loader.php */
/* 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方法。
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
* CodeIgniter
*
* An open source application development framework for PHP 5.1.6 or newer
*
* @package CodeIgniter
* @author EllisLab Dev Team
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc.
* @copyright Copyright (c) 2014 - 2015, British Columbia Institute of Technology (http://bcit.ca/)
* @license http://codeigniter.com/user_guide/license.html
* @link http://codeigniter.com
* @since Version 1.0
* @filesource
*/ // ------------------------------------------------------------------------ /**
* Initialize the database
*
* @category Database
* @author EllisLab Dev Team
* @link http://codeigniter.com/user_guide/database/
* @param string
* @param bool Determines if active record should be used or not
*/
function &DB($params = '', $active_record_override = NULL)
{
// Load the DB config file if a DSN string wasn't passed
if (is_string($params) AND strpos($params, '://') === FALSE)
{
// Is the config file in the environment folder?
if ( ! defined('ENVIRONMENT') OR ! file_exists($file_path = APPPATH.'config/'.ENVIRONMENT.'/database.php'))
{
if ( ! file_exists($file_path = APPPATH.'config/database.php'))
{
show_error('The configuration file database.php does not exist.');
}
} include($file_path); if ( ! isset($db) OR count($db) == 0)
{
show_error('No database connection settings were found in the database config file.');
} if ($params != '')
{
$active_group = $params;
} if ( ! isset($active_group) OR ! isset($db[$active_group]))
{
show_error('You have specified an invalid database connection group.');
} $params = $db[$active_group];
}
elseif (is_string($params))
{ /* parse the URL from the DSN string
* Database settings can be passed as discreet
* parameters or as a data source name in the first
* parameter. DSNs must have this prototype:
* $dsn = 'driver://username:password@hostname/database';
*/ if (($dns = @parse_url($params)) === FALSE)
{
show_error('Invalid DB Connection String');
} $params = array(
'dbdriver' => $dns['scheme'],
'hostname' => (isset($dns['host'])) ? rawurldecode($dns['host']) : '',
'username' => (isset($dns['user'])) ? rawurldecode($dns['user']) : '',
'password' => (isset($dns['pass'])) ? rawurldecode($dns['pass']) : '',
'database' => (isset($dns['path'])) ? rawurldecode(substr($dns['path'], 1)) : ''
); // were additional config items set?
if (isset($dns['query']))
{
parse_str($dns['query'], $extra); foreach ($extra as $key => $val)
{
// booleans please
if (strtoupper($val) == "TRUE")
{
$val = TRUE;
}
elseif (strtoupper($val) == "FALSE")
{
$val = FALSE;
} $params[$key] = $val;
}
}
} // No DB specified yet? Beat them senseless...
if ( ! isset($params['dbdriver']) OR $params['dbdriver'] == '')
{
show_error('You have not selected a database type to connect to.');
} // Load the DB classes. Note: Since the active record class is optional
// we need to dynamically create a class that extends proper parent class
// based on whether we're using the active record class or not.
// Kudos to Paul for discovering this clever use of eval() if ($active_record_override !== NULL)
{
$active_record = $active_record_override;
} require_once(BASEPATH.'database/DB_driver.php'); if ( ! isset($active_record) OR $active_record == TRUE)
{
require_once(BASEPATH.'database/DB_active_rec.php'); if ( ! class_exists('CI_DB'))
{
eval('class CI_DB extends CI_DB_active_record { }');
}
}
else
{
if ( ! class_exists('CI_DB'))
{
eval('class CI_DB extends CI_DB_driver { }');
}
} require_once(BASEPATH.'database/drivers/'.$params['dbdriver'].'/'.$params['dbdriver'].'_driver.php'); // Instantiate the DB adapter
$driver = 'CI_DB_'.$params['dbdriver'].'_driver';
$DB = new $driver($params); if ($DB->autoinit == TRUE)
{
$DB->initialize();
} if (isset($params['stricton']) && $params['stricton'] == TRUE)
{
$DB->query('SET SESSION sql_mode="STRICT_ALL_TABLES"');
} return $DB;
} /* End of file DB.php */
/* 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开启缓存了,没有手动关闭,到脚本结束时也会给你关闭掉,页面还是能展示出来滴。
~哎,就到这儿,得结合代码看,否则就一团糟。果然图表就简单明了。