简单的自定义php模板引擎


Posted in PHP onAugust 26, 2016

模板引擎的思想是来源于MVC(Model View Controller)模型,即模型层、视图层、控制器层。

在Web端,模型层为数据库的操作;视图层就是模板,也就是Web前端;Controller就是PHP对数据和请求的各种操作。模板引擎就是为了将视图层和其他层分离开来,使php代码和html代码不会混杂在一起。因为当php代码和html代码混杂在一起时,将使代码的可读性变差,并且代码后期的维护会变得很困难。 

大部分的模板引擎原理都差不多,核心就是利用正则表达式解析模板,将约定好的特定的标识语句编译成php语句,然后调用时只需要include编译后的文件,这样就讲php语句和html语句分离开来了。甚至可以更进一步将php的输出输出到缓冲区,然后将模板编译成静态的html文件,这样请求时,就是直接打开静态的html文件,请求速度大大加快。 

简单的自定义模板引擎就是两个类,第一个是模板类、第二个是编译类。

首先是编译类: 

class CompileClass {
 private $template;  // 待编译文件
 private $content;  // 需要替换的文本
 private $compile_file;  // 编译后的文件
 private $left = '{';  // 左定界符
 private $right = '}';  // 右定界符
 private $include_file = array();  // 引入的文件
 private $config;  // 模板的配置文件
 private $T_P = array();  // 需要替换的表达式
 private $T_R = array();  // 替换后的字符串
 
 public function __construct($template, $compile_file, $config) {}
 
 public function compile() {
  $this->c_include();
  $this->c_var();
  $this->c_staticFile();
  file_put_contents($this->compile_file, $this->content);
 }
 
 // 处理include
 public function c_include() {}
 
 // 处理各种赋值和基本语句
 public function c_var() {}
 
 // 对静态的JavaScript进行解析 
 public function c_staticFile() {}
}

编译类的大致结构就是上面那样,编译类的工作就是根据配置的文件,将写好的模板文件按照规则解析,替换然后输出到文件中。这个文件的内容是php和html混杂的,但在使用模板引擎进行开发时并不需要在意这个文件,因为我们要编写的是模板文件,也就是html和我们自己定义的标签混合的一个文件。这样View和其他两层就分离开来了。 

在这个自定义模板引擎中,我的左右定界符就是大括号,具体的解析规则就是放在__construct()中 

// 需要替换的正则表达式
$this->T_P[] = "/$this->left\s*\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\xf7-\xff]*)\s*$this->right/";
$this->T_P[] = "/$this->left\s*(loop|foreach)\s*\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\xf7-\xff]*)\s*$this->right/";
$this->T_P[] = "/$this->left\s*(loop|foreach)\s*\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\xf7-\xff]*)\s+"
  . "as\s+\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\xf7-\xff]*)$this->right/";
$this->T_P[] = "/$this->left\s*\/(loop|foreach|if)\s*$this->right/";
$this->T_P[] = "/$this->left\s*if(.*?)\s*$this->right/";
$this->T_P[] = "/$this->left\s*(else if|elseif)(.*?)\s*$this->right/";
$this->T_P[] = "/$this->left\s*else\s*$this->right/";
$this->T_P[] = "/$this->left\s*([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\xf7-\xff]*)\s*$this->right/";

// 替换后的字符串   
$this->T_R[] = "<?php echo \$\\1; ?>";
$this->T_R[] = "<?php foreach((array)\$\\2 as \$K=>\$V) { ?>";
$this->T_R[] = "<?php foreach((array)\$\\2 as &\$\\3) { ?>";
$this->T_R[] = "<?php } ?>";
$this->T_R[] = "<?php if(\\1) { ?>";
$this->T_R[] = "<?php } elseif(\\2) { ?>";
$this->T_R[] = "<?php } else { ?>";
$this->T_R[] = "<?php echo \$\\1; ?>";

上面的解析规则包含了基本的输出和一些常用的语法,if、foreach等。利用preg_replace函数就能对模板文件进行替换。具体情况如下 

<!--模板文件-->
{$data}
{foreach $vars}
 {if $V == 1 }
  <input value="{V}">
 {elseif $V == 2}
  <input value="123123">
 {else }
  <input value="sdfsas是aa">
 {/if}
{/foreach}

{ loop $vars as $var}
 <input value="{var}">
{ /loop }
 // 解析后
