TP6源码解析,ThinkPHP6模板编译流程详解

前言:刚开始写博客。如果觉得本篇文章对您有所帮助。点个赞再走也不迟

模板编译流程,大概是:

  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);
}

  

再追踪到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);
}

  

到这里整个流程就结束了,有兴趣的可以看下我另外一篇文章

tp6源码解析-第二天,ThinkPHP6编译模板流程详解,ThinkPHP6模板源码详解的更多相关文章

  1. Netty 源码解析(七): NioEventLoop 工作流程

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第七篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...

  2. Spring源码解析 | 第二篇:Spring IOC容器之XmlBeanFactory启动流程分析和源码解析

    一. 前言 Spring容器主要分为两类BeanFactory和ApplicationContext,后者是基于前者的功能扩展,也就是一个基础容器和一个高级容器的区别.本篇就以BeanFactory基 ...

  3. 【安卓网络请求开源框架Volley源码解析系列】定制自己的Request请求及Volley框架源码剖析

    通过前面的学习我们已经掌握了Volley的基本用法,没看过的建议大家先去阅读我的博文[安卓网络请求开源框架Volley源码解析系列]初识Volley及其基本用法.如StringRequest用来请求一 ...

  4. Spring Security源码解析一:UsernamePasswordAuthenticationFilter之登录流程

    一.前言 spring security安全框架作为spring系列组件中的一个,被广泛的运用在各项目中,那么spring security在程序中的工作流程是个什么样的呢,它是如何进行一系列的鉴权和 ...

  5. Apktool源码解析——第二篇

    上一篇讲到ApkDecoder这个类,大部分调用到还是Androlib类,而且上次发现brutall的代码竟然不是最新的,遂去找iBotP.的代码了. 今天来看Androlib的代码: private ...

  6. Android View体系(八)从源码解析View的layout和draw流程

    前言 上一篇文章我们讲了View的measure的流程,接下来我们讲下View的layout和draw流程,如果你理解了View的measure的流程,那这篇文章自然就不在话下了. 1.View的la ...

  7. mapbox.gl源码解析——基本架构与数据渲染流程

    加载地图 Mapbox GL JS是一个JavaScript库,使用WebGL渲染交互式矢量瓦片地图和栅格瓦片地图.WebGL渲染意味着高性能,MapboxGL能够渲染大量的地图要素,拥有流畅的交互以 ...

  8. Redis源码解析:23sentinel(四)故障转移流程

    十:故障转移流程中的状态转换 当哨兵针对某个主节点进行故障转移时,该主节点的故障转移状态master->failover_state,要依次经历下面六个状态: SENTINEL_FAILOVER ...

  9. Hadoop源码学习笔记之NameNode启动场景流程三:FSNamesystem初始化源码剖析

    上篇内容分析了http server的启动代码,这篇文章继续从initialize()方法中按执行顺序进行分析.内容还是分为三大块: 一.源码调用关系分析 二.伪代码执行流程 三.代码图解 一.源码调 ...

随机推荐

  1. hough变换算法

    1.算法思想 边缘检测比如canny算子可以识别出图像的边缘,但是实际中由于噪声和光照不均匀等因素,很多情况下获得的边缘点是不连续的,必须通过边缘连接将他们转换为有意义的边缘.Hough变化是一个重要 ...

  2. Windows下EDK2环境的搭建以及经典的程序设计Print Hello World !-----(Linux下的待后续熟练了再更新)

    很久没有更新博客了,之前的博客末尾有提到过要写有关EDK2环境搭建的博客,现在就是完成的时候了,后续博客更新会比较规律(大概每周一篇?) 本人博客仅仅发表于博客园,本人主页为         http ...

  3. span(行级元素)在不定高的div(块级元素)中垂直居中的方法

    设置父级元素: align-items: center; display: flex;

  4. Ubuntu 18 安装 cuda 10

    1.把预先下好的cuda放到某个目录,如Download. 2.Crtl + Alt + F3 进入tty,使用tty登录. 关闭用户图形界面,sudo systemctl set-default m ...

  5. JS中的call()方法和apply()方法用法总结(挺好 转载下)

    最近又遇到了JacvaScript中的call()方法和apply()方法,而在某些时候这两个方法还确实是十分重要的,那么就让我总结这两个方法的使用和区别吧. 1. 每个函数都包含两个非继承而来的方法 ...

  6. 二、create-react-app自定义配置

    这里主要讲解添加less  和  实现Antd按需加载 首选需要执行npm run eject 暴露所有内建的配置 ,这是后面所有配置的基础,这个必须优先执行! 一.实现Antd按需加载 按需加载插件 ...

  7. JS排序算法--冒泡排序和选择排序

    在我们JS语法当中,数据类型中的复杂数据类型,有一项我们常用的数组数据类型,其中存储的数据有时是乱序的,需要排序,我们有多种方法,最简单的肯定是 :变量.sort(fonction(a,b){a> ...

  8. 2019.3.14解题报告&补题报告

    A题 题意: 输入r, c,代表r*c的矩阵,接下来一行,是r个数,代表每一行里最大的数:接下来一行,是c个数,代表每一列中的最大数.求所给数据是否冲突. 思路:判断r个数中最大数maxr和c个数中最 ...

  9. Python中常见的报错名称

    Python中常见的报错名称 1.SyntaxError 语法错误.看看是否用Python关键字命名变量,有没有使用中文符号,运算符.逻辑运算符等符号是不是使用不规范. 2.IndentationEr ...

  10. Android 缓存的使用

    缓存基础类 import android.content.Context; import android.content.SharedPreferences; public class CachePa ...