ThinkPHP 3.2源码分析——从URL到系统设置

【一、架构简述】

可能一开始就开始分析源码的结构可能很多人不明白,那我就先大概说说thinkphp开发者的设计思路吧。大概可以分为这几个阶段:

1、依赖检测阶段

在这阶段里主要检测当前的环境是否满足要求,主要指php的版本等等。

2、参数加载阶段

在这个阶段主要是将目前框架所需要的参数 组成一个参数依赖数组。方便程序统一调用。

3、系统运行设置

该阶段会依赖加载的参数进行系统的全面设置

4、加载切片函数和用户自定义控制器模块

在这一阶段由于thinkphp多切片的原理,使得框架可以在很多阶段插入程序钩子,相信钩子很多人并不陌生。


【二、tp3.2的依赖检测阶段】

在index.php里面主要执行的就是依赖的检测,首先执行的就是php版本检测

// 检测PHP环境,如果php版本小于5.3程序终止
if(version_compare(PHP_VERSION,'5.3.0','<')) 
{
	die('require PHP > 5.3.0 !');
} 

这部分就这么结束了!


【三、tp3.2的参数加载阶段】

首先先定义app_debug模式,方便快速修改

// 开启调试模式 建议开发阶段开启 部署阶段注释或者设为false
define('APP_DEBUG',True);

再定义Application目录位置

// 定义应用目录
define('APP_PATH','./Application/');

最后引入thinkphp入口文件,index.php就算结束了。

// 引入ThinkPHP入口文件
require './ThinkPHP/ThinkPHP.php';

接下来是ThinkPHP.php开始执行

// 这里记录内存的使用,通过的检测函数memory_get_usage是否存在
//	如果函数可用将内存开始使用的位置保存全局函数 GLOBALS 数组中

define('MEMORY_LIMIT_ON',function_exists('memory_get_usage'));
if(MEMORY_LIMIT_ON) 
{
	$GLOBALS['_startUseMems'] = memory_get_usage();
}
//-----------------------------------------
//------接下来开始疯狂的定义框架的设置参数-------
//-----------------------------------------

// 定义tp框架的版本信息

const THINK_VERSION     =   '3.2.0';
// 定义 URL 模式
const URL_COMMON        =   0;  //普通模式
const URL_PATHINFO      =   1;  //PATHINFO模式
const URL_REWRITE       =   2;  //REWRITE模式
const URL_COMPAT        =   3;  // 兼容模式
// 设置类文件的后缀
const EXT               =   '.class.php'; 
//-------------------------
// 定义 THINK_PATH 的文件目录
//-------------------------

if(!defined('THINK_PATH')) 
{
	define('THINK_PATH',     __DIR__.'/');
}
//--------------------
//定义 APP_PATH 文件路径
//---------------------
if(!defined('APP_PATH') )
{
	define('APP_PATH',      dirname($_SERVER['SCRIPT_FILENAME']).'/');
}

//------------------------
// 应用状态 加载对应的配置文件
//-------------------------

if(!defined('APP_STATUS'))
{
	define('APP_STATUS',     ''); 
}
//-----------
// 是否调试模式
//------------

if(!defined('APP_DEBUG'))
{
	define('APP_DEBUG',      false); 
} 	 
//----------------
// 自动识别SAE环境
//	ps: sae环境我曾询问过了解的大牛们,用的其实不多,主要就是新浪的服务器可忽略;
//  在这里如果不是sae环境(阿里云,腾讯云基本都不是)系统会定义APP的运行模式为common
//  缓存文件的储存方式定义为  File
//-----------------

if(function_exists('saeAutoLoader'))
{
    defined('APP_MODE')     or define('APP_MODE',      'sae');
    defined('STORAGE_TYPE') or define('STORAGE_TYPE',  'Sae');
}
else
{
	
    defined('APP_MODE')     or define('APP_MODE',       'common'); // 应用模式 默认为普通模式    
    defined('STORAGE_TYPE') or define('STORAGE_TYPE',   'File'); // 存储类型 默认为File    
}
//-----------------
// 定义系统运行时目录
//------------------

if(!defined('RUNTIME_PATH'))
{
	define('RUNTIME_PATH',   APP_PATH.'Runtime/');   
} 
//----------------
// 系统核心类库目录
//-----------------

if(!defined('LIB_PATH'))
{
	define('LIB_PATH',       realpath(THINK_PATH.'Library').'/'); 
}
//--------------
// Think类库目录
//---------------

if(!defined('CORE_PATH'))
{
	define('CORE_PATH',      LIB_PATH.'Think/'); 
}
//-------------
// 行为类库目录
// 就是钩子所在的目录
//--------------

