ThinkPHP3.2.3源码分析一之系统流程
整体流程
红色的时钩子,蓝色为配置文件或函数定义文件
一 初始化
主要文件加载
\thinkphp_3.2.3\index.php
\thinkphp_3.2.3\ThinkPHP\ThinkPHP.php
\thinkphp_3.2.3\ThinkPHP\Library\Think\Think.class.php
\thinkphp_3.2.3\ThinkPHP\Library\Think\Storage.class.php
\thinkphp_3.2.3\ThinkPHP\Library\Think\Storage\Driver\File.class.php
简单流程
\index.php
// 检测PHP环境
if(version_compare(PHP_VERSION,'5.3.0','<')) die('require PHP > 5.3.0 !');
// 开启调试模式 建议开发阶段开启 部署阶段注释或者设为falseW
define('APP_DEBUG',True);
// 定义应用目录
define('APP_PATH','./Application/');
// 引入ThinkPHP入口文件
require './ThinkPHP/ThinkPHP.php';
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
\ThinkPHP\ThinkPHP.php
// 记录开始运行时间
$GLOBALS['_beginTime'] = microtime(TRUE);
// 记录内存初始使用
define('MEMORY_LIMIT_ON',function_exists('memory_get_usage'));
if(MEMORY_LIMIT_ON) $GLOBALS['_startUseMems'] = memory_get_usage();
// 系统常量定义
defined('THINK_PATH') or define('THINK_PATH', __DIR__.'/');
defined('APP_PATH') or define('APP_PATH', dirname($_SERVER['SCRIPT_FILENAME']).'/');
defined('APP_STATUS') or define('APP_STATUS', ''); // 应用状态 加载对应的配置文件
defined('APP_DEBUG') or define('APP_DEBUG', false); // 是否调试模式
/// 用宏定义define定义大量的系统状态和系统路径
/// 判断操作系统,php版本,运行方式(cgi,sapi)来特殊处理常量定义
// 加载核心Think类
require CORE_PATH.'Think'.EXT;
// 应用初始化
Think\Think::start();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
\ThinkPHP\Library\Think\Think.class.php
static public function start() {
// 注册AUTOLOAD方法
// 主要时去系统目录找类文件,或从应用配置文件注册的命名空间路径找
// array('Think','Org','Behavior','Com','Vendor')
// C('AUTOLOAD_NAMESPACE');
// 兼容处理不开命名空间的项目
spl_autoload_register('Think\Think::autoload');
// 设定系统错误和异常处理函数注册
// 脚本正常结束或者手动或异常终结处理
// 处理: 1.内存中的日志落地到文件 2.如果是异常终止,则输出错误
register_shutdown_function('Think\Think::fatalError');
//系统致命错误处理
//级别_USER_ERROR的写日志,页面输出提示
//低级别WARN NOTICE 等输出PageTrace,不影响脚本
set_error_handler('Think\Think::appError');
//系统抛出异常处理
//写日志,页面输出
set_exception_handler('Think\Think::appException');
// 初始化文件存储方式
Storage::connect(STORAGE_TYPE);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
\ThinkPHP\Library\Think\Storage.class.php
\ThinkPHP\Library\Think\Storage\Driver\File.class.php (or Sae)
//此处文件缓存类,兼容本地缓存和sae的分布式文件环境。
class Storage {
/**
* 操作句柄
* @var string
* @access protected
*/
static protected $handler ;
/**
* 连接分布式文件系统
* @access public
* @param string $type 文件类型
* @param array $options 配置数组
* @return void
*/
static public function connect($type='File',$options=array()) {
$class = 'Think\\Storage\\Driver\\'.ucwords($type);
self::$handler = new $class($options);
}
static public function __callstatic($method,$args){
/ 魔术方法 调用文件缓存驱动类的方法
//调用缓存驱动的方法
if(method_exists(self::$handler, $method)){
return call_user_func_array(array(self::$handler,$method), $args);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
二 应用模式的编译缓存和配置文件加载
主要文件加载
\thinkphp_3.2.3\ThinkPHP\Mode\common.php
\thinkphp_3.2.3\ThinkPHP\Common\functions.php
\thinkphp_3.2.3\ThinkPHP\Library\Think\Hook.class.php
简单流程
\ThinkPHP\Library\Think\Think.class.php
$runtimefile = RUNTIME_PATH.APP_MODE.'~runtime.php';
// 部署模式并且已缓存的情况下,直接load编译缓存文件,此处的load实质是include文件
if(!APP_DEBUG && Storage::has($runtimefile)){
Storage::load($runtimefile);
}else{
// debug模式或无缓存文件时
// 1.清理可能存在的过期缓存文件
// 2. 加载一堆配置文件,语言包(这里的配置加载到内寸中,一般是存在全局函数作用域的静态变量,或者在类的静态成员变量中)
// 3. 部署模式下,缓存起应用编译文件(编译过程,去掉注释空白,替换预编译的字符串, 写入导入配置的代码段)
// 4 debug模式下,导入debug对应的系统配置,应用配置
if(Storage::has($runtimefile))
Storage::unlink($runtimefile);
$content = '';
// 读取应用模式
$mode = include is_file(CONF_PATH.'core.php')?CONF_PATH.'core.php':MODE_PATH.APP_MODE.'.php';
// 加载核心文件
foreach ($mode['core'] as $file){
if(is_file($file)) {
include $file;
if(!APP_DEBUG) $content .= compile($file);
}
}
// 加载应用模式配置文件
foreach ($mode['config'] as $key=>$file){
is_numeric($key)?C(load_config($file)):C($key,load_config($file));
}
// 读取当前应用模式对应的配置文件
if('common' != APP_MODE && is_file(CONF_PATH.'config_'.APP_MODE.CONF_EXT))
C(load_config(CONF_PATH.'config_'.APP_MODE.CONF_EXT));
// 加载模式别名定义
if(isset($mode['alias'])){
self::addMap(is_array($mode['alias'])?$mode['alias']:include $mode['alias']);
}
// 加载应用别名定义文件
if(is_file(CONF_PATH.'alias.php'))
self::addMap(include CONF_PATH.'alias.php');
// 加载模式行为定义
if(isset($mode['tags'])) {
Hook::import(is_array($mode['tags'])?$mode['tags']:include $mode['tags']);
}
// 加载应用行为定义
if(is_file(CONF_PATH.'tags.php'))
// 允许应用增加开发模式配置定义
Hook::import(include CONF_PATH.'tags.php');
// 加载框架底层语言包
L(include THINK_PATH.'Lang/'.strtolower(C('DEFAULT_LANG')).'.php');
if(!APP_DEBUG){
$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).');}';
Storage::put($runtimefile,strip_whitespace('<?php '.$content));
}else{
// 调试模式加载系统默认的配置文件
C(include THINK_PATH.'Conf/debug.php');
// 读取应用调试配置文件
if(is_file(CONF_PATH.'debug'.CONF_EXT))
C(include CONF_PATH.'debug'.CONF_EXT);
}
}
// 读取当前应用状态对应的配置文件
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'));
// 检查应用目录结构 如果不存在则自动创建
// 生成模块目录和Runtime目录
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');
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
三 App初始化和URL解析,定位模块控制器和操作
主要文件加载
\thinkphp_3.2.3\ThinkPHP\Library\Think\App.class.php
\thinkphp_3.2.3\ThinkPHP\Library\Think\Dispatcher.class.php
\thinkphp_3.2.3\ThinkPHP\Library\Think\Route.class.php
简单流程
\ThinkPHP\Library\Think\Think.class.php
// 运行应用
App::run();
- 1
- 2
\ThinkPHP\Library\Think\App.class.php
/**
* 运行应用实例 入口文件使用的快捷方法
* @access public
* @return void
*/
static public function run()
{
// 应用初始化标签
/**
'app_init' => array(
'Behavior\BuildLiteBehavior', // 生成运行Lite文件
),
* 生成lite文件,用于替换入口文件
*/
Hook::listen('app_init');
// app初始化,见init()
App::init();
// 应用开始标签
/**
* 'app_begin' => array(
'Behavior\ReadHtmlCacheBehavior', // 读取静态缓存
),
* 若打开静态缓存开关,则直接获取缓存文件,输出,终止脚本。
*/
Hook::listen('app_begin');
// Session初始化
if (!IS_CLI) {
session(C('SESSION_OPTIONS'));
}
// 记录应用初始化时间
G('initTime');
/**app执行 :定位到controller action,并实例化执行之
*
* //创建控制器实例
$module = controller(CONTROLLER_NAME, CONTROLLER_PATH);
* $action = ACTION_NAME . C('ACTION_SUFFIX');
*
* //反射类,此处不展开说明
* $method = new \ReflectionMethod($module, $action);
* $class = new \ReflectionClass($module);
*
* self::invokeAction($module, $action);
* 用到反射类API,作用:
* 判断方法公共,静态等
* 前置方法,后置方法,空方法捕获并调用
* 实现控制器方法的参数绑定
* 调用action方法及上述方法
*/
App::exec();
/// ...
}
/**
* 应用程序初始化
* @access public
* @return void
*/
static public function init()
{
// 加载动态应用公共文件和配置
// 加载应用配置文件下非系统定义的配置文件名, C('LOAD_EXT_FILE')) C('LOAD_EXT_CONFIG'))
load_ext_file(COMMON_PATH);
// 日志目录转换为绝对路径 默认情况下存储到公共模块下面
C('LOG_PATH', realpath(LOG_PATH) . '/Common/');
// 定义当前请求的系统常量
define('NOW_TIME', $_SERVER['REQUEST_TIME']);
define('REQUEST_METHOD', $_SERVER['REQUEST_METHOD']);
define('IS_GET', REQUEST_METHOD == 'GET' ? true : false);
define('IS_POST', REQUEST_METHOD == 'POST' ? true : false);
define('IS_PUT', REQUEST_METHOD == 'PUT' ? true : false);
define('IS_DELETE', REQUEST_METHOD == 'DELETE' ? true : false);
// URL调度
// 主要是根据url模式,路由配置,子域名配置等,确认下面3个常量
// 此处不展开说明
/**
* define('MODULE_NAME', defined('BIND_MODULE')? BIND_MODULE : self::getModule($varModule));
* define('CONTROLLER_NAME', defined('BIND_CONTROLLER')? BIND_CONTROLLER : self::getController($varController,$urlCase));
define('ACTION_NAME', defined('BIND_ACTION')? BIND_ACTION : self::getAction($varAction,$urlCase));
*/
Dispatcher::dispatch();
if (C('REQUEST_VARS_FILTER')) {
// 全局安全过滤
array_walk_recursive($_GET, 'think_filter');
array_walk_recursive($_POST, 'think_filter');
array_walk_recursive($_REQUEST, 'think_filter');
}
// URL调度结束标签,官方无动作
Hook::listen('url_dispatch');
define('IS_AJAX', ((isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') || !empty($_POST[C('VAR_AJAX_SUBMIT')]) || !empty($_GET[C('VAR_AJAX_SUBMIT')])) ? true : false);
// TMPL_EXCEPTION_FILE 改为绝对地址
C('TMPL_EXCEPTION_FILE', realpath(C('TMPL_EXCEPTION_FILE')));
return;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
四 Action执行和模板解析
主要文件加载
\thinkphp_3.2.3\ThinkPHP\Library\Think\Controller.class.php
\thinkphp_3.2.3\ThinkPHP\Library\Think\View.class.php
\thinkphp_3.2.3\Application\Home\Controller\IndexController.class.php
\thinkphp_3.2.3\ThinkPHP\Library\Behavior\ParseTemplateBehavior.class.php
\thinkphp_3.2.3\ThinkPHP\Library\Think\Template.class.php
\thinkphp_3.2.3\ThinkPHP\Library\Think\Template\TagLib\Cx.class.php
\thinkphp_3.2.3\ThinkPHP\Library\Think\Template\TagLib.class.php
简单流程
\Application\Home\Controller\IndexController.class.php
namespace Home\Controller;
use Think\Controller;
class IndexController extends Controller {
public function index(){
$this->display();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
\thinkphp_3.2.3\ThinkPHP\Library\Think\Controller.class.php
namespace Think;
abstract class Controller {
/**
* 视图实例对象
* @var view
* @access protected
*/
protected $view = null;
/**
* 控制器参数
* @var config
* @access protected
*/
protected $config = array();
/**
* 架构函数 取得模板对象实例
* @access public
*/
public function __construct() {
// 官方文档此处是输出静态缓存,不过源码是没有绑定行为的,不作处理
Hook::listen('action_begin',$this->config);
//实例化视图类
// Controller类方法display assign fetch show 等都是调用此实例的对应方法
$this->view = Think::instance('Think\View');
//控制器初始化
if(method_exists($this,'_initialize'))
$this->_initialize();
}
/**
* 模板显示 调用内置的模板引擎显示方法,
* @access protected
* @param string $templateFile 指定要调用的模板文件
* 默认为空 由系统自动定位模板文件
* @param string $charset 输出编码
* @param string $contentType 输出类型
* @param string $content 输出内容
* @param string $prefix 模板缓存前缀
* @return void
*/
protected function display($templateFile='',$charset='',$contentType='',$content='',$prefix='') {
$this->view->display($templateFile,$charset,$contentType,$content,$prefix);
}
// 此外此类还用魔术方法实现空操作的跳转和默认模板的调用
// 也实现了页面的重定向和自动跳转
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
\ThinkPHP\Library\Think\View.class.php
/**
* 加载模板和页面输出 可以返回输出内容
* @access public
* @param string $templateFile 模板文件名
* @param string $charset 模板输出字符集
* @param string $contentType 输出类型
* @param string $content 模板输出内容
* @param string $prefix 模板缓存前缀
* @return mixed
*/
public function display($templateFile='',$charset='',$contentType='',$content='',$prefix='') {
G('viewStartTime');
// 视图开始标签,无
Hook::listen('view_begin',$templateFile);
// 解析并获取模板内容 见fetch()
$content = $this->fetch($templateFile,$content,$prefix);
// 输出模板内容
// 此时输出的就是最终页面内容 ob_get_clean();(模板编译完, php执行完)
$this->render($content,$charset,$contentType);
// 视图结束标签 无
Hook::listen('view_end');
}
/**
* 解析和获取模板内容 用于输出
* @access public
* @param string $templateFile 模板文件名
* @param string $content 模板输出内容
* @param string $prefix 模板缓存前缀
* @return string
*/
public function fetch($templateFile='',$content='',$prefix='') {
if(empty($content)) {
//自动定位模板文件
$templateFile = $this->parseTemplate($templateFile);
// 模板文件不存在直接返回
if(!is_file($templateFile)) E(L('_TEMPLATE_NOT_EXIST_').':'.$templateFile);
}else{
defined('THEME_PATH') or define('THEME_PATH', $this->getThemePath());
}
// 页面缓存
ob_start();
ob_implicit_flush(0);
// 使用PHP原生模板 直接 extract include
if('php' == strtolower(C('TMPL_ENGINE_TYPE'))) {
$_content = $content;
// 模板阵列变量分解成为独立变量
extract($this->tVar, EXTR_OVERWRITE);
// 直接载入PHP模板
empty($_content)?include $templateFile:eval('?>'.$_content);
非原生,需要模板编译流程,不extract,而是传参
}else{
// 视图解析标签
$params = array('var'=>$this->tVar,'file'=>$templateFile,'content'=>$content,'prefix'=>$prefix);
/ Behavior\ParseTemplateBehavior 编译模板文件
Hook::listen('view_parse',$params);
}
// 获取并清空缓存
$content = ob_get_clean();
// 内容过滤标签
/**
'template_filter'=> array(
'Behavior\ContentReplaceBehavior', // 模板输出替换
),
* // 系统默认的特殊字符串替换,如js路径 css路径字符串
*/
Hook::listen('view_filter',$content);
// 输出模板文件
return $content;
}
/**
* 输出内容文本可以包括Html
* @access private
* @param string $content 输出内容
* @param string $charset 模板输出字符集
* @param string $contentType 输出类型
* @return mixed
*/
private function render($content,$charset='',$contentType=''){
if(empty($charset)) $charset = C('DEFAULT_CHARSET');
if(empty($contentType)) $contentType = C('TMPL_CONTENT_TYPE');
// 网页字符编码
header('Content-Type:'.$contentType.'; charset='.$charset);
header('Cache-control: '.C('HTTP_CACHE_CONTROL')); // 页面缓存控制
header('X-Powered-By:ThinkPHP');
// 输出模板文件
echo $content;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
\ThinkPHP\Library\Behavior\ParseTemplateBehavior.class.php
/// 编译缓存实质是将模板文件的特定标签替换为php代码,自定义的拓展标签只能用在默认的thinkphp模板引擎。
///此处不展开说明了
class ParseTemplateBehavior {
// 行为扩展的执行入口必须是run
public function run(&$_data){
$engine = strtolower(C('TMPL_ENGINE_TYPE'));
$_content = empty($_data['content'])?$_data['file']:$_data['content'];
$_data['prefix'] = !empty($_data['prefix'])?$_data['prefix']:C('TMPL_CACHE_PREFIX');
if('think'==$engine){ // 采用Think模板引擎
if((!empty($_data['content']) && $this->checkContentCache($_data['content'],$_data['prefix']))
|| $this->checkCache($_data['file'],$_data['prefix'])) { // 缓存有效
//载入模版缓存文件
===== 直接include file
Storage::load(C('CACHE_PATH').$_data['prefix'].md5($_content).C('TMPL_CACHFILE_SUFFIX'),$_data['var']);
}else{
$tpl = Think::instance('Think\\Template');
// 编译并加载模板文件
$tpl->fetch($_content,$_data['var'],$_data['prefix']);
}
}else{
// 调用第三方模板引擎解析和输出
if(strpos($engine,'\\')){
$class = $engine;
}else{
$class = 'Think\\Template\\Driver\\'.ucwords($engine);
}
if(class_exists($class)) {
$tpl = new $class;
$tpl->fetch($_content,$_data['var']);
}else { // 类没有定义
E(L('_NOT_SUPPORT_').': ' . $class);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
五 收尾流程
简单流程
\ThinkPHP\Library\Think\App.class.php
// 应用结束标签
/**
* 'app_end' => array(
'Behavior\ShowPageTraceBehavior', // 页面Trace显示
),
*/
Hook::listen('app_end');
return;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
\ThinkPHP\Library\Think\Think.class.php
// 脚本正常结束或者手动或异常终结处理
// 处理: 1.内存中的日志落地到文件 2.如果是异常终止,则输出错误
register_shutdown_function('Think\Think::fatalError');
// 致命错误捕获
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;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
END
以后有机会再研究和补充此框架源码的机制实现说明,如:
Hook机制,controller反射机制,Model的ORM实现,路由解析定位的实现,模板的编译机制,session方法使用机制,文件编译缓存,PageTrace的使用等,欢迎大家一起探讨学习。