[TOC]
* * * * *
## 1 视图模板
### 视图模板概览
![](https://box.kancloud.cn/2016-03-18_56ebc970a2e86.jpg)
### 模板内容解析
![](https://box.kancloud.cn/2016-03-21_56efbf923bc3f.jpg)
* * * * *
### 视图模板意义
>[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(自动加载)