if(!defined('BEHAVIOR_PATH'))
{
	define('BEHAVIOR_PATH',  LIB_PATH.'Behavior/'); 
}
//----------------------------------
//	由于定义的太多了请允许我偷懒一下,哈哈
//	大家不妨可以纸笔记录下定义的内容方便后面快速查看
// 在这里解释一下 or 在其前面的函数如果返回false 则执行后者,如果前者为true则后者不执行
//----------------------------------
// 系统应用模式目录
defined('MODE_PATH')    or define('MODE_PATH',      THINK_PATH.'Mode/'); 

// 第三方类库目录
defined('VENDOR_PATH')  or define('VENDOR_PATH',    LIB_PATH.'Vendor/'); 

// 应用公共目录
defined('COMMON_PATH')  or define('COMMON_PATH',    APP_PATH.'Common/'); 

// 应用配置目录
defined('CONF_PATH')    or define('CONF_PATH',      COMMON_PATH.'Conf/'); 

// 应用语言目录
defined('LANG_PATH')    or define('LANG_PATH',      COMMON_PATH.'Lang/'); 

// 应用静态目录
defined('HTML_PATH')    or define('HTML_PATH',      APP_PATH.'Html/'); 

// 应用日志目录
defined('LOG_PATH')     or define('LOG_PATH',       RUNTIME_PATH.'Logs/'); 

// 应用缓存目录
defined('TEMP_PATH')    or define('TEMP_PATH',      RUNTIME_PATH.'Temp/'); 

// 应用数据目录
defined('DATA_PATH')    or define('DATA_PATH',      RUNTIME_PATH.'Data/'); 

// 应用模板缓存目录
defined('CACHE_PATH')   or define('CACHE_PATH',     RUNTIME_PATH.'Cache/'); 
//----------------------
// 系统信息:设置系统是否将传来的数据中的'"\加上反斜线参数
//-----------------------

if(version_compare(PHP_VERSION,'5.4.0','<')) 
{
    ini_set('magic_quotes_runtime',0);
    define('MAGIC_QUOTES_GPC',get_magic_quotes_gpc()?True:False);
}
else
{
    define('MAGIC_QUOTES_GPC',false);
}
//------------
//是否为CGI模式
//------------

define('IS_CGI',substr(PHP_SAPI, 0,3)=='cgi' ? 1 : 0 );
//----------------
//是否为windows环境
//----------------

define('IS_WIN',strstr(PHP_OS, 'WIN') ? 1 : 0 );
//----------------
//是否为CLI模式
//----------------

define('IS_CLI',PHP_SAPI=='cli'? 1   :   0);
//-----------------------
// 如果是CGI模式(网页模式)定义_PHP_FILE_
// 参数$_SERVER可参考 https://zhidao.baidu.com/question/99055936.html
//	这里的 _PHP_FILE_ 框架不改造的情况下通常输出的为  /index.php
//-----------------------

if(!IS_CLI) {
    // 当前文件名
    if(!defined('_PHP_FILE_')) 
    {
        if(IS_CGI)
         {
            //CGI/FASTCGI模式下
            $_temp  = explode('.php',$_SERVER['PHP_SELF']);
            define('_PHP_FILE_',    rtrim(str_replace($_SERVER['HTTP_HOST'],'',$_temp[0].'.php'),'/'));
        }
        else 
        {
            define('_PHP_FILE_',    rtrim($_SERVER['SCRIPT_NAME'],'/'));
        }
    }

    //------------------------------------
	//定义__ROOT__:根据_PHP_FILE_所在的文件夹
	// 不改造的情况下通常为根目录
	//------------------------------------
    if(!defined('__ROOT__')) {
        $_root  =   rtrim(dirname(_PHP_FILE_),'/');
        define('__ROOT__',  (($_root=='/' || $_root=='\\')?'':$_root));
    }
}
//----------------
// 到这里大部分的框架设置工作已经完成了
// 开始加载核心Think类
//----------------

require CORE_PATH.'Think'.EXT;
//----------
// 应用初始化
//----------- 
Think\Think::start();

【三、系统运行设置】

该配置为thinkphp的核心设计,下面我们一步步解读:

	//----------------------------------------------------------
	//首先架构设计了自动加载类的映射和实例化对象。使得系统更好的复用类和对象。
	//------------------------------------------------------------
	
    // 类映射数组,稍后会详细介绍,这里知道有这个存在就行
    private static $_map      = array();

    // 实例化对象,符合设计原则的单例模式。
    private static $_instance = array();

由于上一章节最后程序是调用的 Think\Think::start();函数,下面我们从该函数开始:

