[TOC]
* * * * *
## 1 視圖模板
### 視圖模板概覽

### 模板內容解析

* * * * *
### 視圖模板意義
>[info] 視圖模板是控制器操作的輸出組織
>主要包含視圖模板操作與模板文件解析兩部分
>最終以web頁面的形式組織控制器操作結果數據
**操作部分:視圖模板配置,模板變量準備**
**解析部分:模板變量,模板內容,模板標簽解析**
* * * * *
### 視圖模板操作層次
**控制器操作:** 處理具體業務邏輯,準備輸出數據
**視圖層操作:** 輸出數據賦值模板變量,模板主題,模板文件定位
**模板層解析:** 模板文件解析,
**標簽層解析:** 模板文件標簽解析
**模板緩存層讀寫:** 讀寫模板編譯結果
* * * * *
## 2 視圖模板配置
### 1 全局配置 thinkphp\convention.php
>[info] 定義View配置參數,
~~~
$view->config
return [
'theme_on' => false,
'default_theme' => 'default',
'view_path' => '',
'view_suffix' => '.html',
'view_depr' => DS,
'view_layer' => VIEW_LAYER,
'parse_str' => [],
'namespace' => '\\think\\view\\driver\\',
'template' => [],
];
~~~
>[info] 定義Template配置參數
~~~
$Template->config
return [
'view_path' => '',
'view_suffix' => '.html',
'view_depr' => DS,
'cache_suffix' => '.php',
'tpl_deny_func_list' => 'echo,exit',
'tpl_deny_php' => false,
'tpl_begin' => '{',
'tpl_end' => '}',
'strip_space' => false,
'tpl_cache' => true,
'compile_type' => 'file',
'cache_prefix' => '',
'cache_time' => 0,
'layout_on' => false,
'layout_name' => 'layout',
'layout_item' => '{__CONTENT__}',
'taglib_begin' => '{',
'taglib_end' => '}',
'taglib_load' => true,
'taglib_build_in' => 'cx',
'taglib_pre_load' => '',
'display_cache' => false,
'cache_id' => '',
'tpl_replace_string' => [],
'tpl_var_identify' => 'array',
'namespace' => '\\think\\template\\driver\\',
];
~~~
>[info] tp5.0Rc2中
> 視圖與模板引擎初始化時不再自動獲取配置參數
> 需要在初始化時手動獲取配置參數
### 2 應用配置 application\config.php
配置參數同全局配置參數設置
>[info] tp5.0Rc2中
> 視圖與模板引擎初始化時不再自動獲取配置參數
> 需要在初始化時手動獲取配置參數
### 3 初始化配置
配置參數同全局配置參數設置
~~~
$view->new think\View( think\Config::get())
$controller->engine( think\Config::get())
~~~
## 4 控制器層操作 think\Controller.php
### 控制器層模板變量賦值 assign()
### 控制器層模板引擎初始化 engine()
`public function engine($engine, $config = [])`
> $engine:模板引擎名稱
> $config:模板引擎配置
`$this->view->engine($engine, $config);`
調用think\View.php視圖對象的engine()初始化模板引擎
## 5 控制器層解析 think\Controller.php
### 控制器層模板解析 fetch()
>[info] 模板解析 fetch()
`public function fetch($template = '', $vars = [], $cache_id = '')`
~~~
$template :指定模板文件名,默認為**控制器/操作.模板后綴**
$vars :模板變量數組
$cache_id :模板緩存id
~~~
` return $this->view->fetch($template, $vars, $cache_id);`
調用think\View的fetch() 解析模板文件內容
### 控制器層內容渲染 show()
>[info] 渲染內容輸出 show()
`public function show($content, $vars = [])`
> $content:待解析內容
> $vars: 模板變量
`return $this->view->show($content, $vars);`
調用think\View的show() 渲染內容輸出
## 6 視圖層操作 think\View.php
### 視圖初始化 instance(), __construct(),config(),engine()
>[info] 視圖實例初始化 instance()
`public static function instance(array $config = [])`
> $config:視圖配置參數
**單例模式對象實例化**
~~~
if (is_null(self::$instance)) {
self::$instance = new self($config);
}
~~~
如果沒有實例對象,初始化視圖對象實例
`return self::$instance;`
如果有實例對象,返回實例對象
>[info] 視圖構造函數 __construct()
`public function __construct(array $config = [])`
> $config: View相關配置參數
~~~
$this->config($config);
~~~
調用think\View->config()設置視圖配置參數
~~~
if (!isset($this->config['template']['type'])) {
$this->config['template']['type'] = 'think';
}
~~~
檢查模板引擎類型配置,默認為think
>[info] 視圖參數配置 config()
`public function config($config = '', $value = null)`
> $config:視圖配置參數名,或視圖配置參數數組
> $value:視圖配置參數名對應值
**讀寫四功能函數:** 數組寫,鍵值讀,鍵值寫,數組讀
~~~
if (is_array($config)) {
foreach ($this->config as $key => $val) {
if (isset($config[$key])) {
$this->config[$key] = $config[$key];
}
}
}
~~~
**1 $config為數組,數組寫 覆蓋到think\view->config[]對應參數**
~~~
elseif (is_null($value)) {
return $this->config[$config];
}
~~~
**2 $config為字符串,$value為空時,鍵值讀 讀取$config鍵名對應配置值**
~~~
else {
$this->config[$config] = $value;
}
~~~
**3 $config,$value都為字符串時,鍵值寫 設置$config對應值為$value**
`return $this;`
**4 $config,$value都為空時,數組讀 讀取整個配置**
>[info] 視圖模板引擎初始化 engine()
`public function engine($engine, array $config = [])`
> $engine:模板引擎名稱
> $config:模板引擎參數
~~~
if ('php' == $engine) {
$this->engine = 'php';
}
~~~
php模板引擎
`$class = $this->config['namespace'] . ucfirst($engine);`
非php模板引擎類名
~~~
if (empty($this->config['view_path']) && defined('VIEW_PATH')) {
$this->config['view_path'] = VIEW_PATH;
}
~~~
視圖文件路徑
默認為base.php的VIEW_PATH
~~~
$config = array_merge($config, [
'view_path' => $this->config['view_path'],
'view_suffix' => $this->config['view_suffix'],
'view_depr' => $this->config['view_depr'],
]);
~~~
參數覆蓋,默認配置覆蓋傳入的配置參數?
`$this->engine = new $class($config);`
創建模板引擎,
默認為 think\viewdriver\Think類的對象
`return $this;`
返回視圖對象,**實現鏈式操作**。
### 視圖變量操作 __set() __get() __isset()
~~~
public function __set($name, $value)
public function __get($name)
public function __isset($name)
~~~
> $name:模板變量名稱
> $value:模板變量值
### 開啟,關閉,設置模板主題 theme()
**讀寫三功能函數:** 開啟,關閉,設置
~~~
if (true === $theme) {
$this->config['theme_on'] = true;
} elseif (false === $theme) {
$this->config['theme_on'] = false;
}
~~~
開啟與關閉主題設置
~~~
else {
$this->config['theme_on'] = true;
$this->theme = $theme;
}
~~~
開啟模板主題,設置模板主題
`return $this;`
返回視圖對象,**實現鏈式操作**
設置,獲取,檢查模板變量
## 7 視圖層解析 think\View.php
### 模板文件解析 fetch()
`public function fetch($template = '', $vars = [], $config = [], $renderContent = false)`
> $template:待解析模板文件與模板內容
> $vars:模板變量
> $config:模板解析引擎配置參數
> $renderContent:是否渲染內容控制
true:返回模板文件名,false:解析后輸出
`$vars = array_merge($this->data, $vars);`
合并傳入模板參數$vars到$this->data
`if (!$renderContent) {}`
**不需要解析時** 返回模板文件名
`$template = $this->parseTemplate($template);`
獲取模板文件名,并返回。
~~~
if (!is_file($template) || (APP_DEBUG && IS_WIN && realpath($template) != $template)) {
throw new Exception('template file not exists:' . $template, 10700);
}
~~~
WIN環境模板不存在,拋出異常
`APP_DEBUG && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export($vars, true) . ' ]', 'info');`
開啟調試,記錄日志信息
~~~
if (is_null($this->engine)) {
$this->engine($this->config['template']['type'], $this->config['template']);
}
~~~
**需要解析時**,首先檢查模板引擎是否初始化。
~~~
ob_start();
ob_implicit_flush(0);
~~~
開啟輸出控制,關閉絕對刷送
有關輸出控制 見 php的輸出控制
~~~
if ('php' == $this->engine || empty($this->engine)) {
extract($vars, EXTR_OVERWRITE);
is_file($template) ? include $template : eval('?>' . $template);
}
~~~
**原生php引擎處理**
導入模板變量到符號表,
加載或者執行模板文件內容。
~~~
else {
$this->engine->fetch($template, $vars, $config);
}
~~~
**非原生php引擎處理**
調用$this->engine->fetch()對模板文件進行解析。
默認使用think\view\driver\Think->fetch()
`$content = ob_get_clean();`
獲取緩存內容并清空
`APP_HOOK && Hook::listen('view_filter', $content);`
標簽過濾回調處理
~~~
if (!empty($this->config['parse_str'])) {
$replace = $this->config['parse_str'];
$content = str_replace(array_keys($replace), array_values($replace), $content);
}
~~~
用戶自定義的字符串替換
~~~
if (!Config::get('response_auto_output')) {
return Response::send($content, Response::type());
}
~~~
關閉自動響應輸出??
直接輸出模板內容字符串到客戶端。
`return $content;`
返回解析后的內容
### 模板內容解析 show()
`public function show($content, $vars = [])`
> $content:模板內容
> $vars:模板變量
`return $this->fetch($content, $vars, '', true);`
直接調用think\View->fetch();
解析模板內容
### 文件層解析 parseTemplate(),getTemplateTheme()
>[info] 模板文件定位 parseTemplate()
`private function parseTemplate($template)`
$template:模板文件參數
~~~
if (is_file($template)) {
return realpath($template);
}
~~~
如果是文件名,直接返回文件路徑名
**獲取模板目錄**
~~~
if (empty($this->config['view_path']) && defined('VIEW_PATH')) {
$this->config['view_path'] = VIEW_PATH;
}
~~~
配置的模板文件目錄獲取。
~~~
$theme = $this->getTemplateTheme();
$this->config['view_path'] .= $theme;
~~~
獲取模板文件主題目錄。
~~~
$depr = $this->config['view_depr'];
$template = str_replace(['/', ':'], $depr, $template);
~~~
替換模板文件路徑中的分隔符
~~~
if (strpos($template, '@')) {
list($module, $template) = explode('@', $template);
$path = APP_PATH . (APP_MULTI_MODULE ? $module . DS : '') . $this->config['view_layer'] . DS;
}
~~~
模板文件路徑包含"@"符號時,定位當前模塊模板目錄
~~~
else {
$path = $this->config['view_path'];
}
~~~
沒有包含"@"時,定位默認配置模板目錄
**獲取模板文件名**
~~~
if (defined('CONTROLLER_NAME')) {
if ('' == $template) {
$template = str_replace('.', DS, CONTROLLER_NAME) . $depr . ACTION_NAME;
} elseif (false === strpos($template, $depr)) {
$template = str_replace('.', DS, CONTROLLER_NAME) . $depr . $template;
}
}
~~~
添加控制器名稱到模板文件名
`return realpath($path) . DS . $template . $this->config['view_suffix'];`
返回模板目錄,模板文件名,模板后綴合并的字符串作為模板文件名。
>[info]獲取模板主題名稱 getTemplateTheme()
~~~
if ($this->config['theme_on']) {
if ($this->theme) {
$theme = $this->theme;
} else {
$theme = $this->config['default_theme'];
}
}
~~~
開啟模板主題時,獲取模板主題或者默認主題
`return isset($theme) ? $theme . DS : '';`
返回模板主題或者空字符串
## 8 模板引擎操作 think\view\driver\Think.php,think\Template.php
### 模板引擎初始化 think\View->engine()
>[info] 初始化入口 think\View.php
上面模板引擎初始化過程think\View->engine()中
`this->engine = new $class($config);`
這里默認創建think\view\driver\Think類
### 視圖引擎構造函數 $Think_engine->__construct()
`public function __construct($config = [])`
> $config:配置參數
~~~
{
$this->template = new Template($config);
}
~~~
創建think\Template.php類的對象實例作為模板引擎。
### 視圖引擎解析入口 $Think_engine->fetch()
`public function fetch($template, $data = [], $config = [])`
> $template:模板文件名
> $data:模板變量
> $config:模板配置參數
~~~
if (is_file($template)) {
$this->template->display($template, $data, $config);
}
~~~
$template為文件名時,
調用think\Template->display()解析文件
~~~
else {
$this->template->fetch($template, $data);
}
~~~
$template為字符串內容時
調用think\Template->fetch()渲染內容
### 視圖引擎其他操作 $Think_engine->__call()
`public function __call($method, $params)`
> $method 調用方法
> $params 調用參數
~~~
{
return call_user_func_array([$this->template, $method], $params);
}
~~~
使用魔術方法__call()
調用$this->template->$method($params);
### 模板引擎構造函數$Template->__construct()
`public function __construct(array $config = [])`
> $config:解析引擎配置參數
`$this->config['cache_path'] = RUNTIME_PATH . 'temp' . DS;`
緩存目錄配置
`$this->config = array_merge($this->config, $config);`
配置參數合并
~~~
$this->config['taglib_begin'] = $this->stripPreg($this->config['taglib_begin']);
$this->config['taglib_end'] = $this->stripPreg($this->config['taglib_end']);
$this->config['tpl_begin'] = $this->stripPreg($this->config['tpl_begin']);
$this->config['tpl_end'] = $this->stripPreg($this->config['tpl_end']);
~~~
標簽庫與普通標簽開始結束標記配置
~~~
$type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File';
$class = $this->config['namespace'] . ucwords($type);
~~~
模板編譯存儲驅動類型配置
`$this->storage = new $class();`
創建模板編譯緩存驅動對象
### 模板擎配置讀寫$Template->config()
`public function config($config)`
> $config:解析引擎配置參數
**讀寫功能函數:** 數組寫,鍵值對讀
~~~
if (is_array($config)) {
$this->config = array_merge($this->config, $config);
} elseif (isset($this->config[$config])) {
return $this->config[$config];
}
~~~
$config為數組,合并到配置參數
$config為字符串,讀取配置鍵名對應值
### 模板引擎配置賦值$Template->__set()
`public function __set($name, $value)`
> $name:配置鍵名
> $value:配置鍵值
`$this->config[$name] = $value;`
設置配置鍵值對關系
### 模板引擎變量賦值$Template->assign()
`public function assign($name, $value = '')`
> $name:變量名
> $value:變量值
數組字符串兩寫功能:數組寫,鍵值對
~~~
if (is_array($name)) {
$this->data = array_merge($this->data, $name);
} else {
$this->data[$name] = $value;
}
~~~
數組,合并到模板變量
鍵值對,鍵值對賦值
### 模板引擎變量獲取$Template->get()
`public function get($name = '')`
> $name:待獲取變量名
~~~
if ('' == $name) {
return $this->data;
}
~~~
空變量名,讀取所有模板變量
~~~
$data = $this->data;
foreach (explode('.', $name) as $key => $val) {
if (isset($data[$val])) {
$data = $data[$val];
} else {
$data = null;
break;
}
}
~~~
變量二級讀取。$test.va1格式變量
`return $data;`
返回讀取的對應變量
### 模板內容布局操作 $Template->layout()
`public function layout($name) `
> $name:布局控制
> true:開啟,false:關閉,字符串:設置layout_name
~~~
if (false === $name) {
$this->config['layout_on'] = false;
}
~~~
$name為false,關閉布局
~~~
else {
$this->config['layout_on'] = true;
if (is_string($name)) {
$this->config['layout_name'] = $name;
}
}
~~~
$name非false,開啟布局,如果是字符串設置布局名稱
`return $this;`
返回對象,實現鏈式操作
## 9 模板內容層解析 think\Template.php
### 1 模板文件編譯入口 display()
`public function display($template, $vars = [], $config = [])`
> $tempalte:模板文件名稱
> $vars:模板變量數組
> $config:模板引擎配置參數
~~~
if ($vars) {
$this->data = $vars;
}
~~~
模板變量設置
~~~
if ($config) {
$this->config($config);
}
~~~
模板引擎配置參數設置
其中的cache_id對應每個渲染模板文件
`if (!empty($this->config['cache_id']) && $this->config['display_cache']) {}`
模板渲染緩存檢測,
**這里的緩存使用Cache緩存php運行后生成的web頁面形式緩存**
不同于使用$Template->storage->write()緩存的編譯模板。
后者包含php代碼,前者不包含php代碼。
前者是一種更高效的緩存技術。可以實現靜態頁面生成。
` $cacheContent = Cache::get($this->config['cache_id']);`
調用Cache::get()獲取渲染緩存內容
這里使用了tp框架內置緩存機制,
為了目標引擎插件化可以解耦這里的Cache
將其設置為配置參數。
~~~
if ($cacheContent !== false) {
echo $cacheContent;
return;
}
~~~
渲染緩存存在的情況下,輸出渲染緩存。
這里返回到View->fetch(),即控制器中的$view->fetch()。
如果沒有開啟渲染緩存,則查找編譯模板。
`$template = $this->parseTemplateFile($template);`
調用template->parseTemplateFile()獲取對應模板文件名
這里的parseTemplateFile()與View.php的parseTemplate()相似
**后者是在View.php的fetch()的參數$renderContent為false,**
**不進行模板編譯時,查找目標文件所用**
**進行模板編譯時,查找目標文件時所用。**
`if ($template) {}`
獲取模板文件名成功時,繼續解析
`$cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($template) . $this->config['cache_suffix'];`
合成緩存文件名稱
` if (!$this->checkCache($cacheFile)) {}`
緩存文件檢查,如果緩存失效,進行重新編譯
~~~
$content = file_get_contents($template);
$this->compiler($content, $cacheFile);
~~~
緩存失效時,讀取模板文件內容,并編譯
緩存可用時,獲取模板編譯緩存
ob_start();
ob_implicit_flush(0);
`$this->storage->read($cacheFile, $this->data);`
讀取模板文件編譯存儲
`$content = ob_get_clean();`
讀取模板文件編譯內容
~~~
if (!empty($this->config['cache_id']) && $this->config['display_cache']) {
Cache::set($this->config['cache_id'], $content, $this->config['cache_time']);
}
~~~
檢查是否需要保存編譯生成的web模板文件
調用Cache:set()保存編譯生成的web模板文件
`echo $content;`
輸出解析結果
### 2 模板內容編譯入口 fetch()
`public function fetch($content, $vars = [])`
> $contens:待編譯模板內容
> $vars:模板變量
~~~
if ($vars) {
$this->data = $vars;
}
~~~
保存模板變量
`$cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($content) . $this->config['cache_suffix'];`
合成模板編譯緩存文件名
~~~
if (!$this->checkCache($cacheFile)) {
$this->compiler($content, $cacheFile);
}
~~~
模板編譯緩存無效,重新編譯
`$this->storage->read($cacheFile, $this->data);`
讀取存儲的編譯模板緩存
### 3 模板文件查找 parseTemplateFile()
`private function parseTemplateFile($template)`
> $template:待查找文件名
`if (false === strpos($template, '.')) {}`
模板文件名中包含符號".",模板文件的后綴
~~~
if (strpos($template, '@')) {
$template = str_replace(['/', ':'], $this->config['view_depr'], $template);
$template = APP_PATH . str_replace('@', '/' . basename($this->config['view_path']) . '/', $template) . $this->config['view_suffix'];
}
~~~
跨模塊模板查找解析。
首先替換"/" ":"為配置的模板分隔符
然后替換"@"為"/",并添加應用路徑/模板路徑和模板后綴到文件名
~~~
else {
if (strpos($template, ':')) {
list($theme, $template) = explode(':', $template, 2);
$path = dirname($this->config['view_path']) . DS . $theme . DS;
} else {
$path = $this->config['view_path'];
}
$template = str_replace(['/', ':'], $this->config['view_depr'], $template);
$template = $path . $template . $this->config['view_suffix'];
}
~~~
當前模塊模板操作
首先解析模板文件名模板主題符號":",
然后替換掉模板文件分割符為配置的分隔符
最后合并模板路徑,模板文件名,模板文件后綴,
~~~
if (is_file($template)) {
$this->includeFile[$template] = filemtime($template);
return $template;
}
~~~
對應模板文件存在時,將對應模板文件加載時間保存到$this->includeFile[]
這里用作模板編譯時間標識。
并返回模板文件名
~~~
else {
throw new Exception('template not exist:' . $template, 10700);
}
~~~
文件不存在拋出異常
### 4 包含文件名解析 parseTemplateName()
`private function parseTemplateName($templateName)`
$templateName:需要包含的文件名
`$array = explode(',', $templateName);`
多個模板文件
`foreach ($array as $templateName) {}`
遍歷加載文件
~~~
if (empty($templateName)) {
continue;
}
~~~
檢查數組
if (0 === strpos($templateName, '$')) {
$templateName = $this->get(substr($templateName, 1));
}
獲取變量文件名
`$template = $this->parseTemplateFile($templateName);`
查找需要加載的文件
~~~
if ($template) {
$parseStr .= file_get_contents($template);
}
~~~
文件存在則進行讀取
`return $parseStr;`
返回讀取的文件內容
### 5 模板編譯緩存檢查 checkCache()
`private function checkCache($cacheFile)`
> $cacheFile:緩存文件名
~~~
if (!$this->config['tpl_cache']) {
return false;
}
~~~
未開啟緩存,返回false
~~~
if (!is_file($cacheFile)) {
return false;
}
~~~
文件不存在,返回false
~~~
if (!$handle = @fopen($cacheFile, "r")) {
return false;
}
~~~
讀取文件失敗,返回false
`preg_match('/\/\*(.+?)\*\//', fgets($handle), $matches);`
讀取文件第一行
~~~
if (!isset($matches[1])) {
return false;
}
~~~
讀取內容為空,返回false
`$includeFile = unserialize($matches[1]);`
對第一行解序列化
~~~
if (!is_array($includeFile)) {
return false;
}
~~~
檢查是否存在包含文件$Template->$inculdeFile[]中
不存在,返回false
~~~
foreach ($includeFile as $path => $time) {
if (is_file($path) && filemtime($path) > $time) {
return false;
}
}
~~~
對比模板文件更新時間與上次編譯時間,
如果模板文件有更新,則返回false
~~~
return $this->storage->check($cacheFile, $this->config['cache_time']);
~~~
最后調用$this->storage->check()檢查緩存有效期,并返回檢查結果
### 6 模板內容編譯過程 compiler()
`private function compiler(&$content, $cacheFile)`
> $content:待編譯內容引用,避免復制傳參帶來的內存消耗
> $cacheFile:緩存文件名
`if ($this->config['layout_on']) {}`
模板布局啟動檢查
~~~
if (false !== strpos($content, '{__NOLAYOUT__}')) {
$content = str_replace('{__NOLAYOUT__}', '', $content);
}
~~~
將模板內容中設置為不使用布局的部分去除掉。
else {
$layoutFile = $this->parseTemplateFile($this->config['layout_name']);
if ($layoutFile) {
$content = str_replace($this->config['layout_item'], $content, file_get_contents($layoutFile));
}
}
讀取布局模板文件 如:index/view/controller_layout.html
讀取布局模板成功時
將布局模板index/view/controller_layout.html中的{__CONTENT__}
替換為模板內容,并獲取替換結果。
分析這里可知道
>[info] 布局模板是模板內容的上層組織
> 通常使用布局模板定義頁面的總體結構,如頁頂,頁腳,頁邊框
> 布局模板中可替換內容對應功能頁面組織, 如內容主體
`$this->parse($content);`
對模板內容進行解析編譯。
>[info] 前面的模板解析在文件層檢查其渲染緩存,編譯緩存,布局文件
> 這里是模板內容解析的總入口
> 下面的模板解析在內容層開始解析模板標簽等內容
### 7 模板內容解析總入口 parse()
`public function parse(&$content)`
> $content:待解析模板文件內容
~~~
if (empty($content)) {
return;
}
~~~
進行參數有效檢查如果待解析內容為空,直接返回
~~~
$this->parseLiteral($content);
$this->parseExtend($content);
$this->parseLayout($content);
$this->parseInclude($content);
$this->parseLiteral($content);
~~~
>[info] 1 解析原生標簽literal內容 parseliteral()
> 2 解析extend標簽內容 parseExtend()
> 3 解析layout標簽內容 parseLayout()
> 4 解析include標簽內容 parseInclude()
> 5 再次解析包含文件中可能攜帶的原生標簽literal內容 parseliteral()
`$this->parsePhp($content);`
php的語法檢查
`if ($this->config['taglib_load']) {}`
允許加載其他標簽庫
`$tagLibs = $this->getIncludeTagLib($content);`
對模板內容的標簽列表進行加載。
~~~
if (!empty($tagLibs)) {
foreach ($tagLibs as $tagLibName) {
$this->parseTagLib($tagLibName, $content);
}
}
~~~
對標簽列表依次進行加載
### 8 literal標簽內容解析 parseLiteral()
`private function parseLiteral(&$content, $restore = false)`
> $content:待解析內容引用
> $restore:內容是否需要還原
~~~
$regex = $this->getRegex($restore ? 'restoreliteral' : 'literal');
~~~
獲取literal標簽內容的正則字符串。
`if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {}`
正則匹配內容中的literal標簽,并保存到$matches數組中。
有關preg_match_all()見php的正則表達式
` if (!$restore) {}`
literal原生標簽內容替換處理,
將literal原生標簽內容保存到$this->literal[]數組
并使用特殊字符串替換掉。
`$count = count($this->literal);`
原生標簽內容緩存數組。
對于需要替換的內容進行保存。
這里獲取數組的下個數字鍵值,
對應當前處理的literal原生標簽內容
`foreach ($matches as $match) {}`
遍歷匹配到的literal標簽內容
`$this->literal[] = substr($match[0], strlen($match[1]), -strlen($match[2]));`
緩存需要恢復的literal原生標簽內容
`$content = str_replace($match[0], "<!--###literal{$count}###-->", $content);`
替換需要literal原生標簽內容為對應$count鍵標記
~~~
$count++;
~~~
數字鍵自增,保存下個literal原生標簽內容
~~~
else {
foreach ($matches as $match) {
$content = str_replace($match[0], $this->literal[$match[1]], $content);
}
$this->literal = [];
}
~~~
else對模板內容中的literal標簽替換字符串
還原為$this->literal[]數組中保存的原生內容
`unset($matches);`
刪除正則內容,減少內存消耗
` return;`
直接返回調用處,
因為處理的引用字符串,不需要返值
### 9 extend標簽解析 parseExtend()
`private function parseExtend(&$content)`
> $content:待解析字符串引用
`$regex = $this->getRegex('extend');`
獲取extend標簽正則表達式字符串
`$array = $blocks = $baseBlocks = [];`
`$extend = '';`
$blocks block標簽數組,$baseBlocks 頂層block標簽數組
$extend 解析結果字符串聲明
**下面聲明了一個閉包函數,進行extend標簽內容解析**
關于閉包函數 見 php的閉包函數
`$func = function ($template) use (&$func, &$regex, &$array, &$extend, &$blocks, &$baseBlocks) {}`
閉包函數聲明
其中$template作為模板內容變量參數
use 包含父作用域變量參數
`if (preg_match($regex, $template, $matches)) {}`
正則模板內容中的extend標簽字符串
`if (!isset($array[$matches['name']])) {}`
檢查是否解析過這個標簽內容,防止重復解析
extend標簽,讀取繼承模板文件內容
`$array[$matches['name']] = 1;`
??
`$extend = $this->parseTemplateName($matches['name']);`
調用parseTemplateName() 根據文件名讀取繼承模板文件內容
`$func($extend);`
遞歸調用匿名函數$func,處理$extend的內容
`$blocks = array_merge($blocks, $this->parseBlock($template));`
調用parseBlock() 解析模板內容中extend標簽下的的block標簽內容
作為父類模板的的子類實現內容。
`return;`
extend標簽解析返回
`else {}`
`$baseBlocks = $this->parseBlock($template, true);`
調用parseBlock() 解析模板內容的block標簽內容
作為繼承的父類模板的blcok聲明內容
**$baseblocks,$blocks兩個block標簽內容通過name屬性對應,并替換。**
~~~
if (empty($extend)) {
$extend = $template;
}
~~~
extend外的block標簽內容,保存到結果
`$func($content);`
匿名函數處理模板內容
`if (!empty($extend)) {}`
返回解析結果內容不為空
`if ($baseBlocks) {}`
父模板block標簽內容數組為不空時
~~~
foreach ($baseBlocks as $name => $val) {}
~~~
遍歷父模板block表示內容數組
~~~
$replace = $val['content'];
~~~
父模板block標簽內容緩存
`if (!empty($children[$name])) {}`
防止重復遍歷
~~~
foreach ($children[$name] as $key) {
$replace = str_replace($baseBlocks[$key]['begin'] . $baseBlocks[$key]['content'] . $baseBlocks[$key]['end'], $blocks[$key]['content'], $replace);
}
~~~
將父模板block標簽替換為子模板對應block標簽內容
`if (isset($blocks[$name])) {}`
父模板$name的block標簽
在子模板block標簽數組$blocks中存在對應內容
需要進行子模板內容替換處理
`$replace = str_replace(['{__BLOCK__}', '{__block__}'], $replace, $blocks[$name]['content']);`
合并子模板的{__Bblock__}標簽內容
`if (!empty($val['parent'])) {}`
子模板的頂級block標簽
`$parent = $val['parent'];`
獲取其父標簽
~~~
if (isset($blocks[$parent])) {
$blocks[$parent]['content'] = str_replace($blocks[$name]['begin'] . $blocks[$name]['content'] . $blocks[$name]['end'], $replace, $blocks[$parent]['content']);
}
~~~
對子模板block中的內容進行替換處理
`$blocks[$name]['content'] = $replace;`
子模板內容
` $children[$parent][] = $name;`
子模板解析結果數組$chilldren
在上面的`if (!empty($children[$name])) {}`中使用
### 10 block標簽解析 parseBlock()
`private function parseBlock(&$content, $sort = false)`
> $content:待解析內容
> $sort:是否排序
`$regex = $this->getRegex('block');`
獲取block標簽的正則表達式
`if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {}`
匹配$content的block標簽內容到$matches
`$right = $keys = [];`
子模板塊內容$right,對應父模板鍵數組$keys?
`foreach ($matches as $match) {}`
遍歷匹配的結果數組$matches
`if (empty($match['name'][0])) {}`
這里是子模板的block內容解析
`if (count($right) > 0) {}`
這里的$right在下的else語句中添加
~~~
$tag = array_pop($right);
$start = $tag['offset'] + strlen($tag['tag']);
$length = $match[0][1] - $start;
~~~
單個標簽處理,獲取其開始與長度
~~~
$result[$tag['name']] = [
'begin' => $tag['tag'],
'content' => substr($content, $start, $length),
'end' => $match[0][0],
'parent' => count($right) ? end($right)['name'] : '',
];
~~~
子標簽內容的組合結果
$keys`[$tag['name']] = $match[0][1];`
保存標簽索引到$keys
~~~
$right[] = [
'name' => $match[2][0],
'offset' => $match[0][1],
'tag' => $match[0][0],
];
~~~
父標block簽壓棧處理
`unset($right, $matches);`
注銷無用內存
~~~
if ($sort) {
array_multisort($keys, $result);
}
~~~
排序處理
`return $result;`
返回block標簽內容
### 11 layout標簽解析 parseLayout()
`private function parseLayout(&$content)`
$content:待解析內容索引
`if (preg_match($this->getRegex('layout'), $content, $matches)) {}`
正則匹配布局字符串內容
`$content = str_replace($matches[0], '', $content);`
Layout標簽內容替換處理
`$array = $this->parseAttr($matches[0]);`
Layout表示屬性解析
`if (!$this->config['layout_on'] || $this->config['layout_name'] != $array['name']) {}`
布局檢測??
~~~
$layoutFile = $this->parseTemplateFile($array['name']);
~~~
加載布局模板文件
`if ($layoutFile) {}`
加載布局模板成功
`$replace = isset($array['replace']) ? $array['replace'] : $this->config['layout_item'];`
獲取替換目標
`$content = str_replace($replace, $content, file_get_contents($layoutFile));`
對布局模板內容進行替換
~~~
else {
$content = str_replace('{__NOLAYOUT__}', '', $content);
}
~~~
刪除模板內容中的{__NOLAYOUT__}
### 12 include標簽解析 parseInclude()
`private function parseInclude(&$content)`
> $content:待解析內容
`$regex = $this->getRegex('include');`
獲取include標簽字符串正則表達式
下面聲明閉包函數遞歸處理include標簽內容
`$func = function ($template) use (&$func, &$regex, &$content) {}`
閉包函數聲明
`if (preg_match_all($regex, $template, $matches, PREG_SET_ORDER)) {}`
正則匹配include標簽內容
~~~
$array = $this->parseAttr($match[0]);
$file = $array['file'];
unset($array['file']);
~~~
獲取其中的file屬性
`$parseStr = $this->parseTemplateName($file);`
加載需要包含的文件
### 13 php語法檢查 parsePhp()
`private function parsePhp(&$content)`
> $content:待解析模板內容
`$content = preg_replace('/(<\?(?!php|=|$))/i', '<?php echo \'\\1\'; ?>' . "\n", $content);`
對短標簽進行替換處理
~~~
if ($this->config['tpl_deny_php'] && false !== strpos($content, '<?php')) {
throw new Exception('not allow php tag', 11600);
}
~~~
禁止php代碼的模板檢測后拋出異常
### 14 標簽庫加載 getIncludeTagLib()
`private function getIncludeTagLib(&$content)`
> $content:待解析內容
`preg_match($this->getRegex('taglib'), $content, $matches)`
獲取Taglib標簽內容
~~~
$content = str_replace($matches[0], '', $content);
~~~
將匹配的內容去除
`return explode(',', $matches['name']);`
以逗號分割標簽庫名稱
`return null;`
如果匹配失敗,返回null。
### 15 標簽庫標簽解析 parseTagLib()
`public function parseTagLib($tagLib, &$content, $hide = false)`
> $tagLibName:標簽庫名稱
> $content:待解析內容引用
~~~
if (strpos($tagLib, '\\')) {
$className = $tagLib;
$tagLib = substr($tagLib, strrpos($tagLib, '\\') + 1);
}
~~~
根命名空間下的標簽庫類名$className
~~~
else {
$className = '\\think\\template\\taglib\\' . ucwords($tagLib);
}
~~~
標簽庫命名空間下的類名
`$tLib = new $className($this);`
創建標簽庫類實例對象
默認為think\template\taglib\Cx.php類
`$tLib->parseTag($content, $hide ? '' : $tagLib);`
調用對應標簽庫類實例對象對模板內容進行解析
這里進入標簽庫標簽解析
### 16 標簽屬性分析 parseAttr()
`public function parseAttr($str, $name = null)`
> $str:標簽字符串
> $name: 屬性名稱
`$regex = '/\s+(?>(?<name>[\w-]+)\s*)=(?>\s*)([\"\'])(?<value>(?:(?!\\2).)*)\\2/is';`
屬性正則表達式
~~~
if (preg_match_all($regex, $str, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
$array[$match['name']] = $match['value'];
}
unset($matches);
}
~~~
對標簽字符串$str中的屬性進行正則匹配,
以鍵值對形式保存到$array中。
~~~
if (!empty($name) && isset($array[$name])) {
return $array[$name];
} else {
return $array;
}
~~~
從$array中獲取$name對應屬性值,并返回
### 17 標簽正則字符串生成 getRegex()
`private function getRegex($tagName)`
> $tagName:標簽名
`$regex = '';`
生成結果字符串
`if ('tag' == $tagName) {}`
普通標簽生成
~~~
$begin = $this->config['tpl_begin'];
$end = $this->config['tpl_end'];
~~~
獲取普通標簽開始與結束標記
~~~
if (strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1) {}
~~~
??
~~~
$regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>[^' . $end . ']*))' . $end;
~~~
??生成的普通標簽正則字符串
`$regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>(?:(?!' . $end . ').)*))' . $end;`
??生成的普通標簽正則字符串
`else {}`
else的內容為標簽庫標簽生成
~~~
$begin = $this->config['taglib_begin'];
$end = $this->config['taglib_end'];
~~~
標簽庫標簽開始與結束標記
`$single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false;`
??
`switch ($tagName) {}`
根據標簽名參數$tagName,生成不同標簽
包括 literal,restoreliteral,include,
taglib,layout,extend,block
分析可知這里的標簽都是內容塊標簽。
~~~
$begin = $this->config['taglib_begin'];
$end = $this->config['taglib_end'];
~~~
標簽庫標簽的開始與結束標記
正則表達式相關見 php的正則表達式
## 10 模板普通標簽解析 Template\Template.php
### 1 普通標簽解析 parseTag()
`private function parseTag(&$content)`
> $content:待解析模板內容
### 2 模板變量解析 parseVar()
`public function parseVar(&$varStr)`
> $varStr:變量的內容
> 格式為{$varname|function1|function2=arg1,arg2}
### 3 模板變量過濾函數解析 parseVarFunction()
`public function parseVarFunction(&$varStr)`
> $varStr:變量的內容
> 格式為{$varname|function1|function2=arg1,arg2}
###4 框架模板變量解析 parseThinkVar()
`public function parseThinkVar(&$vars)`
> $vars: 變量內容
> 格式 以 $Think. 打頭的變量屬于特殊模板變量
## 11 模板標簽庫操作 Template\TagLib.php
### 標簽庫初始化
`public function __construct($template)`
### 獲取標簽列表
`public function getTags()`
### 標簽添加??
增加標簽格式
增加標簽解析
修改標簽解析
刪除標簽解析
## 12 模板標簽庫解析 template\TagLib.php,\taglib\Cx.php
>[info] TagLib.php
### 模板內容中標簽庫標簽解析 parseTag()
`public function parseTag(&$content, $lib = '')`
> $cotent:待解析內容
> $lib:標簽庫類
標簽分閉合與非閉合兩種解析
### 標簽庫標簽的屬性解析 parseAttr()
`public function parseAttr($str, $name, $alias = '')`
> $str: 標簽屬性字符串
> $name:屬性名
> $alias:屬性別名
### 標簽中條件表達式解析 parseCondition()
`public function parseCondition($condition)`
`public function autoBuildVar(&$name)`
`private function getRegex($tags, $close)`
>[info] template\taglib\Cx.php
### php標簽解析 _php()
`public function _php($tag, $content)`
### volist標簽解析 _volist()
`public function _volist($tag, $content)`
### foreach標簽解析 _foreach()
`public function _foreach($tag, $content)`
### if標簽解析 _if()
`public function _if($tag, $content)`
### elseif標簽解析 _elseif()
`public function _elseif($tag, $content)`
### else標簽解析 _else()
`public function _else($tag)`
### switc標簽解析 _switc()
`public function _switch($tag, $content)`
### case標簽解析 _case()
`public function _case($tag, $content)`
### defaul標簽解析 _defaul()
`public function _default($tag)`
### compare標簽解析 _compare()
`public function _compare($tag, $content)`
### range標簽解析 _range()
`public function _range($tag, $content)`
### present標簽解析 _present()
`public function _present($tag, $content)`
### notpresent標簽解析 _notpresent()
`public function _notpresent($tag, $content)`
### empty標簽解析 _empty()
`public function _empty($tag, $content)`
### notempty標簽解析 _notempty()
`public function _notempty($tag, $content)`
### defined標簽解析 _defined()
`public function _defined($tag, $content)`
### notdefined標簽解析 _notdefined()
`public function _notdefined($tag, $content)`
### import標簽解析 _import()
`public function _import($tag, $content, $isFile = false)`
### load標簽解析 _load()
`public function _load($tag, $content)`
### assign標簽解析 _assign()
`public function _assign($tag, $content)`
### define標簽解析 _define()
`public function _define($tag, $content)`
### for標簽解析 _for()
`public function _for($tag, $content)`
### url標簽解析 _url()
`public function _url($tag, $content)`
### functio標簽解析 _functio()
`public function _function($tag, $content)`
## 13 模板緩存讀寫 template\driver\File.php
`public function write($cacheFile, $content)`
`public function read($cacheFile, $vars = [])`
`public function check($cacheFile, $cacheTime)`
- 更新記錄
- 概述
- 文件索引
- 函數索引
- 章節格式
- 框架流程
- 前:章節說明
- 主:(index.php)入口
- 主:(start.php)框架引導
- 主:(App.php)應用啟動
- 主:(App.php)應用調度
- C:(Controller.php)應用控制器
- M:(Model.php)數據模型
- V:(View.php)視圖對象
- 附:(App.php)應用啟動
- 附:(base.php)全局變量
- 附:(common.php)模式配置
- 附:(convention.php)全局配置
- 附:(Loader.php)自動加載器
- 附:(Build.php)自動生成
- 附:(Hook.php)監聽回調
- 附:(Route.php)全局路由
- 附:(Response.php)數據輸出
- 附:(Log.php)日志記錄
- 附:(Exception.php)異常處理
- 框架工具
- 另:(helper.php)輔助函數
- 另:(Cache.php)數據緩存
- 另:(Cookie.php)cookie操作
- 另:(Console.php)控制臺
- 另:(Debug.php)開發調試
- 另:(Error.php)錯誤處理
- 另:(Url.php)Url操作文件
- 另:(Loader.php)加載器實例化
- 另:(Input.php)數據輸入
- 另:(Lang.php)語言包管理
- 另:(ORM.php)ORM基類
- 另:(Process.php)進程管理
- 另:(Session.php)session操作
- 另:(Template.php)模板解析
- 框架驅動
- D:(\config)配置解析
- D:(\controller)控制器擴展
- D:(\model)模型擴展
- D:(\db)數據庫驅動
- D:(\view)模板解析
- D:(\template)模板標簽庫
- D:(\session)session驅動
- D:(\cache)緩存驅動
- D:(\console)控制臺
- D:(\process)進程擴展
- T:(\traits)Trait目錄
- D:(\exception)異常實現
- D:(\log)日志驅動
- 使用范例
- 服務器與框架的安裝
- 控制器操作
- 數據模型操作
- 視圖渲染控制
- MVC開發初探
- 模塊開發
- 入口文件定義全局變量
- 運行模式開發
- 框架配置
- 自動生成應用
- 事件與插件注冊
- 路由規則注冊
- 輸出控制
- 多種應用組織
- 綜合應用
- tp框架整合后臺auto架構快速開發
- 基礎原理
- php默認全局變量
- php的魔術方法
- php命名空間
- php的自動加載
- php的composer
- php的反射
- php的trait機制
- php設計模式
- php的系統時區
- php的異常錯誤
- php的輸出控制
- php的正則表達式
- php的閉包函數
- php的會話控制
- php的接口
- php的PDO
- php的字符串操作
- php的curl
- 框架心得
- 心:整體結構
- 心:配置詳解
- 心:加載器詳解
- 心:輸入輸出詳解
- 心:url路由詳解
- 心:模板詳解
- 心:模型詳解
- 心:日志詳解
- 心:緩存詳解
- 心:控制臺詳解
- 框架更新
- 4.20(驗證類,助手函數)
- 4.27(新模型Model功能)
- 5.4(新數據庫驅動)
- 7.28(自動加載)