<?php echo $data; ?>
<?php foreach((array)$vars as $K=>$V) { ?>
 <?php if( $V == 1) { ?>
  <input value="<?php echo $V; ?>">
 <?php } elseif( $V == 2) { ?>
  <input value="123123">
 <?php } else { ?>
  <input value="sdfsas是aa">
 <?php } ?>
<?php } ?>

<?php foreach((array)$vars as &$var) { ?>
 <input value="<?php echo $var; ?>">
<?php } ?>

编译类的工作大致就是这样,剩下的include和对JavaScript的解析都和这个大同小异。

然后就是模板类 

class Template {
 // 配置数组 
 private $_arrayConfig = array(
  'root' => '',  // 文件根目录
  'suffix' => '.html',  // 模板文件后缀
  'template_dir' => 'templates',  // 模板所在文件夹
  'compile_dir' => 'templates_c',  // 编译后存放的文件夹
  'cache_dir' => 'cache',  // 静态html存放地址
  'cache_htm' => false,  // 是否编译为静态html文件
  'suffix_cache' => '.htm',  // 设置编译文件的后缀
  'cache_time' => 7200,  // 自动更新间隔
  'php_turn' => true,  // 是否支持原生php代码
  'debug' => 'false',
 );
 private $_value = array();  
 private $_compileTool;  // 编译器
 static private $_instance = null;  
 public $file;  // 模板文件名
 public $debug = array();  // 调试信息
 
 public function __construct($array_config=array()) {}
 
 // 单步设置配置文件
 public function setConfig($key, $value=null) {}
 
 // 注入单个变量
 public function assign($key, $value) {}
 
 // 注入数组变量
 public function assignArray($array) {}
 
 // 是否开启缓存
 public function needCache() {}
 
 // 如果需要重新编译文件
 public function reCache() {}
 
 // 显示模板
 public function show($file) {}
 
}

整个模板类的工作流程就是先实例化模板类对象,然后利用assign和assignArray方法给模板中的变量赋值,然后调用show方法,将模板和配置文件传入编译类的实例化对象中然后直接include编译后的php、html混编文件,显示输出。简单的流程就是这样,详细的代码如下 

public function show($file) {
 $this->file = $file;
 if(!is_file($this->path())) {
  exit("找不到对应的模板文件");
 }

 $compile_file = $this->_arrayConfig['compile_dir']. md5($file). '.php';
 $cache_file = $this->_arrayConfig['cache_dir']. md5($file). $this->_arrayConfig['suffix_cache'];

 // 如果需要重新编译文件
 if($this->reCache($file) === false) {
  $this->_compileTool = new CompileClass($this->path(), $compile_file, $this->_arrayConfig);

  if($this->needCache()) {
   // 输出到缓冲区
   ob_start();
  }
  // 将赋值的变量导入当前符号表
  extract($this->_value, EXTR_OVERWRITE);

  if(!is_file($compile_file) or filemtime($compile_file) < filemtime($this->path())) {
   $this->_compileTool->vars = $this->_value;
   $this->_compileTool->compile();
   include($compile_file);
  }
  else {
   include($compile_file);
  }

  // 如果需要编译成静态文件
  if($this->needCache() === true) {
   $message = ob_get_contents();
   file_put_contents($cache_file, $message);
  }
 }
 else {
  readfile($cache_file);
 }
}

在show方法中,我首先判断模板文件存在,然后利用MD5编码生成编译文件和缓存文件的文件名。然后就是判断是否需要进行编译,判断的依据是看编译文件是否存在和编译文件的写入时间是否小于模板文件。如果需要编译,就利用编译类进行编译,生成一个php文件。然后只需要include这个编译文件就好了。 

为了加快模板的载入,可以将编译后的文件输出到缓冲区中,也就是ob_start()这个函数,所有的输出将不会输出到浏览器,而是输出到默认的缓冲区,在利用ob_get_contents()将输出读取出来,保存成静态的html文件。 

具体的使用如下 

require('Template.php');

$config = array(
 'debug' => true,
 'cache_htm' => false,
 'debug' => true
);

$tpl = new Template($config);
$tpl->assign('data', microtime(true));
$tpl->assign('vars', array(1,2,3));
$tpl->assign('title', "hhhh");
$tpl->show('test');


 

 缓存后的文件如下 

<!DOCTYPE html>
<html>
 <head>
  <title>hhhh</title>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 </head>
 <body>
  1466525760.32         <input value="1">
            <input value="123123">
            <input value="sdfsas是aa">
       
     <input value="1">
     <input value="2">
     <input value="3">
    <script src="123?t=1465898652"></script>
 </body>
