昨天在寫代碼的時候,看見寫了無數次的模版渲染方法:$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('Think\View'); //控制器初始化 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而來的,(這個參數是在網站根目錄\application\Common\Conf\config.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是干什么用的
此函數在"站點根目錄\application\Common\Common\function.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方法,此函數在"站點根目錄\simplewind\Core\Common\functions.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'的標簽,
以下代碼在:站點根目錄\application\Common\Conf\tags.php <?php return array( // 添加下面一行定義即可 'app_init'=>array('Common\Behavior\InitHookBehavior'), 'app_begin' => array('Behavior\CheckLangBehavior'), 'view_filter' => array('Common\Behavior\TmplStripSpaceBehavior'), 'testtag' => array('Behavior\TestBehavior'),////////////這是我添加的 );
'testtag'對應的行為在:站點根目錄\simplewind\Core\Library\Behavior\TestBehavior.class.php,
<?php namespace Behavior; class TestBehavior { public function run(&$params) {echo "i am TestBehavior::run"; print_r($params); } }
並在testController::judge方法中添加"testtag"標簽偵聽
<?php namespace Portal\Controller; use Common\Controller\HomebaseController; class TestController extends HomebaseController { public function judge(){ $para=[1,2,3,4,5]; \Think\Hook::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] => Behavior\BuildLiteBehavior [1] => Common\Behavior\InitHookBehavior ) [app_begin] => Array ( [0] => Behavior\ReadHtmlCacheBehavior [1] => Behavior\CheckLangBehavior ) [app_end] => Array ( [0] => Behavior\ShowPageTraceBehavior ) [view_parse] => Array ( [0] => Behavior\ParseTemplateBehavior ) [template_filter] => Array ( [0] => Behavior\ContentReplaceBehavior ) [view_filter] => Array ( [0] => Behavior\WriteHtmlCacheBehavior [1] => Common\Behavior\TmplStripSpaceBehavior ) [testtag] => Array ( [0] => Behavior\TestBehavior ) [footer] => Array ( [0] => Demo ) ) 當前$tags[$tag]是
array(1) { [0]=> string(21) "Behavior\TestBehavior" }
19.可以看到,Hook::$tags屬性將所有的標簽位(鈎子)都打印出來了,包括系統的和應用的
系統的:站點根目錄\simplewind\Core\Mode\common.php
return array( // 配置文件 'config' => array(......), // 別名定義 'alias' => array(......), // 函數和類文件 'core' => array(......), // 行為擴展定義 'tags' => array( 'app_init' => array( 'Behavior\BuildLiteBehavior', // 生成運行Lite文件 ), 'app_begin' => array( 'Behavior\ReadHtmlCacheBehavior', // 讀取靜態緩存 ), 'app_end' => array( 'Behavior\ShowPageTraceBehavior', // 頁面Trace顯示 ), 'view_parse' => array( 'Behavior\ParseTemplateBehavior', // 模板解析 支持PHP、內置模板引擎和第三方模板引擎 ), 'template_filter'=> array( 'Behavior\ContentReplaceBehavior', // 模板輸出替換 ), 'view_filter' => array( 'Behavior\WriteHtmlCacheBehavior', // 寫入靜態緩存 ), ), );
應用的:站點根目錄\application\Common\Conf\tags.php
<?php return array( // 添加下面一行定義即可 'app_init'=>array('Common\Behavior\InitHookBehavior'), 'app_begin' => array('Behavior\CheckLangBehavior'), 'view_filter' => array('Common\Behavior\TmplStripSpaceBehavior'), 'testtag' => array('Behavior\TestBehavior'), );
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:"Behavior\TestBehavior" $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中調用(Think\Think::start()),所以,去看看這個start方法是啥樣的
namespace Think; /** * ThinkPHP 引導類 */ class Think { ..... /** * 應用程序初始化 * @access public * @return void */ static public function start() { // 注冊AUTOLOAD方法 spl_autoload_register('Think\Think::autoload'); ......
}
}
原來自動裝載函數是Think\Think::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 在這里,返回的是命名空間的第一部分,如"Behavior\TestBehavior",返回"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的結果是:站點根目錄simplewind\Core\Library/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:"Behavior\TestBehavior" $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調用的地方,有空接着寫