>백엔드 개발 >PHP 튜토리얼 >晒一下我的模板引擎,欢迎拍砖

晒一下我的模板引擎,欢迎拍砖

WBOY
WBOY원래의
2016-06-23 14:24:42837검색

本帖最后由 xuzuning 于 2012-05-23 15:02:49 编辑

别不多说,直接上代码
template.php(3.55K)
<?phpclass template {  protected $data = array();  protected $drillmode = 0;  function __construct($s) {	if(file_exists($s)) $s = file_get_contents($s);	$this->find_var($s);	$this->data = explode('<', $s);	$this->data[0] = '<?php $_st=$_var=array();?>';	$this->find_dsn();  }  //新增 run 方法,  function run() {	//include "data://," . join('<', $this->data);	eval('?>' . join('<', $this->data));  }  function find($pattern) {	$this->pattern = $pattern;	return array_filter($this->data, array($this, 'find_callback'));  }  private function find_dsn() {	foreach($this->find("#\bdsn\b#i") as $k=>$v) {		$t = $this->find_tag($tag = strtok($v, ' '), $k);		end($t);		$dsn[] = array( $k, key($t) );	}	if($this->drillmode) {		foreach($this->find("#\bdrill\b#i") as $k=>$t) {			foreach($dsn as $i=>$v) if($k < $v[1] && $k > $v[0]) $t = $i;			$drill[] = $dsn[$t];			unset($dsn[$t]);		}	}	foreach($dsn as $v) {		list($start, $end) = $v;		preg_match('/\bdsn\s*=\s*([^\s>]+)/i', $this->data[$start], $reg);		$this->data[$start] = str_replace(' '.$reg[0], '', $this->data[$start]);		$m = explode(',', trim($reg[1], '\'"')) + array(0, 0, '');		$code_start = "?php if(isset(\$_var))\$_st[]=\$_var;foreach((isset(\$_var['$m[0]'])?\$_var['$m[0]']:\$this->$m[0]('$m[1]','$m[2]')) as \$_key=>\$_var){?>";		$code_end = "?php }\$_var=array_pop(\$_st);?>";		switch($m[1]) {			case 0:				$t = explode('>', $this->data[$start]);				$t[1] = "<$code_start" . $t[1];				$this->data[$start] = join('>', $t);				$this->data[$end] = "$code_end<" . $this->data[$end];				break;			case 1:				$this->data[$end] .= "<$code_end";				$this->data[$start] = "$code_start<" . $this->data[$start];				break;			default:				$n = round(100/$m[1]);				$this->data[$end] .= "</dt><$code_end";				$this->data[$start] = "$code_start<dt style='float:left;width:$n%;margin:0px;padding:0px'><" . $this->data[$start];				break;		}	}	if($this->drillmode) foreach($drill as $v) {		list($start, $end) = $v;		preg_match('/\bdsn\s*=\s*([^\s>]+)/i', $this->data[$start], $reg);		$this->data[$start] = str_replace(' '.$reg[0], '', $this->data[$start]);		$m = explode(',', trim($reg[1], '\'"')) + array(0, 0, '');		$code = '';		for($i=$start; $i<=$end; $i++) {			$code .= '<' . $this->data[$i];			if($i > $start) unset($this->data[$i]);		}		$code = addslashes($code);		$this->data[$start] = "?php \$_code='$code';\$this->drill(\$_code, isset(\$_var['$m[0]'])?\$_var['$m[0]']:\$this->$m[0]('$m[1]','$m[2]'));?>";	}  }  protected function find_tag($tag, $offs=0) {	$r = array();	$counter = 0;	foreach($this->find("#^/?$tag#i") as $k=>$v) {		if($k >= $offs) {			$counter += $v{0} == '/' ? 1 : -1;			$r[$k] = $v;			if($counter == 0) break;		}	}	return $r;  }  protected function find_callback($v) {	return preg_match($this->pattern, $v);  }  private function find_var(&$s) {	$s = preg_replace_callback('/\{(\w+)\}/', array($this, 'var_callback'), $s);  }  protected function var_callback($r) {	if($r[1] == 'drill') {		$this->drillmode++;		return '<?php if(isset($_var[\'child\'])) $this->drill($_code, $_var[\'child\']);?>';	}	return "<?php echo isset(\$_var['$r[1]'])?\$_var['$r[1]']:'';?>";  }  protected function drill($_code, $_source) {  	if(empty($_source) || ! is_array($_source)) return array();  	foreach($_source as $_key=>$_var) {		//include 'data://,' . $_code;		eval('?>' . $_code);  	}  }  function __call($func, $param) {	if(function_exists($func)) return call_user_func_array($func, $param);	return array();  }  function __toString() {	return join('<', $this->data);  }}


补充一下
模板中只有两种控制元素:
1、模板变量 用花括号括起的变量名,如 {var}。提供数据的函数应提供对应的数据,如没有提供则自动为空
2、数据源声明 位于 HTML 标记中的 dsn 属性,比如 

{t}


其中 foo 表示提供数据的函数,1 表示充填数据时,循环是包含自身的 比如返回的数据是 array(array('t'=>1,array('t'=>2))
则实际生成的 html 是 

1

2


当第二个参数缺省时,同样的数据只产生 

12


3、数据源函数约定返回的是二维数组:第一维是下标数组,第二维是关联数组。请参见测试例

写程序很简单,写文档就要了我的命了

相关问题解答见 #39

一个扩展的用法见 #61

回复讨论(解决方案)

本帖最后由 xuzuning 于 2012-05-23 08:40:52 编辑

测试例
include 'template.php';function body() {  return array(    array(      'title' => '布局',      'advertising' => '一贯喜欢用表格布局,这种 DIV+CSS 方式很让人头疼的',      'menu' => array(         array('text' => '首页' ),         array('text' => '博客' ),         array('text' => '设计' ),         array('text' => '相册' ),         array('text' => '论坛' ),         array('text' => '关于' ),         ),	'footer' => '为什么表现不一样呢,IE就是对齐的',    ),  );}function img() {	$ar['url'] = 'http://justinyoung.cnblogs.com/images/cnblogs_com/justinyoung/2007/tb_m.PNG';	return array($ar);}function tree() {$data = array(  array('ID'=>1, 'PARENT'=>0, 'NAME'=>'祖父'),  array('ID'=>2, 'PARENT'=>1, 'NAME'=>'父亲'),  array('ID'=>3, 'PARENT'=>1, 'NAME'=>'叔伯'),  array('ID'=>4, 'PARENT'=>2, 'NAME'=>'自己'),  array('ID'=>5, 'PARENT'=>4, 'NAME'=>'儿子'),);$t = find_child($data, 'ID', 'PARENT');//print_r($t);return array($t[1]);}function box() {  return array(    array('t'=>'--1--', 'child' => array(array('t'=>2),array('t'=>2),array('t'=>2),)),    array('t'=>'--2--', 'child' => array(array('t'=>2),array('t'=>2),array('t'=>2),)),    array('t'=>'--3--', 'child' => array(array('t'=>2),array('t'=>2),array('t'=>2),)),    array('t'=>'--4--', 'child' => array(array('t'=>2),array('t'=>2),array('t'=>2),)),    array('t'=>'--5--', 'child' => array(array('t'=>2),array('t'=>2),array('t'=>2),)),    array('t'=>'--6--', 'child' => array(array('t'=>2),array('t'=>2),array('t'=>2),)),  );}$p = new template('布局.txt');$p->run(); //使用也做相应调整


模板文件(布局.txt)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" dsn=body><head><meta http-equiv="Content-Type" content="text/html; charset=gb2312" /><title>{title}</title><style>/*基本信息*/body {font:12px Tahoma;margin:0px;text-align:center;background:#FFF;}a:link,a:visited {font-size:12px;text-decoration:none;}a:hover{}#side { background: #99FF99; height: 100%; width: 200px; float: left; }#side1 { background: #99FF99; height: 100%; width: 200px; float: right; }#main { background: #99FFFF; height: 100%; margin:0 200px; }/*页面层容器*/#container {width:800px;margin:10px auto}/*页面头部*/#banner {height:88px;background:url(http://avatar.csdn.net/8/7/6/2_xuzuning.jpg) no-repeat}#topmenu {    width:800px; float:left;background:#fff; }#topmenu ul{    list-style-type: none;    margin:10px 0 10px 40px;    padding: 0px;}#topmenu li{    display:inline;padding:0 5px;}#topmenu li a{    color:#000;    text-decoration:none;}#topmenu li a:hover{    color:#f00;}/*添加了float:right使得菜单位于页面右侧*/#topmenu ul {float:right;list-style:none;margin:0px;}/*页面主体*/#PageBody {width:800px;margin:0px auto;height:400px;background:#CCFF00}#Sidebar { width:200px; height:400px; float: left; background:#CCc }# MainBody {float: left; height:400px; margin-left:200px; background:#CCFF00 }/*页面底部*/#Footer {width:800px;margin:0px auto;height:50px;background:#00FFFF}.TreeView,.TreeView ul{  margin:0px;  padding:0px; list-style:none;  font-size:12px; }.TreeView li{  padding-left:16px;  text-indent:16px;  line-height:14px;  background:transparent url(folder.gif) 12px 2px no-repeat;}.box {	background: #FFFCC4;border: 1px dotted #BAB9B8; margin: 8px;}.imgbox {display: table-cell;vertical-align:middle;width:120px;height:120px;text-align:center;/* hack for ie */*display: block;*font-size: 105px;/* end */border: 5px solid red;}.imgbox img {vertical-align:middle;}</style></head><body><div id="container"><!--页面层容器-->  <div id="Header"><!--页面头部-->    <div id="banner"></div>    <div style='background:#CCFF00;width:100%'>{advertising}</div>    <!-- 导航菜单开始 -->    <div id="topmenu" style='height:24px; font-size:14pt'><ul dsn=menu><li><a href="#">{text}</a></li></ul></div>    <!-- 导航菜单结束 -->  </div>  <div id="PageBody"><!--页面主体--><div id="side"><!--此处显示 id "side" 的内容--><div style='text-align:left'><ul class=TreeView dsn='tree'> <li >{NAME}{drill}</li></ul></div></div><div id="side1"><!--此处显示 id "side1" 的内容--></div><div id="main"> <!--此处显示 id "main" 的内容--> <div dsn=box,3 class=box>  <div>{t}</div>  <div dsn=child,1  style="text-align:left">{t}</div> </div> <div class="imgbox" dsn=img> <img src="{url}"  alt="" /> </div></div> </div>  <div id="Footer">{footer}</div></div></body></html>

ci1699的AMySQL赶超phpmyadmin;
版主这是要赶超smarty啊。
啥也不说了,弄下来试试先!

能不能加点注释啊,猛一看不太明白,仔细一看还不如猛一看
正需要模板,哈哈,太感谢了

先做个记号,等有空慢慢研究。

能不能加点注释啊,猛一看不太明白,仔细一看还不如猛一看
正需要模板,哈哈,太感谢了 你都弄下来,尝试运行一下。就应该明白了

xuzuning ?? 意外 盗号了? 很少有啊  标记  晚上看看

xuzuning ?? 意外 盗号了? 很少有啊  标记  晚上看看 你上次不是要我晾我的模板引擎吗?当时包在工具箱中,这次剥离出来了

确实牛B了。强人那。

围观教科书...过两天仔细品读.

Fatal error: Call to undefined function find_child() in E:\test.local\test.php on line 51
出现了这个错误。是怎么回事啊?

find_child() 函数是我上次发的  两个不用递归的树形数组构造函数 中的一个

不错啊支持了呵呵

不理解了:

Warning: include() [function.include]: data:// wrapper is disabled in the server configuration by allow_url_include=0 in \path\template_csdn\template_class.php on line 13Warning: include(data://,<?php $_st=$_var=array();?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"><?php if(isset($_var))$_st[]=$_var;foreach((isset($_var['body'])?$_var['body']:$this->body('0','')) as $_key=>$_var){?> <head> <meta name="generator" content="HTML Tidy, see www.w3.org" /> <meta http-equiv="Content-Type" content= "text/html; charset=gb2312" /> <title><?php echo isset($_var['title'])?$_var['title']:'';?></title> <style type="text/css"> /*&raquo;&ugrave;&plusmn;&frac34;&ETH;&Aring;&Iuml;&cent;*/ body {font:12px Tahoma;margin:0px;text-align:center;background:#FFF;} a:link,a:visited {font-size:12px;text-decoration:none;} a:hover{} #side { background: #99FF99; height: 100%; width: 200px; float: left; } in \path\template_csdn\template_class.php on line 13Warning: include() [function.include]: Failed opening 'data://,<?php $_st=$_var=array();?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"><?php if(isset($_var))$_st[]=$_var;foreach((isset($_var['body'])?$_var['body']:$this->body('0','')) as $_key=>$_var){?> <head> <meta name="generator" content="HTML Tidy, see www.w3.org" /> <meta http-equiv="Content-Type" content= "text/html; charset=gb2312" /> <title><?php echo isset($_var['title'])?$_var['title']:'';?></title> <style type="text/css"> /*&raquo;&ugrave;&plusmn;&frac34;&ETH;&Aring;&Iuml;&cent;*/ body {font:12px Tahoma;margin:0px;text-align:center;background:#FFF;} a:link,a:visited {font-size:12px;text-decoration:none;} a:hover{} #si in \path\template_csdn\template_class.php on line 13

不理解了:
HTML code


Warning: include() [function.include]: data:// wrapper is disabled in the server configuration by allow_url_include=0 in \path\template_csdn\template_class.php on line 13

Warning……
忽略了一点:要求 php.ini 中 allow_url_include = on
前一段调试 SAE 打开的。他可以用 saemc:// 访问缓存,我这样写只需换个资源头就可以在他那里用了
把形如
include 'data://,' . $_code;
的改成形如
eval('?>' . $_code);
的即可

强烈支持~~
先占个好位置。。回头研究哈。

枪神你太有爱了。

有大家的支持,不得不用心去做了。

ci1699的AMySQL赶超phpmyadmin;
版主这是要赶超smarty啊。
啥也不说了,弄下来试试先!

#include "data://," . join('<', $this->data);#改成:include eval(join('<', $this->data));#AND#include 'data://,' . $_code;#改成include eval('?>' . $_code);#Error:#Parse error: syntax error, unexpected '<' in \path\template_csdn\template_class.php(12) : eval()'d code on line 1Warning: include() [function.include]: Filename cannot be empty in \path\template_csdn\template_class.php on line 12Warning: include() [function.include]: Failed opening '' for inclusion (include_path='.;C:\php\pear') in \path\template_csdn\template_class.php on line 12

PHP code

#include "data://," . join('data);
#改成:
include eval(join('data));

#AND
#include 'data://,' . $_code;
#改成
include eval('?>' . $_code);

#Error:
#Parse error: syntax ……
哈哈,问题相同啊,刚要问呢,这都出来了,等LZ解答,最好打个包最好哈

测试成功啦,只要把find_child()函数引进,把allow_url_include = on
就可正常运行,FF下图片确实显示位置不一至,慢慢分析学习,再次感谢分享

好啊挖坟

先占个位置。静等终极版本

学习PHP中……


不错 支持一个

可不可以缓存到一个大的cache文件里。不太理解模版的用意是什么

function body() {  return array(    array(      'title' => '布局',      'advertising' => '一贯喜欢用表格布局,这种 DIV+CSS 方式很让人头疼的',      'menu' => array(         array('text' => '首页' ),         array('text' => '博客' ),         array('text' => '设计' ),         array('text' => '相册' ),         array('text' => '论坛' ),         array('text' => '关于' ),         ),    'footer' => '为什么表现不一样呢,IE就是对齐的',    ),  );}


从代码上分析,这种模板引擎不行的,你看吧,已知参数还好点,但是如果这些数据来自数据库,那么查询的结果集,还要一起推入一个大的数组

而且外围还有 return array(
    array(

多么的复杂


而且文件代码最后一句$p = new template('布局.txt');
实例化,怎么感觉这个$p 没有什么突出的表现或意义

好的模板引擎应该是这样的

$p=new XXX();
不管是字符串,一维数组,二维数组,都应该是这种简单明朗的模式
$p->assign('xxx',$xxx);


其实这就是smarty的形式,(流行就有道理啊)

内部代码怎么实现各自发挥,但是外表还是这样比较好

楼主的模板引擎显然只注重了代码的花哨,忽略了如何适合初级程序员使用,而且过多使用array(array(array()())

使其呈现在代码表面而不是封装运行在楼主的模版类里面,对于初级程序员而言,怎么熟练使用array也还不一定。

另外说的是html模板,虽然是模板文件,但是还是要遵守html规范,原有的html标记不应该过多改动(虽然也有自定义属性),但一般用于javascript特效上的处理,而不应该像楼主这样随意添加,

比如楼主的
一看就是不专业,用自定义属性最好搭配自己的自定义标签,这样能够让初学者更好地掌握,而不应该不友好的破坏html结构

cope下来试一试,学习下。

大晚上的还有人喷口水。
单纯的过来看看..
哦,还要小小研究下。

性能呢?使用了eval函数,这个尽量别用,很损耗性能的。

果断收藏!

性能呢?使用了eval函数,这个尽量别用,很损耗性能的。

首先eval不是函数,原版的执行方法在注释的地方。至于性能,你测试过吗?
=======================

我觉得亮点在数据从模板中决定需要调用的函数

模板信息传递很巧妙。。。另外由此会使得,模板中load其他模板,会变得稍微复杂一些吧

仅作为核心功能演示吗?好像仅支持dsn=函数名,而没考虑类方法?

收藏~~~

引用 32 楼  的回复:

性能呢?使用了eval函数,这个尽量别用,很损耗性能的。


首先eval不是函数,原版的执行方法在注释的地方。至于性能,你测试过吗?
=======================

我觉得亮点在数据从模板中决定需要调用的函数

模板信息传递很巧妙。。。另外由此会使得,模板中load其他模板,会变得稍微复杂一些吧

仅作为核心功能演示吗?好……

没有细看版主的引擎具体实现.不过有几个看法:
1. 如果允许模板文件中调用任意函数(或加入PHP代码), 就很难避免开发人员在模板中嵌入业务逻辑, 也许是人员素质的问题, 但是, 从软件工程的角度来看, 我们应该尽量避免人员素质带来的问题
2. 使用正则表达式匹配处理的模板引擎都有硬伤(ThinkPHP自带的模板引擎存在下面两个问题):
  a) 要支持多种复杂的语法, 通常都会进行多次匹配, 带来对字符串的多次扫描, 产生不必要浪费
  b) 通常都不对字面量处理(涉及引号配对, 转义检查, 非标准HTML兼容等处理, 用正则难度太大), 使得引号内的特殊字符无法使用

关于一些问题的解答


#28
【问题】已知参数还好点,但是如果这些数据来自数据库,那么查询的结果集,还要一起推入一个大的数组
【解答】恰恰是出于数据大多来自数据库的考虑,所以才选择了这样的结构。大多数据库类都提供有 fetchall方法,用来以数组方式返回结果集
所以数据源函数只需简单的 return DB::fetchall($sql); 就可以了

【问题】文件代码最后一句$p = new template('布局.txt');
实例化,怎么感觉这个$p 没有什么突出的表现或意义
【解答】因为可以通过 echo $p; 来查看模板翻译后的代码

【问题】好的模板引擎应该是这样的
$p=new XXX();
不管是字符串,一维数组,二维数组,都应该是这种简单明朗的模式
$p->assign('xxx',$xxx);
其实这就是smarty的形式,(流行就有道理啊)
【解答】你描述的是传统的“推”方式模板引擎。无论模板是否需要,都要讲数据 assign 到模板引擎的缓冲区去
而我的这个是“拉”方式模板引擎。只在模板需要时才向模板引擎请求数据

【问题】忽略了如何适合初级程序员使用,而且过多使用array(array(array()()),对于初级程序员而言,怎么熟练使用array也还不一定
【解答】前面已经说了,来自数据库的数据可通过数据库类的 fetchall 方法取得。对数据的进一步加工,我也曾经提供了一些函数
比如:产生树结构可用 find_child 函数;产生“面包屑”所需的数据可用 find_parent 函数
使用模板引擎的目的就是让开发者的精力放在算法的实现上,所以熟练掌握数组函数的使用还是很有必要的

【问题】虽然是模板文件,但是还是要遵守html规范,原有的html标记不应该过多改动(虽然也有自定义属性),但一般用于javascript特效上的处理

一看就是不专业,用自定义属性最好搭配自己的自定义标签,这样能够让初学者更好地掌握,而不应该不友好的破坏html结构
【解答】这个就要仁者见仁,智者见智了。所有的模板引擎都会破坏 HTML 原有的完美性。我倒是觉得这个引擎是破坏最小的了
只需附加一个属性就可完成三种控制流结构(顺序、循环、分支)中的二个半,很值得了

#32
【问题】性能呢?使用了eval函数,这个尽量别用,很损耗性能的。
【解答】使用 eval 和使用 include 是一样的。

#33
【问题】模板中load其他模板,会变得稍微复杂一些吧
【解答】是的,不考虑在模板中嵌入模板。因为这样做会使用工具编辑 HTML 文档
你只需 扩充 var_callback 方法就可以达到实现命令、函数、条件语句的功能

【问题】好像仅支持dsn=函数名,而没考虑类方法?
【解答】我的宗旨的尽可能的减少对作为模板的 HTML 文档做改动,把事情交给 php 去完成

#36
【问题】使用正则表达式匹配处理的模板引擎都有硬伤
【解答】是的,但不用正则又很难去识别所需的成分。
不过现在的 php 的正则表达式处理的性能有了极大的提高
实测表明:当字符串长度大于50字节后 preg_split 速度就要高于 explode

 

各种看不懂,干什么用的呀

测试成功啦,只要把find_child()函数引进,把allow_url_include = on
就可正常运行,FF下图片确实显示位置不一至,慢慢分析学习,再次感谢分享
的确还要引入find_child()函数,忽略了。总算出结果了,两种效果:
IE8:

FF:

感觉了一下,确实如#28所说,数组层级多的话,看上去就有些复杂,个人还是比较习惯数据库。
但从中看到好多知识结构以前没有涉及,真正学习了。

好东西  标记 一下!

问个问题

如何解决

  • ……    比如是个排名列表,前面3个li class名都不一样,再后面都和第四一致

    你描述的是传统的“推”方式模板引擎。无论模板是否需要,都要讲数据 assign 到模板引擎的缓冲区去
    而我的这个是“拉”方式模板引擎。只在模板需要时才向模板引擎请求数据

    看你的文件运行顺序可知,这种说法不属实,首先运行的是 php文件,然后读取html,模板中用的是

    if(file_exists($s)) $s = file_get_contents($s); 也就不存在【只在模板需要时才向模板引擎请求数据】这种说法了,而是先读取整个模板,然后根据里面的自定义属性,进行替换操作。

    如果反过来,运行html文件,然后碰到自定义属性的时候,调取所需数据,这才符合
    【只在模板需要时才向模板引擎请求数据】这种说法

    不会php的飘过,支持楼主分享精神

    由于控制权在 php ,不在模板里

    <ul dsn='ranking'>  <li class='class{n}'>{txt}</li></ul>function ranking() {  return array(    array('n' => 1, 'txt' => '一'),    array('n' => 2, 'txt' => '二'),    array('n' => 3, 'txt' => '三'),    array('n' => 0, 'txt' => '四'),    array('n' => 0, 'txt' => '五'),    array('n' => 0, 'txt' => '六'),    array('n' => 0, 'txt' => '七'),  );}

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.