//-------------------
// 注册AUTOLOAD方法
//由于该方法我之前写过一篇文章,不再讲解
//------贴出连接-------
//https://blog.csdn.net/weixin_44187959/article/details/91864086
//--------------------

spl_autoload_register('Think\Think::autoload');    
//----------------------------
// 设定错误导致程序退出时的处理规则
// 如果有对错误处理不太了解的可以参考我下面的连接。
// https://blog.csdn.net/weixin_44187959/article/details/90750562
//----------------------------

register_shutdown_function('Think\Think::fatalError');

// 贴出方法内容,tp3对错误的处理根据错误级别处理包含,存入日志,报出行号、错误信息等等。
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;
         }
     }
 }
//-------------------------------------
// 设置发生错误时候 程序所做的处理,方法和shutdown方法类似,输出错误信息和行号等等。
//-------------------------------------

set_error_handler('Think\Think::appError');

//贴出处理的顺序,不做过多的解读,应该很简单,大家都看得懂。
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:
        ob_end_clean();
        $errorStr = "$errstr ".$errfile." 第 $errline 行.";
        if(C('LOG_RECORD')) Log::write("[$errno] ".$errorStr,Log::ERR);
        self::halt($errorStr);
        break;
      case E_STRICT:
      case E_USER_WARNING:
      case E_USER_NOTICE:
      default:
        $errorStr = "[$errno] $errstr ".$errfile." 第 $errline 行.";
        self::trace($errorStr,'','NOTIC');
        break;
  }
}
//--------------
//最后是处理异常
//---------------

set_exception_handler('Think\Think::appException');


//同样贴出方法,具体过程也不多说,大家都看得懂。
static public function appException($e) {
    $error = array();
    $error['message']   =   $e->getMessage();
    $trace              =   $e->getTrace();
    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);
}
//-----------------
// 初始化文件存储方式,这里的STORAGE_TYPE已经定义为File
//-----------------

 Storage::connect(STORAGE_TYPE);



//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//这里属于延伸赘述一下Storage这个类
//这个类的作用是通过不同的加载方式,读取不同加载方式类内部的方法
//很符合tp的多驱动方式设计模式,很多类似facede这种实现都依赖这种方法
//-----------------
static protected $handler;

static public function connect($type,$options=array()) {
   $class  =   'Think\\Storage\\Driver\\'.ucwords($type);
   self::$handler = new $class($options);
}

