• 基于TP框架的ThinkCMF,控制器display方法源码分析


    昨天在写代码的时候,看见写了无数次的模版渲染方法:$this->display(),突然很想弄清楚它是如何实现的.

    今天不忙,就分析了一下.

    class TestController extends HomebaseController {
        public function judge(){
            ......
            $this->display("xxxxxxx");
        }
    }

    1.这是调用了父类的display方法,看一下HomebaseController有没有此方法,发现有,

    class HomebaseController extends AppframeController {
      public function display($templateFile = '', $charset = '', $contentType = '', $content = '', $prefix = '') {
            //从控制器$this->display(":judge")传递过来的的$templateFile参数,就是":judge"
            //echo $this->parseTemplate($templateFile);
            parent::display($this->parseTemplate($templateFile), $charset, $contentType);
        }
    }

    2.以上,这个方法是调用了父类的display方法,父类是AppframeController,然后去AppframeController.class.php查找display方法,然而并没有(以下代码),那就是找AppframeController的父类Controller的display方法了

    class AppframeController extends Controller {
    //这里并没有display方法
    }

    3.在Controller.class.php中找到了display方法

    abstract class Controller {   
     protected function   display($templateFile='',$charset='',$contentType='',$content='',$prefix='') {
            $this->view->display($templateFile,$charset,$contentType,$content,$prefix);
        }
    }
    

    4.发现在方法中是使用了他的view属性的display方法,然后去查找Controller::view是什么,首先去看构造方法,在构造方法中发现了.

    public function __construct() {
            Hook::listen('action_begin',$this->config);
            //实例化视图类
            $this->view     = Think::instance('ThinkView');
            //控制器初始化
            if(method_exists($this,'_initialize'))
                $this->_initialize();
        }

    5.原来是View类的实例,那就看View类的display方法去吧

    class View {
    /**
         * 加载模板和页面输出 可以返回输出内容
         * @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);
            // 解析并获取模板内容
            $content = $this->fetch($templateFile,$content,$prefix);
            // 输出模板内容
            $this->render($content,$charset,$contentType);
            // 视图结束标签
            Hook::listen('view_end');
        }
    }

    6.先看第一个参数$templateFile,这是什么呢?再倒回去追溯,汗!参数在文章初始代码xxxxController::display方法中传入.在此假设为参数为"test"

    7.然后,往下走到第1步.

     parent::display($this->parseTemplate($templateFile), $charset, $contentType);

    8.他要先使用parseTemplate方法去处理传递的 $templateFile参数(即假定的"test")

    9.去看parseTemplate方法,为了方便,分析思路都写在注释里了

    class HomebaseController extends AppframeController {
         /**
         * 自动定位模板文件
         * @access protected
         * @param string $template 模板文件规则
         * @return string
         */
        public function parseTemplate($template='') {
            $tmpl_path=C("SP_TMPL_PATH");//此配置项在配置文件中是:  "themes/",
            define("SP_TMPL_PATH", $tmpl_path);//把SP_TMPL_PATH定义为常量
            // 获取当前主题名称
            $theme      =    C('SP_DEFAULT_THEME');//此配置项在配置文件中是:simplebootx
            if(C('TMPL_DETECT_THEME')) {// 自动侦测模板主题,这个日后再看
                $t = C('VAR_TEMPLATE');
                if (isset($_GET[$t])){
                    $theme = $_GET[$t];
                }elseif(cookie('think_template')){
                    $theme = cookie('think_template');
                }
                if(!file_exists($tmpl_path."/".$theme)){
                    $theme  =   C('SP_DEFAULT_THEME');
                }
                cookie('think_template',$theme,864000);
            }
            
            $theme_suffix="";//这个为何是固定的???
            
            if(C('MOBILE_TPL_ENABLED') && sp_is_mobile()){//开启手机模板支持,暂时略过
                
                if (C('LANG_SWITCH_ON',null,false)){
                    if(file_exists($tmpl_path."/".$theme."_mobile_".LANG_SET)){//优先级最高
                        $theme_suffix  =  "_mobile_".LANG_SET;
                    }elseif (file_exists($tmpl_path."/".$theme."_mobile")){
                        $theme_suffix  =  "_mobile";
                    }elseif (file_exists($tmpl_path."/".$theme."_".LANG_SET)){
                        $theme_suffix  =  "_".LANG_SET;
                    }
                }else{
                    if(file_exists($tmpl_path."/".$theme."_mobile")){
                        $theme_suffix  =  "_mobile";
                    }
                }
            }else{//本项目走到else
                $lang_suffix="_".LANG_SET;//这是语言选择,用于在html页面的head中
                if (C('LANG_SWITCH_ON',null,false) && file_exists($tmpl_path."/".$theme.$lang_suffix)){
                    $theme_suffix = $lang_suffix;
                }
            }
            
            $theme=$theme.$theme_suffix;//右侧分别是 "simplebootx"."",所以左侧是"simplebootx"
            
            C('SP_DEFAULT_THEME',$theme);//因此缺省主题是simplebootx
            
            
            $current_tmpl_path=$tmpl_path.$theme."/";//右侧,"模板路径"."当前主题" 即 "themes/"."simplebootx",所以,当前模板路径是 "themes/"."simplebootx",
    即当前模板下的主题的路径是此 // 获取当前主题的模版路径
    define('THEME_PATH', $current_tmpl_path);//把当前"模板路径"定位常量 C("TMPL_PARSE_STRING.__TMPL__",__ROOT__."/".$current_tmpl_path);/*

    解读:右边,__ROOT__是空字符串,然后"/",然后当前模板路径"themes/"."simplebootx", 这个是重点,代表了一种思路,思路是,把__ROOT__(站点根目录)与模板路径结合,
    现在详细说下,看两者都是如何生成的? 第一个:__ROOT__是根据index.php在站点中的路径:$_SERVER['SCRIPT_NAME']来判断, 大致是:利用dirname($_SERVER['SCRIPT_NAME'])取其目录部分,如果是根目录,则为空,如不是根目录,则是啥就是啥,所以,index.php在哪儿,网站根目录就在哪儿,模板目录就在哪儿(index.php与模板目录平级) 第二个:当前模板路径current_tmpl_path:$current_tmpl_path=$tmpl_path.$theme."/" 它是两部分组成,一个是tmpl_path,即模板路径,一个是theme,即模板下的主题路径, 先看tmpl_path,即模板路径 tmpl_path:是根据配置参数SP_TMPL_PATH而来的,(这个参数是在网站根目录applicationCommonConfconfig.php中设定), 'SP_TMPL_PATH' => 'themes/',由于这个参数必将与__ROOT__结合,因此,SP_TMPL_PATH一定是根目录的延续,所以此参数确保在根目录下 再看theme,即模板下的主题路径,$theme = C('SP_DEFAULT_THEME') (它是读取的SP_DEFAULT_THEME参数, 'SP_DEFAULT_THEME' => 'simplebootx', // 前台模板文件) 官方注释说是前台模板文件,我感觉,更严格来说,是前台模板文件的目录路径中,主题所占的那一部分,所以,既可以单个文件夹,如"simplebootx",也可以是多层次,如"simplebootx/folder/..." 然后,两者组合,就是模板下的主题的路径
    */ C('SP_VIEW_PATH',$tmpl_path); C('DEFAULT_THEME',$theme); define("SP_CURRENT_THEME", $theme); if(is_file($template)) {//如果$this->display("这里面是存在的文件的路径,比如绝对路径,则直接返回此路径"); return $template; } $depr = C('TMPL_FILE_DEPR');/*如果觉得目录结构太深,可以通过设置 TMPL_FILE_DEPR 参数来配置简化模板的目录层次,例如设置: 'TMPL_FILE_DEPR'=>'_' 默认的模板文件就变成了: ./Application/Home/View/User_add.html */ $template = str_replace(':', $depr, $template);//$template参数中有冒号,如":test",则把冒号换为"/",因此$template会变成"/test" // 获取当前模块 $module = MODULE_NAME; if(strpos($template,'@')){ // 跨模块调用模版文件,暂时略过 list($module,$template) = explode('@',$template); } // 分析模板文件规则 if('' == $template) { // 如果模板文件名为空 ,即调用display方法时没有传递任何$template参数,则按照默认规则定位即控制器下的方法(控制器/方法)或者,如果$depr不是目录分隔符,比如是"_"则变为
    "控制器_方法",此时不再有控制器目录
    $template = "/".CONTROLLER_NAME . $depr . ACTION_NAME; }elseif(false === strpos($template, '/')){//如果$depr是"/",那么$template中,"/"一定存在,如果在此判断为false,那么说明同时满足了两
    个条件
    1.$depr不是"/"了,是别的了,比如"_".
    2.$template参数没有冒号,因为冒号会被替换为"/"
    因此可以推断,此分支适用于类似$template为"test"这样的参数,并且$depr不为"/" $template = "/".CONTROLLER_NAME . $depr . $template;//此时,$template就是一层目录 } $file = sp_add_template_file_suffix($current_tmpl_path.$module.$template);//在此调用了系统函数sp_add_template_file_suffix $file= str_replace("//",'/',$file); if(!file_exists_case($file)) E(L('_TEMPLATE_NOT_EXIST_').':'.$file); return $file; } }

     10.去查看系统函数sp_add_template_file_suffix是干什么用的

    此函数在"站点根目录applicationCommonCommonfunction.php"中(ThinkCMF专有)

    /**
     * 给没有后缀的模板文件,添加后缀名
     * @param string $filename_nosuffix
     */
    function sp_add_template_file_suffix($filename_nosuffix){
        
        
        if(file_exists_case($filename_nosuffix.C('TMPL_TEMPLATE_SUFFIX'))){//在此调用了file_exists_case方法,此为TP框架的方法,去11步分析此方法
            $filename_nosuffix = $filename_nosuffix.C('TMPL_TEMPLATE_SUFFIX');
        }else if(file_exists_case($filename_nosuffix.".php")){
            $filename_nosuffix = $filename_nosuffix.".php";
        }else{
            $filename_nosuffix = $filename_nosuffix.C('TMPL_TEMPLATE_SUFFIX');
        }
        
        return $filename_nosuffix;
    }

    11.分析file_exists_case方法,此函数在"站点根目录simplewindCoreCommonfunctions.php"里

    /**
     * 区分大小写的文件存在判断
     * @param string $filename 文件地址
     * @return boolean
     */
    function file_exists_case($filename) {
        if (is_file($filename)) {//如果能找到此文件
            if (IS_WIN && APP_DEBUG) {//是windows系统并且调试模式
                if (basename(realpath($filename)) != basename($filename))//猜测这样写是为了严格文件名大小写,防止在Linux服务器上因为文件名大小写出错
                    return false;
            }
            return true;
        }
        return false;
    }

    12.可以发现,如果不是一个正常的文件,或者在windows系统的调试模式下,都会返回false,返回第10步,第一个if传递的是带指定后缀的模板,如果返回false,那就elseif,看后缀是php是否可以,如果仍然不行,那就完璧归赵再加上默认后缀好了.返回第9步对sp_add_template_file_suffix的调用,可以发现,它会对路径中的"//"转换为"/"再次用file_exists_case对文件是否存在进行验证,如果没有,那就报错

    13.第9步如果返回了正确的模板路径,则继续追溯到第7步....第7步源自第1步,去看第1步→第2步→第3步→第4步→第5步,好了,为方便把第5步的代码再次粘过来

    class View {
    /**
         * 加载模板和页面输出 可以返回输出内容
         * @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);
            // 解析并获取模板内容
            $content = $this->fetch($templateFile,$content,$prefix);
            // 输出模板内容
            $this->render($content,$charset,$contentType);
            // 视图结束标签
            Hook::listen('view_end');
        }
    }

    14.可以看到View::display方法中调用了G方法,应该是统计时间用的,先忽略,看Hook::listen方法

    class Hook {
          /**
         * 监听标签的插件
         * @param string $tag 标签名称
         * @param mixed $params 传入参数
         * @return void
         */
        static public function listen($tag, &$params=NULL) {
            if(isset(self::$tags[$tag])) {//这里判断有没有这个标签位,例如"view_begin",如果没有直接返回
                if(APP_DEBUG) {
                    G($tag.'Start');
                    trace('[ '.$tag.' ] --START--','','INFO');
                }
                foreach (self::$tags[$tag] as $name) {
                    APP_DEBUG && G($name.'_start');
                    $result =   self::exec($name, $tag,$params);
                    if(APP_DEBUG){
                        G($name.'_end');
                        trace('Run '.$name.' [ RunTime:'.G($name.'_start',$name.'_end',6).'s ]','','INFO');
                    }
                    if(false === $result) {
                        // 如果返回false 则中断插件执行
                        return ;
                    }
                }
                if(APP_DEBUG) { // 记录行为的执行日志
                    trace('[ '.$tag.' ] --END-- [ RunTime:'.G($tag.'Start',$tag.'End',6).'s ]','','INFO');
                }
            }
            return;
        }  
    }

    Hook是系统钩子类,listen方法是用来监听标签位的,什么是标签位呢?看手册怎么说的...

    Behavior(行为)
    行为(Behavior)是ThinkPHP扩展机制中比较关键的一项扩展,行为既可以独立调用,也可以绑定到某
    个标签(位)中进行侦听。这里的行为指的是一个比较抽象的概念,你可以想象成在应用执行过程中的一
    个动作或者处理,在框架的执行流程中,各个位置都可以有行为产生,例如路由检测是一个行为,静态缓
    存是一个行为,用户权限检测也是行为,大到业务逻辑,小到浏览器检测、多语言检测等等都可以当做是
    一个行为,甚至说你希望给你的网站用户的第一次访问弹出Hello,world!这些都可以看成是一种行为,
    行为的存在让你无需改动框架和应用,而在外围通过扩展或者配置来改变或者增加一些功能。
    而不同的行为之间也具有位置共同性,比如,有些行为的作用位置都是在应用执行前,有些行为都是在模
    板输出之后,我们把这些行为发生作用的位置称之为标签(位),也可以称之为钩子当应用程序运行到
    这个标签的时候,就会被拦截下来,统一执行相关的行为,类似于AOP编程中的“切面”的概念,给某一
    个标签绑定相关行为就成了一种类AOP编程的思想。

    15.我在应用中添加了名为'testtag'的标签,

    以下代码在:站点根目录applicationCommonConf	ags.php
    <?php 
    return array(     // 添加下面一行定义即可    
        'app_init'=>array('CommonBehaviorInitHookBehavior'), 
        'app_begin' => array('BehaviorCheckLangBehavior'),
        'view_filter' => array('CommonBehaviorTmplStripSpaceBehavior'),
        'testtag' => array('BehaviorTestBehavior'),////////////这是我添加的
        
    );

    'testtag'对应的行为在:站点根目录simplewindCoreLibraryBehaviorTestBehavior.class.php,

    <?php
    namespace Behavior;
    class TestBehavior {
        public function run(&$params) {echo "i am TestBehavior::run";
            print_r($params);
        }
    }

    并在testController::judge方法中添加"testtag"标签侦听

    <?php
    namespace PortalController;
    use CommonControllerHomebaseController; 
    class TestController extends HomebaseController {
        public function judge(){
            $para=[1,2,3,4,5];
            ThinkHook::listen('testtag',$para);
        }
    }

    16.调用TestController::judge方法后的结果

    i am TestBehavior::run
    Array
    (
        [0] => 1
        [1] => 2
        [2] => 3
        [3] => 4
        [4] => 5
    )

    17.好了,现在去研究Hooke::listen方法

    打印一下,listen方法的参数$tag是什么?" if(isset(self::$tags[$tag])) "中的self::$tags是什么?改造一下listen方法以便查看

     static public function listen($tag, &$params=NULL) {
            if($tag=="testtag"){
                echo "$tags是";print_r(self::$tags);
                echo "当前$tags[$tag]";var_dump(self::$tags[$tag]);
    
            }
            if(isset(self::$tags[$tag])) {......}
    }

    18.查看打印结果:

    $tags是
    Array
    ( [app_init] => Array ( [0] => BehaviorBuildLiteBehavior [1] => CommonBehaviorInitHookBehavior ) [app_begin] => Array ( [0] => BehaviorReadHtmlCacheBehavior [1] => BehaviorCheckLangBehavior ) [app_end] => Array ( [0] => BehaviorShowPageTraceBehavior ) [view_parse] => Array ( [0] => BehaviorParseTemplateBehavior ) [template_filter] => Array ( [0] => BehaviorContentReplaceBehavior ) [view_filter] => Array ( [0] => BehaviorWriteHtmlCacheBehavior [1] => CommonBehaviorTmplStripSpaceBehavior ) [testtag] => Array ( [0] => BehaviorTestBehavior ) [footer] => Array ( [0] => Demo ) ) 当前$tags[$tag]是
    array(1) { [0]=> string(21) "BehaviorTestBehavior" }

    19.可以看到,Hook::$tags属性将所有的标签位(钩子)都打印出来了,包括系统的和应用的

    系统的:站点根目录simplewindCoreModecommon.php

    return array(
        // 配置文件
        'config'    =>  array(......),
    
        // 别名定义
        'alias'     => array(......),
    
        // 函数和类文件
        'core'      =>  array(......),
        // 行为扩展定义
        'tags'  =>  array(
            'app_init'     =>  array(
                'BehaviorBuildLiteBehavior', // 生成运行Lite文件
            ),        
            'app_begin'     =>  array(
                'BehaviorReadHtmlCacheBehavior', // 读取静态缓存
            ),
            'app_end'       =>  array(
                'BehaviorShowPageTraceBehavior', // 页面Trace显示
            ),
            'view_parse'    =>  array(
                'BehaviorParseTemplateBehavior', // 模板解析 支持PHP、内置模板引擎和第三方模板引擎
            ),
            'template_filter'=> array(
                'BehaviorContentReplaceBehavior', // 模板输出替换
            ),
            'view_filter'   =>  array(
                'BehaviorWriteHtmlCacheBehavior', // 写入静态缓存
            ),
        ),
    );

    应用的:站点根目录applicationCommonConf ags.php

    <?php 
    return array(     // 添加下面一行定义即可    
        'app_init'=>array('CommonBehaviorInitHookBehavior'), 
        'app_begin' => array('BehaviorCheckLangBehavior'),
        'view_filter' => array('CommonBehaviorTmplStripSpaceBehavior'),
        'testtag' => array('BehaviorTestBehavior'),
        
    );

    20.好了,知道参数和Hook::$tags是什么了,接着看listen的代码,分析过程我写在了注释里:

    static public function listen($tag, &$params=NULL) {
            if(isset(self::$tags[$tag])) {//这里判断有没有这个标签位,例如"view_begin",如果没有直接返回
            
    
                if(APP_DEBUG) {//用于调试的,先不看
                    G($tag.'Start');
                    trace('[ '.$tag.' ] --START--','','INFO');
                }
                foreach (self::$tags[$tag] as $name) {
                //针对我自定义的'testtag'来说,他要遍历$tags['testtag']
                    APP_DEBUG && G($name.'_start');//用于调试的,先不看
                    //if($tag=="testtag"){echo $name;print_r( $params);exit();}
                    /*
                        下面使用三个形参调用了exec方法
                        三个形参分别是
                        $name:"BehaviorTestBehavior"
                        $tag:"testtag"
                        $params:Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 5 )
                        既然调用了exec,那去下一步看看exec是干什么的
                    */
    
                    $result =   self::exec($name, $tag,$params);
                    if(APP_DEBUG){
                        ......
                    }
                    if(false === $result) {
                       ......
                    }
                }
                if(APP_DEBUG) { // 记录行为的执行日志
                  ......
                }
            }
            return;
        }

    21.看看Hook::exec,原来是执行插件的

        /**
         * 执行某个插件
         * @param string $name 插件名称
         * @param string $tag 方法名(标签名)     
         * @param Mixed $params 传入的参数
         * @return void
         */
        static public function exec($name, $tag,&$params=NULL) {
            if('Behavior' == substr($name,-8) ){
                // 行为扩展必须用run入口方法
                $class = $name;
                $tag    =   'run';
            }else{
                $class   =  "plugins\{$name}\{$name}Plugin";
            }
            if(class_exists($class)){ //ThinkCMF NOTE 插件或者行为存在时才执行,走到这,要重温一下class_exists,看下一步
                $addon   = new $class();
                return $addon->$tag($params);
            }
            
        }
    }

     22.上面代码中出现了class_exists,这个函数是用来检查类是否已定义,手册中这样定义的

    bool class_exists ( string $class_name [, bool $autoload = true ] )
     
    检查指定的类是否已定义。 
    
    
    参数
     
    
    class_name 
    类名。名字的匹配是大小写不敏感的。 
    autoload 
    是否默认调用 __autoload

    第二个参数很重要,默认是调用__autoload的,由于spl_autoload_register() 提供了一种更加灵活的方式来实现类的自动加载。因此,不再建议使用 __autoload() 函数,在以后的版本中它可能被弃用。

    spl_autoload_register怎么用?

    spl_autoload_register — 注册给定的函数作为 __autoload 的实现 
    
    
    说明
     
    bool spl_autoload_register ([ callable $autoload_function [, bool $throw = true [, bool $prepend = false ]]] )
     
    将函数注册到SPL __autoload函数队列中。如果该队列中的函数尚未激活,则激活它们。 
    
    如果在你的程序中已经实现了 __autoload() 函数,它必须显式注册到 __autoload() 队列中。
    因为 spl_autoload_register() 函数会将Zend Engine中的 __autoload() 函数取代为 spl_autoload() 或 spl_autoload_call() 。 如果需要多条 autoload 函数, spl_autoload_register() 满足了此类需求。
    它实际上创建了 autoload 函数的队列,按定义时的顺序逐个执行。相比之下, __autoload() 只可以定义一次。
    autoload_function 
    欲注册的自动装载函数。如果没有提供任何参数,则自动注册 autoload 的默认实现函数 spl_autoload() 。 
    throw 
    此参数设置了 autoload_function 无法成功注册时, spl_autoload_register() 是否抛出异常。 
    prepend 
    如果是 true, spl_autoload_register() 会添加函数到队列之首,而不是队列尾部。 

    所以,去看框架中如何定义spl_autoload_register,此函数在Think::start方法中定义(Core/library/Think/Think.class.php),并在ThinkPHP框架的公共入口文件ThinkPHP.php中调用(ThinkThink::start()),所以,去看看这个start方法是啥样的

    namespace Think;
    /**
     * ThinkPHP 引导类
     */
    class Think {
    .....
        /**
         * 应用程序初始化
         * @access public
         * @return void
         */
        static public function start() {
          // 注册AUTOLOAD方法
          spl_autoload_register('ThinkThink::autoload');      
          ......
    }
    }

    原来自动装载函数是ThinkThink::autoload方法,去看这个方法,分析都在注释里面:

        /**
         * 类库自动加载
         * @param string $class 对象类名
         * @return void
         */
        public static function autoload($class) {
            // 检查是否存在映射
            if(isset(self::$_map[$class])) {//在map中出现,我自定义类不会出现
                include self::$_map[$class];
            }elseif(false !== strpos($class,'\')){//如果$class中出现了反斜杠
              /*strstr函数,如果不设置第三个参数或为false,strstr() 将返回
                haystack 字符串从 needle 第一次出现的位置开始到 haystack 结尾的字符串。
                如果设置为true,strstr()将返回 
                needle 在 haystack 中的位置之前的部分。
                这两个结果的并集就是完整的haystack
                在这里,返回的是命名空间的第一部分,如"BehaviorTestBehavior",返回"Behavior"
              */
              $name           =   strstr($class, '\', true);
              /*由于返回Behavior,所以以下判断条件是满足的*/
              if(in_array($name,array('Think','Org','Behavior','Com','Vendor')) || is_dir(LIB_PATH.$name)){ 
                  // Library目录下面的命名空间自动定位
                  $path       =   LIB_PATH;//此常量为类库的位置
              }else{
                  // 检测自定义命名空间 否则就以模块为命名空间
                  $namespace  =   C('AUTOLOAD_NAMESPACE');
                  $path       =   isset($namespace[$name])? dirname($namespace[$name]).'/' : APP_PATH;
              }
              /*
              以下$filename,是根据命名空间获取类文件,因为TP框架中,类的命名空间和类文件的目录是一样的,
              比如$filename的结果是:站点根目录simplewindCoreLibrary/Behavior/TestBehavior.class.php
    
              */
              $filename       =   $path . str_replace('\', '/', $class) . EXT;
              if(is_file($filename)) {
                  // Win环境下面严格区分大小写
                  if (
                    IS_WIN && false 
                    === 
                    /*
                    在此,
                    1.要先用realpath得出$filename的规范化的绝对路径名
                    2.然后用str_Replace将正斜杠变为反斜杠,不是realpath只会返回反斜杠吗,不知为何多加一步
                    3.用strpos验证$class.EXT是否在str_Replace返回的路径中存在
                    */
                    strpos(str_replace('/', '\', realpath($filename)), $class . EXT)
                    ){
                      return ;
                  }
                  include $filename;//这是最终的目的,下列先不分析
              }
            }elseif (!C('APP_USE_NAMESPACE')) {//先不分析
                // 自动加载的类库层
                foreach(explode(',',C('APP_AUTOLOAD_LAYER')) as $layer){
                    if(substr($class,-strlen($layer))==$layer){
                        if(require_cache(MODULE_PATH.$layer.'/'.$class.EXT)) {
                            return ;
                        }
                    }            
                }
                // 根据自动加载路径设置进行尝试搜索
                foreach (explode(',',C('APP_AUTOLOAD_PATH')) as $path){
                    if(import($path.'.'.$class))
                        // 如果加载类成功则返回
                        return ;
                }
            }
        }

    23.好了,关于class_exits函数和spl_autoload_register函数的分析到此为止,返回21步

        static public function exec($name, $tag,&$params=NULL) {
                ......
                $addon   = new $class();//通过class_exists调用的spl_autoload_register,文件已经被包含,因此可以实例化对象了
                return $addon->$tag($params);//$tag是固定的,TP框架规定必须是"run",因此调用了run方法
            }
            
        }

     24.返回第20步,也就是调用exec的地方

        /**
         * 监听标签的插件
         * @param string $tag 标签名称
         * @param mixed $params 传入参数
         * @return void
         */
        static public function listen($tag, &$params=NULL) {
                 ......
                foreach (self::$tags[$tag] as $name) {
                //针对我自定义的'testtag'来说,他要遍历$tags['testtag']
                    APP_DEBUG && G($name.'_start');//用于调试的,先不看
                    //if($tag=="testtag"){echo $name;print_r( $params);exit();}
                    /*
                        下面使用三个形参调用了exec方法
                        三个形参分别是
                        $name:"BehaviorTestBehavior"
                        $tag:"testtag"
                        $params:Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 5 )
                        既然调用了exec,拿去看看exec是干什么的
                    */
    
                    $result =   self::exec($name, $tag,$params);
                    if(APP_DEBUG){
                        G($name.'_end');
                        trace('Run '.$name.' [ RunTime:'.G($name.'_start',$name.'_end',6).'s ]','','INFO');
                    }
                    if(false === $result) {
                        // 如果返回false 则中断插件执行
                        return ;
                    }
                }
                if(APP_DEBUG) { // 记录行为的执行日志
                    trace('[ '.$tag.' ] --END-- [ RunTime:'.G($tag.'Start',$tag.'End',6).'s ]','','INFO');
                }
            }
            return;
        }

    25.上面的listen方法调用完self::exec方法后,整个listen方法就调用完毕了,再往前追溯.返回listen第一次出现的地方:第13步

        /**
         * 加载模板和页面输出 可以返回输出内容
         * @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');
            // 视图开始标签
            /*
            下面的listen方法,和上面分析的testtag一样,"view_begin"也是一个标签位,其实对应的就是一个behavior目录内的类文件,然后传递了$templateFile参数
            经我在代码中测试,并没有"view_begin"这个标签位置,这应该是TP框架开发者
            提示用户可以利用视图开始(view_begin)标签位侦听并执行绑定行为
            */
            Hook::listen('view_begin',$templateFile);
            // 解析并获取模板内容,去下一步看看fetch方法是什么?
            $content = $this->fetch($templateFile,$content,$prefix);
            // 输出模板内容
            $this->render($content,$charset,$contentType);
            // 视图结束标签
            Hook::listen('view_end');
        }

    26.fetch方法,解析和获取模板内容,用于输出

        /**
         * 解析和获取模板内容 用于输出
         * @access public
         * @param string $templateFile 模板文件名
         * @param string $content 模板输出内容
         * @param string $prefix 模板缓存前缀
         * @return string
         */
        public function fetch($templateFile='',$content='',$prefix='') {
            /*print_r(func_get_args());exit();
            打印fetch方法的参数
                Array
                (
                    [0] => themes/simplebootx/Portal/judge.html
                    [1] => 
                    [2] => 
                )
            */
            if(empty($content)) {//是空的
    
                $templateFile   =   $this->parseTemplate($templateFile);//又去调用parseTemplate方法,好,去第27步看看这个是干啥的
                // 模板文件不存在直接返回
                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);
    
            if('php' == strtolower(C('TMPL_ENGINE_TYPE'))) { // 使用PHP原生模板
                $_content   =   $content;
                // 模板阵列变量分解成为独立变量
                extract($this->tVar, EXTR_OVERWRITE);
                // 直接载入PHP模板
                empty($_content)?include $templateFile:eval('?>'.$_content);
            }else{
                // 视图解析标签
                $params = array('var'=>$this->tVar,'file'=>$templateFile,'content'=>$content,'prefix'=>$prefix);
                //print_r($params);exit();
                Hook::listen('view_parse',$params);
            }
            // 获取并清空缓存
            $content = ob_get_clean();
            // 内容过滤标签
            Hook::listen('view_filter',$content);
            // 输出模板文件
            return $content;
        }

    27.View::parseTemplate方法

        /**
         * 自动定位模板文件
         * @access protected
         * @param string $template 模板文件规则
         * @return string
         */
        public function parseTemplate($template='') {
            if(is_file($template)) {
                return $template;//走到这里了,下面先不看了,日后再说,去28步
            }
            $depr       =   C('TMPL_FILE_DEPR');
            $template   =   str_replace(':', $depr, $template);
    
            // 获取当前模块
            $module   =  MODULE_NAME;
            if(strpos($template,'@')){ // 跨模块调用模版文件
                list($module,$template)  =   explode('@',$template);
            }
            // 获取当前主题的模版路径
            defined('THEME_PATH') or    define('THEME_PATH', $this->getThemePath($module));
    
            // 分析模板文件规则
            if('' == $template) {
                // 如果模板文件名为空 按照默认规则定位
                $template = CONTROLLER_NAME . $depr . ACTION_NAME;
            }elseif(false === strpos($template, $depr)){
                $template = CONTROLLER_NAME . $depr . $template;
            }
            $file   =   THEME_PATH.$template.C('TMPL_TEMPLATE_SUFFIX');
            if(C('TMPL_LOAD_DEFAULTTHEME') && THEME_NAME != C('DEFAULT_THEME') && !is_file($file)){
                // 找不到当前主题模板的时候定位默认主题中的模板
                $file   =   dirname(THEME_PATH).'/'.C('DEFAULT_THEME').'/'.$template.C('TMPL_TEMPLATE_SUFFIX');
            }
            return $file;
        }

    28.由于27步View::parseTemplate返回了$template即模板的路径,我们回到26步的fetch方法中View::parseTemplate调用的地方,有空接着写

  • 相关阅读:
    01-Django 简介
    函数及函数的嵌套等
    循环及循环嵌套
    运算符
    if, elif, else及if嵌套
    变量及变量计算和引用
    Python的3种执行方式
    Jmeter设置默认中文启动
    Vysor
    python基础学习(二)
  • 原文地址:https://www.cnblogs.com/ch459742906/p/5949168.html
Copyright © 2020-2023  润新知