牛骨文教育服务平台(让学习变的简单)
博文笔记

ThinkPHP3.2.3源码分析一之系统流程

创建时间:2016-10-31 投稿人: 浏览次数:703

  • ThinkPHP323源码分析一之系统流程
    • 整体流程
    • 一 初始化
      • 主要文件加载
      • 简单流程
        • indexphp
        • ThinkPHPThinkPHPphp
        • ThinkPHPLibraryThinkThinkclassphp
        • ThinkPHPLibraryThinkStorageclassphp
        • ThinkPHPLibraryThinkStorageDriverFileclassphp or Sae
    • 二 应用模式的编译缓存和配置文件加载
      • 主要文件加载
      • 简单流程
        • ThinkPHPLibraryThinkThinkclassphp
    • 三 App初始化和URL解析定位模块控制器和操作
      • 主要文件加载
      • 简单流程
        • ThinkPHPLibraryThinkThinkclassphp
        • ThinkPHPLibraryThinkAppclassphp
    • 四 Action执行和模板解析
      • 主要文件加载
      • 简单流程
        • ApplicationHomeControllerIndexControllerclassphp
        • thinkphp_323ThinkPHPLibraryThinkControllerclassphp
        • ThinkPHPLibraryThinkViewclassphp
        • ThinkPHPLibraryBehaviorParseTemplateBehaviorclassphp
    • 五 收尾流程
      • 简单流程
        • ThinkPHPLibraryThinkAppclassphp
        • ThinkPHPLibraryThinkThinkclassphp
  • END

整体流程

红色的时钩子,蓝色为配置文件或函数定义文件

这里写图片描述

一 初始化

主要文件加载

hinkphp_3.2.3index.php
hinkphp_3.2.3ThinkPHPThinkPHP.php
hinkphp_3.2.3ThinkPHPLibraryThinkThink.class.php
hinkphp_3.2.3ThinkPHPLibraryThinkStorage.class.php
hinkphp_3.2.3ThinkPHPLibraryThinkStorageDriverFile.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";

ThinkPHPThinkPHP.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;
// 应用初始化 
ThinkThink::start();

ThinkPHPLibraryThinkThink.class.php

static public function start() {
      // 注册AUTOLOAD方法
      // 主要时去系统目录找类文件,或从应用配置文件注册的命名空间路径找
      // array("Think","Org","Behavior","Com","Vendor")
      // C("AUTOLOAD_NAMESPACE");
      // 兼容处理不开命名空间的项目
      spl_autoload_register("ThinkThink::autoload");

      // 设定系统错误和异常处理函数注册

      // 脚本正常结束或者手动或异常终结处理
      // 处理: 1.内存中的日志落地到文件 2.如果是异常终止,则输出错误
      register_shutdown_function("ThinkThink::fatalError");

      //系统致命错误处理
      //级别_USER_ERROR的写日志,页面输出提示
      //低级别WARN NOTICE 等输出PageTrace,不影响脚本
      set_error_handler("ThinkThink::appError");

      //系统抛出异常处理
      //写日志,页面输出
      set_exception_handler("ThinkThink::appException");

      // 初始化文件存储方式
      Storage::connect(STORAGE_TYPE);

ThinkPHPLibraryThinkStorage.class.php

ThinkPHPLibraryThinkStorageDriverFile.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);
        }
    }
}

二 应用模式的编译缓存和配置文件加载

主要文件加载

hinkphp_3.2.3ThinkPHPModecommon.php
hinkphp_3.2.3ThinkPHPCommonfunctions.php
hinkphp_3.2.3ThinkPHPLibraryThinkHook.class.php

简单流程

ThinkPHPLibraryThinkThink.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  .=  "
namespace { Think\Think::addMap(".var_export(self::$_map,true).");";
              $content  .=  "
L(".var_export(L(),true).");
C(".var_export(C(),true).");ThinkHook::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");

三 App初始化和URL解析,定位模块控制器和操作

主要文件加载

hinkphp_3.2.3ThinkPHPLibraryThinkApp.class.php
hinkphp_3.2.3ThinkPHPLibraryThinkDispatcher.class.php
hinkphp_3.2.3ThinkPHPLibraryThinkRoute.class.php

简单流程

ThinkPHPLibraryThinkThink.class.php

 // 运行应用
      App::run();

ThinkPHPLibraryThinkApp.class.php

 /**
     * 运行应用实例 入口文件使用的快捷方法
     * @access public
     * @return void
     */
    static public function run()
    {
        // 应用初始化标签
        /**
                "app_init"     =>  array(
                    "BehaviorBuildLiteBehavior", // 生成运行Lite文件
                ),
         *      生成lite文件,用于替换入口文件
         */
        Hook::listen("app_init");


        // app初始化,见init()
        App::init();

        // 应用开始标签
        /**
         *           "app_begin"     =>  array(
                             "BehaviorReadHtmlCacheBehavior", // 读取静态缓存
                      ),
         *           若打开静态缓存开关,则直接获取缓存文件,输出,终止脚本。
         */
        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;
    }

四 Action执行和模板解析

主要文件加载

hinkphp_3.2.3ThinkPHPLibraryThinkController.class.php
hinkphp_3.2.3ThinkPHPLibraryThinkView.class.php
hinkphp_3.2.3ApplicationHomeControllerIndexController.class.php
hinkphp_3.2.3ThinkPHPLibraryBehaviorParseTemplateBehavior.class.php
hinkphp_3.2.3ThinkPHPLibraryThinkTemplate.class.php
hinkphp_3.2.3ThinkPHPLibraryThinkTemplateTagLibCx.class.php
hinkphp_3.2.3ThinkPHPLibraryThinkTemplateTagLib.class.php

简单流程

ApplicationHomeControllerIndexController.class.php

namespace HomeController;
use ThinkController;
class IndexController extends Controller {
    public function index(){
        $this->display();
    }
}

hinkphp_3.2.3ThinkPHPLibraryThinkController.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("ThinkView");

        //控制器初始化
        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);
    }

 // 此外此类还用魔术方法实现空操作的跳转和默认模板的调用
// 也实现了页面的重定向和自动跳转

ThinkPHPLibraryThinkView.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);
            ///// BehaviorParseTemplateBehavior 编译模板文件
            Hook::listen("view_parse",$params);
        }
        // 获取并清空缓存
        $content = ob_get_clean();
        // 内容过滤标签
        /**
               "template_filter"=> array(
                "BehaviorContentReplaceBehavior", // 模板输出替换
                ),
         *      // 系统默认的特殊字符串替换,如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;
    }

ThinkPHPLibraryBehaviorParseTemplateBehavior.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);
            }
        }
    }

五 收尾流程

简单流程

ThinkPHPLibraryThinkApp.class.php

// 应用结束标签
        /**
         *     "app_end"       =>  array(
                "BehaviorShowPageTraceBehavior", // 页面Trace显示
                ),
         */
        Hook::listen("app_end");
        return;

ThinkPHPLibraryThinkThink.class.php

 // 脚本正常结束或者手动或异常终结处理
 // 处理: 1.内存中的日志落地到文件 2.如果是异常终止,则输出错误
 register_shutdown_function("ThinkThink::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;
            }
        }
    }

以后有机会再研究和补充此框架源码的机制实现说明,如:
Hook机制,controller反射机制,Model的ORM实现,路由解析定位的实现,模板的编译机制,session方法使用机制,文件编译缓存,PageTrace的使用等,欢迎大家一起探讨学习。

声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。