static public function __callstatic($method,$args){
   $type=end($args);
   $method_type=$method.ucfirst($type);
   if(method_exists(self::$handler, $method_type)){
      return call_user_func_array(array(self::$handler,$method_type), $args);
   }
   //调用缓存类型自己的方法
   if(method_exists(self::$handler, $method)){
      return call_user_func_array(array(self::$handler,$method), $args);
   }
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//--------------
//定义缓存文件目录
//--------------

$runtimefile  = RUNTIME_PATH.APP_MODE.'~runtime.php';
//-------------------------------------------
//在 debug关闭(生产环境),并且缓存文件存在的情况下,会读取缓存
//--------------------------------------------

if(!APP_DEBUG && Storage::has($runtimefile))
{
    Storage::load($runtimefile);
}
//------------------------------
//如果不是上述的条件下会执行如下的操作
//------------------------------

//----------------------------------
//删除旧的缓存文件,定义缓存字符串content
//-----------------------------------
if(Storage::has($runtimefile))
{
	Storage::unlink($runtimefile);
}
$content =  '';
//----------------------------
//这里的mode 为加载的参数数组
//如果用户app目录下没有定义自己的设置目录,默认加载系统的设置目录。
//----------------------------
$mode   =   include is_file(CONF_PATH.'core.php')?CONF_PATH.'core.php':MODE_PATH.APP_MODE.'.php';

//---------------------
//在这里贴出系统的设置参数
//--------------------

'config'    =>  array(
    THINK_PATH.'Conf/convention.php',   // 系统惯例配置
    CONF_PATH.'config.php',      // 应用公共配置
),

// 别名定义
'alias'     =>  array(
    'Think\Log'               => CORE_PATH . 'Log'.EXT,
    'Think\Log\Driver\File'   => CORE_PATH . 'Log/Driver/File'.EXT,
    'Think\Exception'         => CORE_PATH . 'Exception'.EXT,
    'Think\Model'             => CORE_PATH . 'Model'.EXT,
    'Think\Db'                => CORE_PATH . 'Db'.EXT,
    'Think\Template'          => CORE_PATH . 'Template'.EXT,
    'Think\Cache'             => CORE_PATH . 'Cache'.EXT,
    'Think\Cache\Driver\File' => CORE_PATH . 'Cache/Driver/File'.EXT,
    'Think\Storage'           => CORE_PATH . 'Storage'.EXT,
),

// 函数和类文件
'core'      =>  array(
    THINK_PATH.'Common/functions.php',
    COMMON_PATH.'Common/function.php',
    CORE_PATH . 'Hook'.EXT,
    CORE_PATH . 'App'.EXT,
    CORE_PATH . 'Dispatcher'.EXT,
    //CORE_PATH . 'Log'.EXT,
    CORE_PATH . 'Route'.EXT,
    CORE_PATH . 'Controller'.EXT,
    CORE_PATH . 'View'.EXT,
    BEHAVIOR_PATH . 'ParseTemplateBehavior'.EXT,
    BEHAVIOR_PATH . 'ContentReplaceBehavior'.EXT,
),
// 行为扩展定义
'tags'  =>  array(
    'app_begin'     =>  array(
        'Behavior\ReadHtmlCache', // 读取静态缓存
    ),
    'app_end'       =>  array(
        'Behavior\ShowPageTrace', // 页面Trace显示
    ),
    'view_parse'    =>  array(
        'Behavior\ParseTemplate', // 模板解析 支持PHP、内置模板引擎和第三方模板引擎
    ),
    'template_filter'=> array(
        'Behavior\ContentReplace', // 模板输出替换
    ),
    'view_filter'   =>  array(
        'Behavior\WriteHtmlCache', // 写入静态缓存
    ),
)
//------------
// 加载系统核心文件
// debug关闭情况下,编译缓存
//------------
foreach ($mode['core'] as $file)
{
    if(is_file($file)) {
      include $file;
      if(!APP_DEBUG) $content   .= compile($file);
    }
}
//------------------
// 加载应用模式配置文件
// 解释一下C函数:
// 如果参数1为数组,则合并设置数组
// 如果参数1为字符串,则获取对应的参数值
// 如果参数1为字符串,参数二有值,则为更新字符串在数组中的值
//-------------------
foreach ($mode['config'] as $key=>$file){
   is_numeric($key)?C(include $file):C($key,include $file);
}
//--------------------------
// 读取当前应用模式对应的配置文件
// 如果系统的运行模式不是common模式并且对应的config存在,则加载设置到系统设置组
//---------------------------

if('common' != APP_MODE && is_file(CONF_PATH.'config_'.APP_MODE.'.php'))
{
	C(include CONF_PATH.'config_'.APP_MODE.'.php');  
}
//----------------
// 加载模式别名定义
// 在本章节前我们提到的类的映射数组,该函数是将别名映射到类的路径,简化实例
// 如果用户有自定义的别名数组,也加载到映射中。
//----------------

//系统设置
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');   
}
//--------------------
// 加载框架底层语言包
// 通过C函数 获取 系统的默认语言设置,加载对应文件
// 这里的L函数只要将语言映射 保存在静态数组中,支持数组和字符串单映射
//---------------------

L(include THINK_PATH.'Lang/'.strtolower(C('DEFAULT_LANG')).'.php');
//--------------------
// 这里主要是区别生产环境和开发环境
// 如果是生产环境(debug=false),则储存缓存
// 如果是开发环境(debug=true), 则加载debug系统配置文件和用户自定义的配置文件
//--------------------

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('
//---------------------------
// 读取当前应用状态对应的配置文件
// 这里也是加载文件,不过APP_STATUS我记得前面设置为 null  所以一般不会加载这个配置
//---------------------------

if(APP_STATUS && is_file(CONF_PATH.APP_STATUS.'.php'))
{
	C(include CONF_PATH.APP_STATUS.'.php');   
}
//--------------
// 设置系统时区
//--------------

date_default_timezone_set(C('DEFAULT_TIMEZONE'));
//--------------------------------
// 检查应用目录结构 如果不存在则自动创建
// 这里主要是创建一些日志和缓存目录,可自行查阅源码不多赘述
//--------------------------------

if(C('CHECK_APP_DIR') && !is_dir(LOG_PATH)) {
    // 创建应用目录结构
    require THINK_PATH.'Common/build.php';
}
//-----------------------------------
// 这里记录了所有文件的加载时间并且启动app
//-----------------------------------
// 记录加载文件时间
G('loadTime');
// 运行应用
App::run();

从url输入到系统的配置我们就全部说完了,接下来的内容请查看我的另一篇文章;

ThinkPHP 3.2源码分析——系统的App的执行流程;
https://blog.csdn.net/weixin_44187959/article/details/93604224

你可能感兴趣的:(php)