PHP实现简单的模板引擎功能示例


Posted in PHP onSeptember 02, 2017

本文实例讲述了PHP实现简单的模板引擎功能。分享给大家供大家参考,具体如下:

php web开发中广泛采取mvc的设计模式,controller传递给view层的数据,必须通过模板引擎才能解析出来。实现一个简单的仅仅包含if,foreach标签,解析$foo变量的模板引擎。

编写template模板类和compiler编译类。代码如下:

<?php
namespace foo\base;
use foo\base\Object;
use foo\base\Compiler;
/**
* 
*/
class Template extends Object
{
 private $_config = [
  'suffix' => '.php',//文件后缀名
  'templateDir' => '../views/',//模板所在文件夹
  'compileDir' => '../runtime/cache/views/',//编译后存放的目录
  'suffixCompile' => '.php',//编译后文件后缀
  'isReCacheHtml' => false,//是否需要重新编译成静态html文件
  'isSupportPhp' => true,//是否支持php的语法
  'cacheTime' => 0,//缓存时间,单位秒
 ];
 private $_file;//带编译模板文件
 private $_valueMap = [];//键值对
 private $_compiler;//编译器
 public function __construct($compiler, $config = [])
 {
  $this->_compiler = $compiler;
  $this->_config = array_merge($this->_config, $config);
 }
 /**
  * [assign 存储控制器分配的键值]
  * @param [type] $values [键值对集合]
  * @return [type]   [description]
  */
 public function assign($values)
 {
  if (is_array($values)) {
   $this->_valueMap = $values;
  } else {
   throw new \Exception('控制器分配给视图的值必须为数组!');
  }
  return $this;
 }
 /**
  * [show 展现视图]
  * @param [type] $file [带编译缓存的文件]
  * @return [type]  [description]
  */
 public function show($file)
 {
  $this->_file = $file;
  if (!is_file($this->path())) {
   throw new \Exception('模板文件'. $file . '不存在!');
  }
  $compileFile = $this->_config['compileDir'] . md5($file) . $this->_config['suffixCompile'];
  $cacheFile = $this->_config['compileDir'] . md5($file) . '.html';
  //编译后文件不存在或者缓存时间已到期,重新编译,重新生成html静态缓存
  if (!is_file($compileFile) || $this->isRecompile($compileFile)) {
   $this->_compiler->compile($this->path(), $compileFile, $this->_valueMap);
   $this->_config['isReCacheHtml'] = true;
   if ($this->isSupportPhp()) {
    extract($this->_valueMap, EXTR_OVERWRITE);//从数组中将变量导入到当前的符号表
   }
  }
  if ($this->isReCacheHtml()) {
   ob_start();
   ob_clean();
   include($compileFile);
   file_put_contents($cacheFile, ob_get_contents());
   ob_end_flush();
  } else {
   readfile($cacheFile);
  }
 }
 /**
  * [isRecompile 根据缓存时间判断是否需要重新编译]
  * @param [type] $compileFile [编译后的文件]
  * @return boolean    [description]
  */
 private function isRecompile($compileFile)
 {
  return time() - filemtime($compileFile) > $this->_config['cacheTime'];
 }
 /**
  * [isReCacheHtml 是否需要重新缓存静态html文件]
  * @return boolean [description]
  */
 private function isReCacheHtml()
 {
  return $this->_config['isReCacheHtml'];
 }
 /**
  * [isSupportPhp 是否支持php语法]
  * @return boolean [description]
  */
 private function isSupportPhp()
 {
  return $this->_config['isSupportPhp'];
 }
 /**
  * [path 获得模板文件路径]
  * @return [type] [description]
  */
 private function path()
 {
  return $this->_config['templateDir'] . $this->_file . $this->_config['suffix'];
 }
}
<?php
namespace foo\base;
use foo\base\Object;
/**
* 
*/
class Compiler extends Object
{
 private $_content;
 private $_valueMap = [];
 private $_patten = [
  '#\{\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}#',
  '#\{if (.*?)\}#',
  '#\{(else if|elseif) (.*?)\}#',
  '#\{else\}#',
  '#\{foreach \\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)}#',
  '#\{\/(foreach|if)}#',
  '#\{\\^(k|v)\}#',
 ];
 private $_translation = [
  "<?php echo \$this->_valueMap['\\1']; ?>",
  '<?php if (\\1) {?>',
  '<?php } else if (\\2) {?>',
  '<?php }else {?>',
  "<?php foreach (\$this->_valueMap['\\1'] as \$k => \$v) {?>",
  '<?php }?>',
  '<?php echo \$\\1?>'
 ];
 /**
  * [compile 编译模板文件]
  * @param [type] $source [模板文件]
  * @param [type] $destFile [编译后文件]
  * @param [type] $values [键值对]
  * @return [type]   [description]
  */
 public function compile($source, $destFile, $values)
 {
  $this->_content = file_get_contents($source);
  $this->_valueMap = $values;
  if (strpos($this->_content, '{$') !== false) {
   $this->_content = preg_replace($this->_patten, $this->_translation, $this->_content);
  }
  file_put_contents($destFile, $this->_content);
 }
}

