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. aireplay包注入攻击

    reaver爆破WPS PIN码: airodump-ng wlan0 reaver -i wlan0 -b D0:76:E7:51:2A:78 -vv   macchanger更改MAC地址: ma ...

  2. python3.4.3 连接Oracle生成报表并发送邮件

    python很简单,又很实用.当有需求时用起来会更有方向,大可不必从语法.循环等基础看起. 由于工作需要,每天要拉一份报表发给业务的同事,先是用SSIS做了个包部署到服务器上,每天定时拉报表发邮件给同 ...

  3. 什么是yarn,如何使用yarn安装项目依赖

    一.yarn的简介: Yarn是facebook发布的一款取代npm的包管理工具. 二.yarn的特点: 1.速度超快. Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载. 同时利用并行下载 ...

  4. VS配置C++依赖包

    处理好三个东西 1.头文件,Configuration Properties → VC++ Directories → Include Directories 2.静态库,Configuration ...

  5. CSS3-3D技术

    CSS3-3D技术 transform翻译成汉语具有"变换"或者"改变"的意思. 此属性具有非常强大的功能,比如可以实现元素的位移.拉伸或者旋转等效果, 最能体 ...

  6. 关于Quartz .NET(V3.0.7)的简要说明

    目录 0. 任务调度 1. Quartz .NET 1.1 基本概念 1.2 主要接口和对象 2. 使用示例 2.0 准备工作 2.1 每间隔一定时间间隔执行一次任务 2.3 某天的固定时间点执行任务 ...

  7. CMDB_Agent版本

    目录 CMDB_Agent版本 CMDB概念 CMDB_Agent介绍 agent方案 ssh类方案 相比较 架构目录 bin-start.py 启动文件 conf-config.py 自定义配置文件 ...

  8. Spring Cloud 系列之 Alibaba Sentinel 服务哨兵

    前文中我们提到 Netflix 中多项开源产品已进入维护阶段,不再开发新的版本,就目前来看是没有什么问题的.但是从长远角度出发,我们还是需要考虑是否有可替代产品使用.比如本文中要介绍的 Alibaba ...

  9. IDEA编写 Scala时没有语法检查和错误提示

    #问题描述 在IDEA中写scala程序时,一些明显的语法错误不进行检查,没有像写Java时的红线什么的,当编译运行时,编译失败. https://blog.csdn.net/weizheng1000 ...

  10. 北邮oj 97. 二叉排序树

    97. 二叉排序树 时间限制 1000 ms 内存限制 65536 KB 题目描述 二叉排序树,也称为二叉查找树.可以是一颗空树,也可以是一颗具有如下特性的非空二叉树: 若左子树非空,则左子树上所有节 ...