</html>

一个简单的自定义模板引擎就完成了,虽然简陋但是能用,而且重点在于造轮子的乐趣和收获。 
完整代码可见我的 github

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

PHP 相关文章推荐
浅谈PHP强制类型转换,慎用!
Jun 06 PHP
php实现的支持imagemagick及gd库两种处理的缩略图生成类
Sep 23 PHP
PHP使用glob函数遍历目录或文件夹的方法
Dec 16 PHP
php函数重载的替代方法--伪重载详解
May 08 PHP
PHP多维数组排序array详解
Nov 21 PHP
php记录搜索引擎爬行记录的实现代码
Mar 02 PHP
PHP 多任务秒级定时器的实现方法
May 13 PHP
PHP函数积累总结
Mar 19 PHP
php获取微信openid方法总结
Oct 10 PHP
PHP 枚举类型的管理与设计知识点总结
Feb 13 PHP
php模拟实现斗地主发牌
Apr 22 PHP
PHP7 字符串处理机制修改
Mar 09 PHP
PHP flush 函数使用注意事项
Aug 26 #PHP
PHP获取不了React Native Fecth参数的解决办法
Aug 26 #PHP
简单的pgsql pdo php操作类实现代码
Aug 25 #PHP
php mongodb操作类 带几个简单的例子
Aug 25 #PHP
php flush无效,IIS7下php实时输出的方法
Aug 25 #PHP
php 的反射详解及示例代码
Aug 25 #PHP
php 输入输出流详解及示例代码
Aug 25 #PHP
You might like
总集篇&特番节目先行播出!《SAO Alicization War of Underworld》第2季度TV动画4月25日放送!
2020/03/06 日漫
php简单socket服务器客户端代码实例
2015/05/18 PHP
php使用 readfile() 函数设置文件大小大小的方法
2017/08/11 PHP
PHP 进度条函数的简单实例
2017/09/19 PHP
PHP图像处理技术实例总结【绘图、水印、验证码、图像压缩】
2018/12/08 PHP
javascript 学习之旅 (1)
2009/02/05 Javascript
Js如何判断客户端是PC还是手持设备简单分析
2012/11/22 Javascript
JS中attr和prop属性的区别以及优先选择示例介绍
2014/06/30 Javascript
javascript判断数组内是否重复的方法
2015/04/21 Javascript
js检测用户输入密码强度
2015/10/22 Javascript
深入理解angular2启动项目步骤
2017/07/15 Javascript
form表单序列化详解(推荐)
2017/08/15 Javascript
vue-cli项目优化方法- 缩短首屏加载时间
2018/04/01 Javascript
IE9 elementUI文件上传的问题解决
2018/10/17 Javascript
koa2 数据api中间件设计模型的实现方法
2020/07/13 Javascript
Vue的自定义组件不能使用click方法的解决
2020/07/28 Javascript
解决vue自定义指令导致的内存泄漏问题
2020/08/04 Javascript
python re正则表达式模块(Regular Expression)
2014/07/16 Python
使用Python脚本将Bing的每日图片作为桌面的教程
2015/05/04 Python
python spyder中读取txt为图片的方法
2018/04/27 Python
Python弹出输入框并获取输入值的实例
2019/06/18 Python
python实现可变变量名方法详解
2019/07/01 Python
200行python代码实现2048游戏
2019/07/17 Python
python利用re,bs4,requests模块获取股票数据
2019/07/29 Python
python文件读写代码实例
2019/10/21 Python
使用python 的matplotlib 画轨道实例
2020/01/19 Python
Python导入数值型Excel数据并生成矩阵操作
2020/06/09 Python
Oracle里面常用的数据字典有哪些
2014/02/14 面试题
乡镇党的群众路线教育实践活动总结报告
2014/10/30 职场文书
模范教师事迹材料
2014/12/16 职场文书
拉贝日记观后感
2015/06/05 职场文书
小学信息技术教学反思
2016/02/16 职场文书
python 如何将两个实数矩阵合并为一个复数矩阵
2021/05/19 Python
MySQL不使用order by实现排名的三种思路总结
2021/06/02 MySQL
MySQL 用 limit 为什么会影响性能
2021/09/15 MySQL
详解Python+OpenCV绘制灰度直方图
2022/03/22 Python