我们的控制器就可以调用template中的assign方法进行赋值,show方法进行模板编译了。

/**
* [render 渲染模板文件]
* @param [type] $file   [待编译的文件]
* @param [type] $values   [键值对]
* @param array $templateConfig [编译配置]
* @return [type]     [description]
*/
protected function render($file, $values, $templateConfig = [])
{
  $di = Container::getInstance();
  //依赖注入实例化对象
  $di->template = function () use ($di, $templateConfig) {
   $di->compiler = 'foo\base\Compiler';
   $compiler = $di->compiler;
   return new \foo\base\Template($compiler, $templateConfig);
  };
  $di->template->assign($values)->show($file);
}

Container类如下:

<?php
namespace foo\base;
use foo\base\Object;
class Container extends Object
{
 private static $_instance;
 private $s = [];
 public static $instances = [];
 public static function getInstance()
 {
  if (!(self::$_instance instanceof self)) {
   self::$_instance = new self();
  }
  return self::$_instance;
 }
 private function __construct(){}
 private function __clone(){}
 public function __set($k, $c)
 {
  $this->s[$k] = $c;
 }
 public function __get($k)
 {
  return $this->build($this->s[$k]);
 }
 /**
  * 自动绑定(Autowiring)自动解析(Automatic Resolution)
  *
  * @param string $className
  * @return object
  * @throws Exception
  */
 public function build($className)
 {  
  // 如果是闭包函数(closures)
  if ($className instanceof \Closure) {
   // 执行闭包函数
   return $className($this);
  }
  if (isset(self::$instances[$className])) {
   return self::$instances[$className];
  }
  /** @var ReflectionClass $reflector */
  $reflector = new \ReflectionClass($className);
  // 检查类是否可实例化, 排除抽象类abstract和对象接口interface
  if (!$reflector->isInstantiable()) {
   throw new \Exception($reflector . ': 不能实例化该类!');
  }
  /** @var ReflectionMethod $constructor 获取类的构造函数 */
  $constructor = $reflector->getConstructor();
  // 若无构造函数,直接实例化并返回
  if (is_null($constructor)) {
   return new $className;
  }
  // 取构造函数参数,通过 ReflectionParameter 数组返回参数列表
  $parameters = $constructor->getParameters();
  // 递归解析构造函数的参数
  $dependencies = $this->getDependencies($parameters);
  // 创建一个类的新实例,给出的参数将传递到类的构造函数。
  $obj = $reflector->newInstanceArgs($dependencies);
  self::$instances[$className] = $obj;
  return $obj;
 }
 /**
  * @param array $parameters
  * @return array
  * @throws Exception
  */
 public function getDependencies($parameters)
 {
  $dependencies = [];
  /** @var ReflectionParameter $parameter */
  foreach ($parameters as $parameter) {
   /** @var ReflectionClass $dependency */
   $dependency = $parameter->getClass();
   if (is_null($dependency)) {
    // 是变量,有默认值则设置默认值
    $dependencies[] = $this->resolveNonClass($parameter);
   } else {
    // 是一个类,递归解析
    $dependencies[] = $this->build($dependency->name);
   }
  }
  return $dependencies;
 }
 /**
  * @param ReflectionParameter $parameter
  * @return mixed
  * @throws Exception
  */
 public function resolveNonClass($parameter)
 {
  // 有默认值则返回默认值
  if ($parameter->isDefaultValueAvailable()) {
   return $parameter->getDefaultValue();
  }
  throw new \Exception('I have no idea what to do here.');
 }
}

要想以键值对的方式访问对象的属性必须实现ArrayAccess接口的四个方法,

Object基类代码如下:

public function offsetExists($offset) 
{
  return array_key_exists($offset, get_object_vars($this));
}
public function offsetUnset($key) 
{
  if (array_key_exists($key, get_object_vars($this)) ) {
   unset($this->{$key});
  }
}
public function offsetSet($offset, $value) 
{
  $this->{$offset} = $value;
}
public function offsetGet($var) 
{
  return $this->$var;
}

在某一控制器中就可以调用父类Controller的render方法啦

$this->render('test\index', ['name' => 'tom', 'age' => 20, 'friends' => ['jack', 'rose']], ['cacheTime' => 10]);

