简单的自定义php模板引擎

 更新时间:2016年08月26日 14:33:32   作者:czrzchao  
这篇文章主要为大家详细介绍了简单的自定义php模板引擎,模板引擎的原理是什么,感兴趣的小伙伴们可以参考一下

模板引擎的思想是来源于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查询数据库中满足条件的记录条数(两种实现方法)

    在需要输出网站用户注册数或者插入数据之前判断是否有重复记录时,就需要获取满足条件的MySQL查询的记录数目,接下来介绍两种查询统计方法,感兴趣的朋友可以了解下啊,或许对你有所帮助
    2013-01-01
  • 浅谈discuz密码加密的方式

    浅谈discuz密码加密的方式

    这篇文章主要介绍了discuz密码加密的方式,需要的朋友可以参考下
    2014-05-05
  • php实现cc攻击防御和防止快速刷新页面示例

    php实现cc攻击防御和防止快速刷新页面示例

    这篇文章主要介绍了php实现cc攻击防御的方法和防止快速刷新页面示例,需要的朋友可以参考下
    2014-02-02
  • php自定义的格式化时间示例代码

    php自定义的格式化时间示例代码

    时间刚好是5分钟前,则对应的时间戳就会被格式化为5分钟前,自定义的格式化时间方法如下,感兴趣的朋友可以参考下
    2013-12-12
  • PHP实现打包zip并下载功能

    PHP实现打包zip并下载功能

    这篇文章主要介绍了PHP实现打包zip并下载功能,代码简单易懂,非常不错,具有一定的参考借鉴价值,需要的朋友参考下吧
    2018-06-06
  • php格式化时间戳

    php格式化时间戳

    一般mysql数据库中存储时间都是使用的Unix时间戳,那么我们显示时间的时候如果更加的友好呢,今天就给大家分享3个封装好的格式化函数,有需要的小伙伴可以参考下
    2016-12-12
  • php的lavarel框架中join和orWhere的用法

    php的lavarel框架中join和orWhere的用法

    这篇文章主要介绍了php的lavarel框架中join和orWhere的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • Laravel路由中不固定数量的参数如何实现?

    Laravel路由中不固定数量的参数如何实现?

    最近在学习laravel的时候发现了一个有趣的地方,下面和大家分享下,这篇文章主要给大家介绍了关于Laravel路由中不固定数量的参数是如何实现的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-12-12
  • Yii获取当前url和域名的方法

    Yii获取当前url和域名的方法

    这篇文章主要介绍了Yii获取当前url和域名的方法,涉及Yii针对URL、域名、目录等操作的相关技巧,需要的朋友可以参考下
    2015-06-06
  • 关于ThinkPhp 框架表单验证及ajax验证问题

    关于ThinkPhp 框架表单验证及ajax验证问题

    tp数据验证有两种方式,一种是静态方式,一种是动态方式,下面小编给大家带来了ThinkPhp 框架表单验证及ajax验证问题,感兴趣的朋友一起看看吧
    2017-07-07

最新评论