### 模板编译流程,大概是:
1. 先获取到View类实例(依赖注入也好,通过助手函数也好)
2. 使用View编译方法fetch或display。都会通过engine方法获取到当前的模板驱动
3. 把模板以及要编译的数据传入到驱动中对应的display方法或fetch方法
4. display和fetch差不多(个人感觉),这俩的大致流程都是
5. 判断缓存文件失效没,如果失效重新编译放入缓存目录。如果没失效则直接读取输出或返回
### 1、app\\controller\\Index->Index() 控制器方法
```
public function index(View $view)
{
//定义一个data变量,是个关联数组
$data = [
'data'=>'我是变量data',
'msg'=>'我是变量msg'
];
//直接调用View实例中的fetch方法进行渲染。并把data传入进去
return $view->fetch('index',$data);
//第二种方式
$strings = "{$data}-{$msg}";
return $view->assign($data)->display($strings,$data);
}
```
上面代码所示,tp的渲染模板。默认有两种方式,一种是调用fetch直接进行渲染。第二种就是先调用assign方法传入要渲染的数据。然后再调用display方法进行渲染。(还有助手函数view().这里就不过多描述了)
### 2、接下来就讲讲tp是如何将数据渲染到模板上。并且输出①第一种方式首先会调用\\Think\\View视图类中的fetch方法
```
public function fetch(string $template = '', array $vars = []): string
{
//调用类中的getContent方法,并将模板文件、渲染的数据
//封装成匿名函数传入到getContent方法中
return $this->getContent(function () use ($vars, $template) {
$this->engine()->fetch($template, array_merge($this->data, $vars));
});
}
```
### 接下来看下getContent方法
```
//这个方法,主要就是把传入过来的匿名函数给运行一下
//通过ob_系列函数捕捉到缓冲区的输出
//然后将捕捉到的缓冲区内容返回
protected function getContent($callback): string
{
//开启缓存
ob_start();
//取消缓存输出到页面上
ob_implicit_flush(0);
//开始渲染
try {
//执行闭包
$callback();
} catch (\Exception $e) {
//如果闭包函数执行失败则清空缓存
ob_end_clean();
//然后弹出异常
throw $e;
}
//获取并清空缓存
$content = ob_get_clean();
//如果filter有闭包。一般为替换当前页面的什么东西。执行一下
if ($this->filter) {
$content = call_user_func_array($this->filter, [$content]);
}
//最后直接返回
return $content;
}
```
### 传入的闭包内容
```
//大概就是获取视图驱动,然后传入模板和数据,渲染模板
$this->engine()->fetch($template, array_merge($this->data, $vars));
```
```
### 追踪到engine方法//这个就不多解释什么了,主要就是获取视图驱动的实例
public function engine(string $type = null)
{
return $this->driver($type);
}
```
### 3、再追踪到tp默认视图驱动中的fetch方法
```
public function fetch(string $template, array $vars = []): void
//判断是否有数据如果有则合并this->data
if ($vars) {
$this->data = array_merge($this->data, $vars);
}
//判断有没有缓存,如果有。则直接输出缓存
if (!empty($this->config['cache_id']) && $this->config['display_cache'] && $this->cache) {
// 读取渲染缓存
if ($this->cache->has($this->config['cache_id'])) {
echo $this->cache->get($this->config['cache_id']);
return;
}
}
//解析传入过来的模板名
$template = $this->parseTemplateFile($template);
if ($template) {
//这里生成一下缓存路径+模板
$cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($this->config['layout_on'] . $this->config['layout_name'] . $template) . '.' . ltrim($this->config['cache_suffix'], '.');
//判断缓存文件是否还有效,或者存在
if (!$this->checkCache($cacheFile)) {
// 缓存无效 重新模板编译
//如果不存在,读取模板。并调用compiler重新编译
$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'] && $this->cache) {
// 缓存页面输出
$this->cache->set($this->config['cache_id'], $content, $this->config['cache_time']);
}
//输出编译后的内容
echo $content;
}
}
```
### 这里主要是编译模板的方法 compiler。主要就是把模板内的文件都替换掉
```
private function compiler(string &$content, string $cacheFile): void
{
// 判断是否启用布局
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));
}
}
} else {
$content = str_replace('{__NOLAYOUT__}', '', $content);
}
//这里是替换模板内的tp语法例如 {$data} = echo $data
//{if (1==1))} == <?php if (1==1): ?>
//{/endif} == <?php endif;?>
$this->parse($content);
if ($this->config['strip_space']) {
/* 去除html空格与换行 */
$find = ['~>\s+<~', '~>(\s+\n|\r)~'];
$replace = ['><', '>'];
$content = preg_replace($find, $replace, $content);
}
// 优化生成的php代码
$content = preg_replace('/\?>\s*<\?php\s(?!echo\b|\bend)/s', '', $content);
// 模板过滤输出
$replace = $this->config['tpl_replace_string'];
$content = str_replace(array_keys($replace), array_values($replace), $content);
// 添加安全代码及模板引用记录
$content = '<?php /*' . serialize($this->includeFile) . '*/ ?>' . "\n" . $content;
//写入缓存
$this->storage->write($cacheFile, $content);
$this->includeFile = [];
}
```
### 接着回到fetch方法中
```
/ 读取编译存储
$this->storage->read($cacheFile, $this->data);
```
### 这里是获取到\\Think\\template\\driver\\File类实例并且将缓存文件和数据传入到read方法中
```
public function read(string $cacheFile, array $vars = []): void
{
$this->cacheFile = $cacheFile;
if (!empty($vars) && is_array($vars)) {
//通过extract方法,将数据写到符号表内
extract($vars, EXTR_OVERWRITE);
}
//载入模版缓存文件
include $this->cacheFile;
}
```
# 到这里,第一种方式流程就算是完了
第二种、display方法去渲染
```
//这个和第一种流程第一步一样,不多解释
public function display(string $content, array $vars = []): string
{
return $this->getContent(function () use ($vars, $content) {
//调用template中的display方法,并将content和数据传入进去
$this->engine()->display($content, array_merge($this->data, $vars));
});
}
```
### 追踪到Template类中的display方法
```
public function display(string $content, array $vars = []): void
{
//判断有没有要编译的数据/如果有就和this->data叔祖1合并
if ($vars) {
$this->data = array_merge($this->data, $vars);
}
//获取到编译后的缓存模板
$cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($content) . '.' . ltrim($this->config['cache_suffix'], '.');
//调用checkCahce把缓存模板传入进去,查看是否过期
//或不存在
if (!$this->checkCache($cacheFile)) {
//如果模板无效或不存在,就调用
//compiler方法重写缓存
$this->compiler($content, $cacheFile);
}
// 读取编译存储
$this->storage->read($cacheFile, $this->data);
}
```
- 空白目录
- php语法结构
- 安装与更新
- 开启调试模式及代码跟踪器
- 架构
- 源码分析
- 应用初始化
- 请求流程
- 中间件源码分析
- 请求处理源码分析
- Request源码分析
- 模板编译流程
- 路由与请求流程
- 容器
- 获取目录位置
- 入口文件
- 多应用模式及URL访问
- 依赖注入与容器
- 容器属性及方法
- Container
- App
- facade
- 中间件(middleware)
- 系统服务
- extend 扩展类库
- 笔记
- 配置
- env配置定义及获取
- 配置文件的配置获取
- 单应用模式-(配置)文件目录结构(默认)
- 多应用模式(配置)文件目录结构(配置文件)
- 配置文件
- 应用配置:app.php
- 缓存配置: cache.php
- 数据库配置:database.php
- 路由和URL配置:route.php
- Cookie配置:cookie.php
- Session配置:session.php
- 命令行配置:console.php
- 多语言配置:lang.php
- 日志配置:log.php
- 页面Trace配置:trace.php
- 磁盘配置: filesystem.php
- 中间件配置:middleware.php
- 视图配置:view.php
- 改成用yaconf配置
- 事件
- 例子:省略事件类的demo
- 例子2:完整事件类
- 例子3:事件订阅,监听多个事件
- 解析
- 路由
- 路由定义
- 路由地址
- 变量规则
- MISS路由
- URL生成
- 闭包支持
- 路由参数
- 路由中间件
- 路由分组
- 资源路由
- 注解路由
- 路由绑定
- 域名路由
- 路由缓存
- 跨域路由
- 控制器
- 控制器定义
- 空控制器、空操作
- 空模块处理
- RESTFul资源控制器
- 控制器中间件
- 请求对象Request(url参数)
- 请求信息
- 获取输入变量($_POST、$_GET等)
- 请求类型的获取与伪装
- HTTP头信息
- 伪静态
- 参数绑定
- 请求缓存
- 响应对象Response
- 响应输出
- 响应参数
- 重定向
- 文件下载
- 错误页面的处理办法
- 应用公共文件common.php
- 模型
- 模型定义及常规属性
- 模型数据获取与模型赋值
- 查询
- 数据集
- 增加
- 修改
- 删除
- 条件
- 查询范围scope
- 获取器
- 修改器
- 搜索器
- 软删除
- 模型事件
- 关联预载入
- 模型关联
- 一对一关联
- 一对多关联
- 多对多关联
- 自动时间戳
- 事务
- 数据库
- 查询构造器
- 查询合集
- 子查询
- 聚合查询
- 时间查询
- 视图查询(比join简单)
- 获取查询参数
- 快捷方法
- 动态查询
- 条件查询
- 打印sql语句
- 增
- 删
- 改
- 查
- 链式操作
- 查询表达式
- 分页查询
- 原生查询
- JSON字段
- 链接数据库配置
- 分布式数据库
- 查询事件
- Db获取器
- 事务操作
- 存储过程
- Db数据集
- 数据库驱动
- 视图
- 模板
- 模板配置
- 模板位置
- 模板渲染
- 模板变量与赋值(assign)
- 模板输出替换
- url生成
- 模板详解
- 内置标签
- 三元运算
- 变量输出
- 函数输出
- Request请求参数
- 模板注释及原样输出
- 模板继承
- 模板布局
- 原生PHP
- 模板引擎
- 视图过滤
- 视图驱动
- 验证
- 验证进阶之最终版
- 错误和日志
- 异常处理
- 日志处理
- 调试
- 调试模式
- Trace调试
- SQL调试
- 变量调试
- 远程调试
- 杂项
- 缓存
- Session
- Cookie
- 多语言
- 上传
- 扩展说明
- N+1查询
- TP类库
- 扩展类库
- 数据库迁移工具
- Workerman
- think助手工具库
- 验证码
- Swoole
- request
- app
- Response
- View
- Validate
- Config
- 命令行
- 助手函数
- 升级指导(功能的添加与删除说明)
- siyucms
- 开始
- 添加页面流程
- 列表页加载流程
- 弹出框
- 基础控制器
- 基础模型
- 快速构建
- 表单form构建
- 表格table构建
- MakeBuilder
- 前端组件
- 日期组件
- layer 弹层组件
- Moment.js 日期处理插件
- siyucms模板布局
- 函数即其变量
- 前端页面
- $.operate.方法
- $.modal.方法:弹出层
- $.common.方法:通用方法
- 被cms重写的表格options
- 自定义模板
- 搜索框
- 自定义form表单
- 获取表单搜索参数并组装为url字符串