thinkphp源码分析1

开门见山,入口文件,简单配置就不分析了,从think核心引导类开始分析.

文件路径 ThinkPHP\Library\Think\Think.class.php

namespace Think;//定义命名空间class Think { private static $_map = array();//类库别名映射
 private static $_instance = array();//类实例map
 static public function start() {}//应用程序初始化
 static public function addMap($class, $map=''){}// 注册classmap
 static public function getMap($class=''){}// 获取classmap
 public static function autoload($class) {}//类库自动加载
 static public function instance($class,$method='') {}//取得对象实例 支持调用类的静态方法
 static public function appException($e) {}//自定义异常处理
 static public function appError($errno, $errstr, $errfile, $errline) {}//自定义错误处理
 static public function fatalError() {} // 致命错误捕获
 static public function halt($error) {}//错误输出
 static public function trace($value='[think]',$label='',$level='DEBUG',$record=false) {}//添加和获取页面Trace记录
namespace Think;//命名空间
/**
* ThinkPHP 引导类
*/
class Think {

// 类映射存储数组
private static $_map = array();

// 实例化对象存储数组
private static $_instance = array();

/**
* 应用程序初始化
* @access public
* @return void
此方法是tp框架的核心开始方法.
*/
static public function start() {
// 注册AUTOLOAD方法
/*
__autoload 已经不被推荐使用了,推荐使用 spl_autoload_register 来注册加载器。
spl_autoload_register 相关的有一组函数可以更灵活地控制自动加载的具体行为。
__autoload 只能存在一个,如果项目中使用的两个库都有 __autoload 就会出现冲突而无法运行,spl_autoload_register 则没有这个问题,多次使用 spl_autoload_register, 它会按顺序逐个调用加载器。
如果已经使用了 spl_autoload_register, 那么 __autoload 会被忽略,就是将tp实现的自动加载方法注册到SPL __autoload函数栈中数栈中。如果该栈中的函数尚未激活,则激活它们。
*/
spl_autoload_register('Think\Think::autoload');
// 设定错误和异常处理
/*
register_shutdown_function函数功能是捕获错误,进行日志存储,并对错误页面进行输出.可以说是一个析构函数,也可以说是一个钩子函数,他的运行时机是php脚本结束的时候。当我们的脚本执行完成或意外死掉导致PHP执行即将关闭时,我们的这个函数将会 被调用.
 register_shutdown_function 执行机制是:PHP把要调用的函数调入内存。当页面所有PHP语句都执行完成时,再调用此 函数。注意,在这个时候从内存中调用,不是从PHP页面中调用。
 注意:register_shutdown_function是指在执行完所有PHP语句后再调用函数,不要理解成客户端关闭流浏览器页面时调用函数。
 可以这样理解调用条件:
 1、当页面被用户强制停止时
 2、当程序代码运行超时时
 3、当PHP代码执行完成时
 
*/
register_shutdown_function('Think\Think::fatalError');
/*
下面是tp框架使用set_error_handler函数自定义的错误处理函数。报错处理,提示错误的是多少行。
*/
set_error_handler('Think\Think::appError');
/*
set_exception_handler就是设置当你的程序需要抛出一个异常的时候调用哪个自定义的函数。
php中抛异常使用Exception类,使用方法:throw new Execption("this is a new exception");
那么当执行这个语句的时候会自动调用我们转进去的自定义函数。
*/
set_exception_handler('Think\Think::appException');

// 初始化文件存储方式
/*
这是tp的文件操作封装类。但是TP作为一个框架,需要考虑的方面很多。就拿这个文件操作来说,不同的环境实现方式不一样。tp就做了Storage这个类,我们可以在connect方法中传入一个类型参数,在内部,Storage根据参数不同实例化不同的文件操作类并保存其实例。在Think\Storage\Driver下面tp实现了file本地文件操作和sae环境下的文件操作。file类和sae实现类的方法名称都相同,类比于java中可以说是实现了同一个文件接口。这样的好处是我们只需要Storage->hander实例就可以用同一套接口来操作了。
默认的STORAGE_TYPE是file,还有一种是SAE 与普通存储不一样.所以tp写了2个
*/
Storage::connect(STORAGE_TYPE);
//根据当前的环境参数拼出运行时文件目录
$runtimefile = RUNTIME_PATH.APP_MODE.'~runtime.php';
//如果是当前系统不是调试模式并且运行时文件存在,那么当前应为线上模式。我们就直接加载运行时文件。省去了下面诸多步骤,提高效率。
if(!APP_DEBUG && Storage::has($runtimefile)){
Storage::load($runtimefile);
}else{
//如果当前是调试模式,那么判断运行时文件存在不。如果存在就删除,重新构建。
if(Storage::has($runtimefile))
Storage::unlink($runtimefile);
/*
构建开始

*/
$content = '';
// 构建步骤1:根据当前环境的应用模式,读取应用模式配置文件
$mode = include is_file(CONF_PATH.'core.php')?CONF_PATH.'core.php':MODE_PATH.APP_MODE.'.php';
// 构建步骤2:读取配置文件中的core项,加载核心文件
foreach ($mode['core'] as $file){
if(is_file($file)) {
include $file;
if(!APP_DEBUG) $content .= compile($file);
}
}

// 构建步骤3:加载应用模式配置文件
foreach ($mode['config'] as $key=>$file){
is_numeric($key)?C(load_config($file)):C($key,load_config($file));
}

// 构建步骤4:读取当前应用模式对应的配置文件
if('common' != APP_MODE && is_file(CONF_PATH.'config_'.APP_MODE.CONF_EXT))
C(load_config(CONF_PATH.'config_'.APP_MODE.CONF_EXT));

// 构建步骤5:加载模式别名定义
if(isset($mode['alias'])){
self::addMap(is_array($mode['alias'])?$mode['alias']:include $mode['alias']);
}

// 构建步骤6:加载应用别名定义文件
if(is_file(CONF_PATH.'alias.php'))
self::addMap(include CONF_PATH.'alias.php');

// 构建步骤7:加载模式行为定义
if(isset($mode['tags'])) {
Hook::import(is_array($mode['tags'])?$mode['tags']:include $mode['tags']);
}

// 构建步骤8:加载应用行为定义
if(is_file(CONF_PATH.'tags.php'))
// 允许应用增加开发模式配置定义
Hook::import(include CONF_PATH.'tags.php');

// 构建步骤9:加载框架底层语言包
L(include THINK_PATH.'Lang/'.strtolower(C('DEFAULT_LANG')).'.php');

/*
如果当前不是处于调试模式,创建运行时文件并写入编译好的代码。
这里使用php5.3以后的命名空间特性。参考http://developer.51cto.com/art/200907/137746.htm
*/
if(!APP_DEBUG){
//namespace {}这种方式用于声明代码块中的命名空间属于全局命名空间,这句代码用于生成加载别名映射的php代码
$content .= "\nnamespace { Think\Think::addMap(".var_export(self::$_map,true).");";
//生成语言加载代码,生成配置项加载代码,生成钩子加载代码
$content .= "\nL(".var_export(L(),true).");\nC(".var_export(C(),true).');Think\Hook::import('.var_export(Hook::get(),true).');}';
//将$content变量内容去除注释和换行、空隔之后写入到运行时编译缓存文件
Storage::put($runtimefile,strip_whitespace('<?php '.$content));
}else{
// 调试模式加载系统默认的配置文件
C(include THINK_PATH.'Conf/debug.php');
// 有debug配置文件就读取应用调试配置文件
if(is_file(CONF_PATH.'debug'.CONF_EXT))
C(include CONF_PATH.'debug'.CONF_EXT);
}
}

// 读取当前应用状态对应的配置文件.根据APP_STATUS读取当前部署环境配置文件,常用在上线前数据库连接配置等,用于覆盖默认配置行为
if(APP_STATUS && is_file(CONF_PATH.APP_STATUS.CONF_EXT))
C(include CONF_PATH.APP_STATUS.CONF_EXT);

// 设置系统时区
date_default_timezone_set(C('DEFAULT_TIMEZONE'));

// 检查应用目录结构 如果不存在则自动创建
if(C('CHECK_APP_DIR')) {
$module = defined('BIND_MODULE') ? BIND_MODULE : C('DEFAULT_MODULE');
if(!is_dir(APP_PATH.$module) || !is_dir(LOG_PATH)){
// 检测应用目录结构
Build::checkDir($module);
}
}

// 记录加载文件时间
G('loadTime');
// 运行应用
App::run();
}

// 注册classmap
/*
addMap和getMap以及$_map主要用来实现tp官方所说的类映射功能。主要用来解决命名空间较多带来的效率问题。我们可以通过此函数把常用的命名空间和类文件实际路径进行映射,这样php就不用再去寻找路径了,提高了运行效率。$_map是一个数组,键为命名空间字符串,值为对应的路径信息。
addMap有两种使用方法,如果我们只是添加一个映射关系,那么Think\Think::addMap('Think\Log',THINK_PATH.'Think\Log.php');
如果我们要批量添加,第二个参数就不需要了。我们可以直接添加一个映射数组。从下面的实现代码中我们就可以看到,如果$class是一个数组的话,系统会直接和$_map数组进行合并。如果不是一个数组,就把$class和$map分别当做$_map数组的键和值。
*/
static public function addMap($class, $map=''){
if(is_array($class)){
self::$_map = array_merge(self::$_map, $class);
}else{
self::$_map[$class] = $map;
}
}

// 获取classmap
/*
根据键从$_map数组中取得对应的路径信息
*/
static public function getMap($class=''){
if(''===$class){
return self::$_map;
}elseif(isset(self::$_map[$class])){
return self::$_map[$class];
}else{
return null;
}
}

/**
* 类库自动加载
* @param string $class 对象类名
* @return void
*/
public static function autoload($class) {
// 优先级1:检查是否存在映射,如果有的话就加载对应的文件路径
if(isset(self::$_map[$class])) {
include self::$_map[$class];
}
//优先级2:检查命名空间,如果存在\,就取\之前的字符串$name,如果$name是Library文件夹下的任何一个子文件夹,就可以定位$oath为当前的类库路径。即Library。
elseif(false !== strpos($class,'\\')){
$name = strstr($class, '\\', true);
if(in_array($name,array('Think','Org','Behavior','Com','Vendor')) || is_dir(LIB_PATH.$name)){
//  Library目录下面的命名空间自动定位
$path = LIB_PATH;
}else{
//优先级3: 检测自定义命名空间 否则就以模块为命名空间
/*
一般来说自带类都会写在Library文件夹下,所以先从Library文件夹下去搜索。
但是我们也可以不写在Library文件夹下。我们可以自己在Thinkphp文件夹下创建一个和Library平级的文件夹,tp把他叫做创建了一个新的命名空间。
当我们检测到存在此文件夹,就返回其所在的目录。如果不存在,
我们就进入优先级4:$path = APP_PATH. 则会当作模块的命名空间进行自动加载
*/
$namespace = C('AUTOLOAD_NAMESPACE');
$path = isset($namespace[$name])? dirname($namespace[$name]).'/' : APP_PATH;
}
/*
经过上面的四个顺序后,我们就会得到$path
接下来就拿$path和命名空间路径来拼凑真正对应的文件路径。
*/
$filename = $path . str_replace('\\', '/', $class) . EXT;
/*
检测如果$filename是文件,win环境下区分大小写
*/
if(is_file($filename)) {
// Win环境下面严格区分大小写
if (IS_WIN && false === strpos(str_replace('/', '\\', realpath($filename)), $class . EXT)){
return ;
}
include $filename;
}
}elseif (!C('APP_USE_NAMESPACE')) {
// 自动加载的类库层
/*
tp在配置文件中可以通过APP_USE_NAMESPACE来定义是否使用5.3的新特性命名空间。
如果你配置为false,那么tp就需要你再配置一个APP_AUTOLOAD_LAYER或者 APP_AUTOLOAD_PATH来告诉tp应该去哪个文件夹下去寻找你要使用的类。
*/
foreach(explode(',',C('APP_AUTOLOAD_LAYER')) as $layer){
if(substr($class,-strlen($layer))==$layer){
if(require_cache(MODULE_PATH.$layer.'/'.$class.EXT)) {
return ;
}
}
}
// 根据自动加载路径设置进行尝试搜索
/*这里就是在没有使用命名空间的情况下使用APP_AUTOLOAD_PATH来定义要加载类的路径*/
foreach (explode(',',C('APP_AUTOLOAD_PATH')) as $path){
if(import($path.'.'.$class))
// 如果加载类成功则返回
return ;
}
}
}

/**
* 取得对象实例 支持调用类的静态方法
* @param string $class 对象类名
* @param string $method 类的静态方法名
* @return object
此方法的作用就是你传进去一个类,他给你返回一个该类的实例,保存在$_instance数组中,数组键为$identify,值为对应的实例。此外,此方法还支持调用类的静态方法,如果你除了传入类名外,还传入了一个该类所有的方法名,那么instance方法返回的就不会类所对应的实例了,而是返回执行了类的静态方法后,该静态方法返回的值。
*/
static public function instance($class,$method='') {
//类的唯一标识,用类名和方法名组成
$identify = $class.$method;
//检测$_instance变量中是否存在改标识,如果不存在,那么再检查类是否存在,如果存在就实例化。在类存在的前提下去检查方法是否存在,如果方法也存在就调用并返回方法的执行结果给$_instance.如果方法不存在,就直接返回类的实例给$_instace.
if(!isset(self::$_instance[$identify])) {
if(class_exists($class)){
$o = new $class();
if(!empty($method) && method_exists($o,$method))
self::$_instance[$identify] = call_user_func(array(&$o, $method));
else
self::$_instance[$identify] = $o;
}
else
//如果加载不到类(命名空间,类名大小写是否按规范问题)或者文件不存在,那么输出错误信息。
self::halt(L('_CLASS_NOT_EXIST_').':'.$class);
}
return self::$_instance[$identify];
}

/**
* 自定义异常处理
* @access public
* @param mixed $e 异常对象
改函数由set_exception_handler调用
*/
static public function appException($e) {
$error = array();
//获得异常错误信息
$error['message'] = $e->getMessage();
/*得到异常的错误信息数组字符串
一般的格式如下:
array(1) {
 [0]=> array(6) {
 ["file"]=> string(54) "/....../test.php"
 ["line"]=> int(37)
 ["function"]=> string(11) "__construct"
 ["class"]=> string(4) "Test"
 ["type"]=> string(2) "->"
 ["args"]=> array(0) { }
 }
}
*/
$trace = $e->getTrace();
/*
tp的公共函数库中有一个函数E,专门给开发者使用。用于让开发者抛出异常。所以这里首先检查第一个函数是不是E,如果是的话返回错误文件和错误行数
*/
if('E'==$trace[0]['function']) {
$error['file'] = $trace[0]['file'];
$error['line'] = $trace[0]['line'];
}else{
/*
如果不是的话直接返回错误文件和行数
*/
$error['file'] = $e->getFile();
$error['line'] = $e->getLine();
}
$error['trace'] = $e->getTraceAsString();
/*
记录错误信息到日志中
*/
Log::record($error['message'],Log::ERR);
// 发送404信息
header('HTTP/1.1 404 Not Found');
header('Status:404 Not Found');
self::halt($error);
}

/**
* 自定义错误处理
* @access public
* @param int $errno 错误类型
* @param string $errstr 错误信息
* @param string $errfile 错误文件
* @param int $errline 错误行数
* @return void
*/
static public function appError($errno, $errstr, $errfile, $errline) {
switch ($errno) {
case E_ERROR:
case E_PARSE:
case E_CORE_ERROR:
case E_COMPILE_ERROR:
case E_USER_ERROR:
/*
清空输出缓冲, 其实就是把php默认的错误输出给清除掉
*/
ob_end_clean();
$errorStr = "$errstr ".$errfile." 第 $errline 行.";
/*
根据LOG_RECORD是否记录错误信息,决定是否写入错误日志
*/
if(C('LOG_RECORD')) Log::write("[$errno] ".$errorStr,Log::ERR);
self::halt($errorStr);
break;
default:
$errorStr = "[$errno] $errstr ".$errfile." 第 $errline 行.";
self::trace($errorStr,'','NOTIC');
break;
}
}

// 致命错误捕获
static public function fatalError() {
Log::save();
if ($e = error_get_last()) {
switch($e['type']){
case E_ERROR:
case E_PARSE:
case E_CORE_ERROR:
case E_COMPILE_ERROR:
case E_USER_ERROR:
ob_end_clean();
self::halt($e);
break;
}
}
}

/**
* 错误输出
* @param mixed $error 错误
* @return void
*/
static public function halt($error) {
$e = array();
if (APP_DEBUG || IS_CLI) {
//调试模式下输出错误信息
if (!is_array($error)) {
$trace = debug_backtrace();
$e['message'] = $error;
$e['file'] = $trace[0]['file'];
$e['line'] = $trace[0]['line'];
ob_start();
debug_print_backtrace();
$e['trace'] = ob_get_clean();
} else {
$e = $error;
}
if(IS_CLI){
exit(iconv('UTF-8','gbk',$e['message']).PHP_EOL.'FILE: '.$e['file'].'('.$e['line'].')'.PHP_EOL.$e['trace']);
}
} else {
//否则定向到错误页面
$error_page = C('ERROR_PAGE');
if (!empty($error_page)) {
redirect($error_page);
} else {
$message = is_array($error) ? $error['message'] : $error;
$e['message'] = C('SHOW_ERROR_MSG')? $message : C('ERROR_MESSAGE');
}
}
// 包含异常页面模板
$exceptionFile = C('TMPL_EXCEPTION_FILE',null,THINK_PATH.'Tpl/think_exception.tpl');
include $exceptionFile;
exit;
}

/**
* 添加和获取页面Trace记录
* @param string $value 变量
* @param string $label 标签
* @param string $level 日志级别(或者页面Trace的选项卡)
* @param boolean $record 是否记录日志
* @return void
*/
static public function trace($value='[think]',$label='',$level='DEBUG',$record=false) {
//采用静态变量存储错误信息
static $_trace = array();
if('[think]' === $value){ // 获取trace信息
return $_trace;
}else{
$info = ($label?$label.':':'').print_r($value,true);
//将错误级别转为大写
$level = strtoupper($level);
//如果是ajax请求那么就不显示trace调试工具或者就记录到日志中区
if((defined('IS_AJAX') && IS_AJAX) || !C('SHOW_PAGE_TRACE') || $record) {
Log::record($info,$level,$record);
}else{
//判断错误等级是否存在或者该类错误信息是否达到错误类型记录上限,由TRACE_MAX_RECORD配置.如果超额,重置错误信息数组
if(!isset($_trace[$level]) || count($_trace[$level])>C('TRACE_MAX_RECORD')) {
$_trace[$level] = array();
}
$_trace[$level][] = $info;
}
}
}
}

nickname
content