编写视图模板文件'test\index':

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Document</title>
</head>
<body>
 <p>展示模板文件视图</p> 
 <p>{$name}</p>
 <p>{$age}</p>
 <?php echo ++$age;?>
 {if $age > 18}
  <p>已成年</p>
 {else if $age < 10}
  <p>小毛孩</p>
 {/if}
 {foreach $friends} 
  <p>{^v} </p>
 {/foreach}
</body>
</html>

至此,一个简单的模板编译引擎就写好了。

希望本文所述对大家PHP程序设计有所帮助。

PHP 相关文章推荐
PHP_MySQL教程-第一天
Mar 18 PHP
php中使用__autoload()自动加载未定义类的实现代码
Feb 06 PHP
基于PHP+Ajax实现表单验证的详解
Jun 25 PHP
php while循环得到循环次数
Oct 26 PHP
php之Smarty模板使用方法示例详解
Jul 08 PHP
推荐25款php中非常有用的类库
Sep 29 PHP
PHP模板引擎Smarty内建函数section,sectionelse用法详解
Apr 11 PHP
100行PHP代码实现socks5代理服务器
Apr 28 PHP
PHP防止图片盗用(盗链)的方法小结
Nov 11 PHP
PHP对象相关知识总结
Apr 09 PHP
PHP排序算法之冒泡排序(Bubble Sort)实现方法详解
Apr 20 PHP
php 可变函数使用小结
Jun 12 PHP
PHP实现类似于C语言的文件读取及解析功能
Sep 01 #PHP
PHP设计模式之工厂模式实例总结
Sep 01 #PHP
浅谈关于PHP解决图片无损压缩的问题
Sep 01 #PHP
phpStudy配置多站点多域名和多端口的方法
Sep 01 #PHP
[原创]PHP获取数组表示的路径方法分析【数组转字符串】
Sep 01 #PHP
使用PHP连接数据库_实现用户数据的增删改查的整体操作示例
Sep 01 #PHP
php插件Xajax使用方法详解
Aug 31 #PHP
You might like
php面向对象全攻略 (十一)__toString()用法 克隆对象 __call处理调用错误
2009/09/30 PHP
php获取URL中带#号等特殊符号参数的解决方法
2014/09/02 PHP
wamp服务器访问php非常缓慢的解决过程
2015/07/01 PHP
thinkPHP5.0框架环境变量配置方法
2017/03/17 PHP
PHP 二维array转换json的实例讲解
2018/08/21 PHP
vmware linux系统安装最新的php7图解
2019/04/14 PHP
用JS将搜索的关键字高亮显示实现代码
2013/11/08 Javascript
基于mouseout和mouseover等类似事件的冒泡问题解决方法
2013/11/18 Javascript
JavaScript eval() 函数介绍及应用示例
2014/07/29 Javascript
深入学习AngularJS中数据的双向绑定机制
2016/03/04 Javascript
JS与Ajax Get和Post在使用上的区别实例详解
2016/06/08 Javascript
jQuery中fadein与fadeout方法用法示例
2016/09/16 Javascript
JS添加或修改控件的样式(Class)实现方法
2016/10/15 Javascript
vue-router 中router-view不能渲染的解决方法
2017/05/23 Javascript
vue2.0中click点击当前li实现动态切换class
2017/06/21 Javascript
使用ionic(选项卡栏tab) icon(图标) ionic上拉菜单(ActionSheet) 实现通讯录界面切换实例代码
2017/10/20 Javascript
微信小程序基于movable-view实现滑动删除效果
2020/01/08 Javascript
Numpy 将二维图像矩阵转换为一维向量的方法
2018/06/05 Python
用Python编写一个简单的CS架构后门的方法
2018/11/20 Python
python爬取微信公众号文章的方法
2019/02/26 Python
Python3.0中普通方法、类方法和静态方法的比较
2019/05/03 Python
python检查目录文件权限并修改目录文件权限的操作
2020/03/11 Python
Python configparser模块应用过程解析
2020/08/14 Python
Zadig&Voltaire官网:法国时装品牌
2018/01/05 全球购物
如果一个类实现了多个接口但是这些接口有相同的方法名将会怎样
2013/06/16 面试题
公司培训心得体会
2014/01/03 职场文书
教师评优事迹材料
2014/01/10 职场文书
两年的个人工作自我评价
2014/01/10 职场文书
环境工程专业自荐信
2014/03/03 职场文书
《大禹治水》教学反思
2014/04/27 职场文书
副校长竞聘演讲稿
2014/09/01 职场文书
房产协议书范本2014
2014/09/30 职场文书
2015年语言文字工作总结
2015/07/23 职场文书
军训决心书范文
2015/09/22 职场文书
2015新员工工作总结范文
2015/10/15 职场文书
六年级数学教学反思
2016/02/16 职场文书