天天看點

視圖view類-源碼解析

視圖類view主要用于頁面内容的輸出,模闆調用等,用在控制器類中,可以使得控制器類把表現和資料結合起來。下面我們來看一下執行流程。

首先,在控制器類中保持着一個view類的對象執行個體,隻要繼承自控制器父類的類都可以使用這個執行個體,是以我們在控制器子類中就可以使用view類執行個體去很容易的調用模闆,輸出内容。

看Controller.class.php類的第22行和35行,分别聲明了一個執行個體變量,在構造函數中執行個體化一個view執行個體。

namespace Think;

/**

* ThinkPHP 控制器基類 抽象類

*/

abstract class Controller {

/**

* 視圖執行個體對象

* @var view

* @access protected

/**

* 控制器參數

* @var config

* @access protected

*/

protected $config = array();

/**

* 架構函數 取得模闆對象執行個體

* @access public

*/

public function __construct() {

Hook::listen('action_begin',$this->config);

//執行個體化視圖類

//控制器初始化

if(method_exists($this,'_initialize'))

$this->_initialize();

}

我們在控制器類中輸出模闆和内容主要調用控制器類中的display方法,但是我們檢視此方法就會發現,此方法裡面調用的還是view類的display方法,display的主要實作邏輯還在view類中。在view類的大概67行左後,我們就可以看到這個函數的實作過程,下面來分析一個display方法。

/**

* 加載模闆和頁面輸出 可以傳回輸出内容

* @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');

}

我們可以看到在display方法中也是分别調用多個函數來協作完成模闆處理工作的。

我們來整理分析一下流程。

dispaly方法接受一個templateFile參數,調用parseTemplate方法根據這個參數去偵測模闆檔案的位置,結合主題組合出一個模闆的位址,執行view_parse标簽行為,在行為類裡面去調用模闆引擎的fetch方法去解析模闆,傳回編譯後的内容。

是以我們知道,對于視圖類來說,大概分為以下幾步,第一是根據參數去偵測出模闆檔案的位址,然後調用模闆引擎去編譯模闆檔案,形成編譯緩存php檔案,然後執行php檔案傳回内容。

下面我們來分析一下tp是怎樣根據使用者傳進來的參數來偵測模闆檔案的位址的。核心代碼邏輯在view類的140行左右。

/**

* 自動定位模闆檔案

* @access protected

* @param string $template 模闆檔案規則

* @return string

*/

public function parseTemplate($template='') {

//如果$template是一個檔案位址的話,那麼就直接傳回該位址。這是最簡單的一種使用方式。

//這裡的檔案位址是一個相對檔案路徑位址,要注意模闆檔案位置是相對于項目的入口檔案

if(is_file($template)) {

return $template;

}

//擷取模闆檔案的分隔符,模闆檔案CONTROLLER_NAME與ACTION_NAME之間的分割符

$depr = C('TMPL_FILE_DEPR');

//将冒号替換成分隔符

$template = str_replace(':', $depr, $template);

// 擷取目前主題名稱

$theme = $this->getTemplateTheme();

/*

我們來看一下tp的模闆偵測邏輯。

首先tp要求我們傳入一個位址表達式。格式如下:

[子產品@][控制器:][操作] 比如: m@c:a 表示m子產品下的c控制器下的a方法

我們隻要給此方法傳入子產品,控制器和方法三個參數,這個方法就能給我們偵測出對應的模闆檔案位址。

具體來說,他會先定義出截止到子產品這個階段的目錄,然後在得到控制器到最後的模闆檔案位址,最後組合起來形成最終的檔案位址。

1:先定義子產品的目錄

首先解析位址表達式,如果有@,表示傳入了子產品位址,解析她,指派給module變量。

我們的控制器檔案夾和方法存放在哪裡呢?

如果定義了視圖目錄,就存放在視圖目錄中,如果沒有定義,就看看是否定義了模闆路徑,如果定義了就存放在該路徑下的對應子產品目錄下,如果沒有定義模闆路徑,預設就存放在應用檔案夾下的對應子產品檔案夾下的預設視圖層下。

最後我們會得到一個THEME_PATH表示控制器模闆存放的目錄。如果有主題的話加上主題

*/

// 擷取目前子產品

$module = MODULE_NAME;

if(strpos($template,'@')){ // 跨子產品調用模版檔案

list($module,$template) = explode('@',$template);

}

// 擷取目前主題的模版路徑

if(!defined('THEME_PATH')){

if(C('VIEW_PATH')){ // 子產品設定獨立的視圖目錄

$tmplPath = C('VIEW_PATH');

}else{ // 定義TMPL_PATH 改變全局的視圖目錄到子產品之外

$tmplPath = defined('TMPL_PATH')? TMPL_PATH.$module.'/' : APP_PATH.$module.'/'.C('DEFAULT_V_LAYER').'/';

}

define('THEME_PATH', $tmplPath.$theme);

}

// 分析模闆檔案規則

/*

然後分析位址表達式中的模闆規則,如果位址表達式為空,或者隻是傳了一個子產品名,那麼這裡的template就為空,那麼預設的位址就是預設控制器下的預設方法

*/

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;

}

到這裡為止,我們已經根據使用者傳入的模闆位址表達式得到了模闆檔案位址,接下來就是調用模闆引擎來解析這個模闆。在view類的fetch方法中并沒有直接調用模闆引擎template類的的方法去解析模闆,而是去調用了一個view_parse标簽,在這個标簽上綁定了行為模式擴充類ParseTemplateBehavior,模闆的解析就是在這個類的run方法中進行的,這個類總我們不僅而已使用tp自帶的模闆引擎,還可以使用其他開源第三方的模闆引擎類,具有很好的高擴充性。關于模闆引擎解析模闆的裸機,請看源碼分析8