好久不来CSDN,感觉有点跟不上时代的潮流了,所以飞快的看了下推荐帖子,看见了许大大的模板引擎,所以一时心痒痒也练了下手(本人还是头一次弄这东西),只实现了if、foreach的解析,当然了,其中有很多死角,而且我感觉我写的这个很怪异,希望大家帮忙梳理下,本人喜欢被拍class Template
{
public $template_dir = null; // 模板目录
public $compile_dir = null; // 编译目录
public $cache_dir = null; // 缓存目录
public $cache_life = 0; // 缓存时间
public $force_compile = false; // 是否强制编译
public $left_delimiter = '{'; // 左边界符
public $right_delimiter = '}'; // 右边界符 public $var_list = array(); // 变量集合
public $tpl_name = null; /**
* 编译并显示模板
*
* @param string|array $var 变量名|变量数组
* @param all 变量值
*/
public function assign($var, $value = null)
{
if (is_array($var)) {
$this->_assign_arr($var);
} else {
$this->_assign_var($var, $value);
}
} /**
* 编译并显示模板
*
* @param string $string
*/
public function display($tpl_name)
{
$this->template_dir = rtrim($this->template_dir, '/');
$tpl = $this->template_dir . '/' . $tpl_name; if (is_file($tpl)) {
$this->tpl_name = $tpl_name;
$this->fetch(file_get_contents($tpl), true);
} else {
throw new Exception("Template \"{$tpl_name}\" does not exist");
}
} /**
* 编译字符串
*
* @param string $content
* @param bool $print 是否输出(true-输出)
* @param bool $print_exit 输出终止
* @return echo|string
*/
public function fetch($content, $print = false, $print_exit = true)
{
$contents = false;
if ($this->cacheing) {
$contents = $this->_get_cache();
}
if ($contents === false) {
$compile_file_path = $this->_compile($content);
extract($this->var_list); ob_start();
require_once($compile_file_path);
$content = ob_get_contents();
ob_end_clean(); if ($this->cacheing) {
$this->_set_cache($content);
}
} else {
$content = $contents;
} if ($print) {
echo $content;
if ($print_exit) exit;
} else {
return $content;
}
} /**
* 设置缓存
*
* @param string $content
* @return string
*/
private function _set_cache($content)
{
$cache_file = $this->tpl_name;
$cache_file_path = $this->cache_dir . '/' . $cache_file;
$this->_mkdir('cache_dir', '0777', true);
@file_put_contents($cache_file_path, $content . "<!--{/**{$this->cache_life}**/}-->");
} /**
* 获取缓存
*
* @return bool/string
*/
private function _get_cache()
{
$cache_file = $this->tpl_name;
$cache_file_path = $this->cache_dir . '/' . $cache_file;
if (is_file($cache_file_path)) {
$contents = file_get_contents($cache_file_path);
$cache_life_match = preg_match('#<\!--{\/\*\*(\d+)\*\*\/}-->#', $contents, $arr);
if (!$cache_life_match) {
return false;
}
$cache_life = $arr[1];
$filemtime = filemtime($cache_file_path);
if ($filemtime + $cache_life > time()) {
return file_get_contents($cache_file_path);
} else {
return false;
}
}
return false;
} /**
* 编译
*
* @param string $content
* @return string
*/
private function _compile($content)
{
$content = $this->_match($content);
$compile_file = md5_file($this->template_dir . '/' . $this->tpl_name) . $this->tpl_name . '.php';
$compile_file_path = $this->compile_dir . '/' . $compile_file; if ($this->force_compile || !is_file($compile_file_path)) { // 删除原编译文件
$unlink_file = '*' . $this->tpl_name . '.php';
if (strpos(strtolower(PHP_OS), 'win') === false) {
$cmd = "rm -f compile_dir/{$unlink_file}";
} else {
$cmd = "del compile_dir\\{$unlink_file}";
}
exec($cmd); $this->_mkdir('compile_dir', '0777', true);
@file_put_contents($compile_file_path, $content);
} return $compile_file_path;
} /**
* 匹配
*
* @param string $content
* @return string
*/
private function _match($content)
{
// if
$replace = array(
'#' . $this->left_delimiter . 'if[ ]+([^' . $this->right_delimiter . ']+)' . $this->right_delimiter . '#is' => '<?php if (\1) {?>',
'#' . $this->left_delimiter . '/if' . $this->right_delimiter . '#is' => '<?php }?>',
);
$content = preg_replace(array_keys($replace), array_values($replace), $content); // foreach
$replace = array(
'#' . $this->left_delimiter . 'foreach[ ]+from=\$([a-z_]+\w*)[ ]+item=([a-z_]+\w*)[ ]*' . $this->right_delimiter . '#is' => '<?php foreach ($\1 as $\2) {?>',
'#' . $this->left_delimiter . '/foreach' . $this->right_delimiter . '#is' => '<?php }?>'
);
$content = preg_replace(array_keys($replace), array_values($replace), $content); // variables
$content = preg_replace_callback('#' . $this->left_delimiter . '(((?!\?>).)*(\$(\.?\w+)+)[^' . $this->right_delimiter . ']*)' . $this->right_delimiter . '#i', array($this, '_back'), $content); return $content;
} private function _back($m)
{
$s = $m[1];
$r = '#\$(\.?\w+)+#i';
preg_match_all($r, $m[1], $list);
foreach ($list[0] as $val) {
$arr = explode('.', $val);
$var = '';
foreach($arr as $v) {
if (strpos($v, '$') === false) {
$var .= '[\'' . $v . '\']';
} else {
$var .= $v;
}
}
$s = strtr($s, array($val => $var));
}
return '<?php echo ' . $s . ';?>';
} /**
* 创建内部目录
*
* @param string $type 创建类型(compile_dir-编译目录|cache_dir-缓存目录)
* @param string $mode 权限
* @param bool $recursive 是否递归创建
* @return bool
*/
private function _mkdir($type, $mode = '0777', $recursive = false)
{
$allow_type = array('compile_dir', 'cache_dir');
if (!in_array($type, $allow_type)) {
throw new Exception("Not allowed to create the type of \"{$type}\".");
} $dir = null;
switch ($type) {
case 'compile_dir':
$dir = $this->compile_dir;
break;
case 'cache_dir':
$dir = $this->cache_dir;
break;
} if (!$dir) {
throw new Exception("Please specify the \"{$type}\" directory.");
}
if (!is_dir($dir) && !mkdir($dir, $mode, $recursive)) {
throw new Exception("\"{$dir}\" Failed to create \"{$type}\" directory");
}
return true;
} /**
* 检测变量名是否和服
*
* @param string $var 变量名
* @return bool
*/
private function _check_var($var)
{
$var_rule = '#^[a-z_]+\w*$#i';
if (preg_match($var_rule, $var)) {
return true;
}
return false;
} /**
* 单变量赋值
*/
private function _assign_var($var, $value)
{
if ($this->_check_var($var)) {
$this->var_list[$var] = $value;
}
} /**
* 多变量赋值
*/
private function _assign_arr($data)
{
foreach ($data as $var => $value) {
if ($this->_check_var($var)) {
$this->var_list[$var] = $value;
}
}
}
}$t = new Template;$t->template_dir = 'template';
$t->compile_dir = 'compile_dir';
$t->cache_dir = 'cache_dir';
$t->cacheing = false;
$t->cache_life = 5;
$t->force_compile = true;
$t->left_delimiter = '{';
$t->right_delimiter = '}';$c = array(
'user1' => array('username' => 'zhangsan', 'age' => 20),
'user2' => array('username' => 'thelisis', 'age' => 22)
);
$t->assign(array('a' => 123, 'b' => 'abc', 'c' => $c, 'x' => true));
$t->display('t.html');
{
public $template_dir = null; // 模板目录
public $compile_dir = null; // 编译目录
public $cache_dir = null; // 缓存目录
public $cache_life = 0; // 缓存时间
public $force_compile = false; // 是否强制编译
public $left_delimiter = '{'; // 左边界符
public $right_delimiter = '}'; // 右边界符 public $var_list = array(); // 变量集合
public $tpl_name = null; /**
* 编译并显示模板
*
* @param string|array $var 变量名|变量数组
* @param all 变量值
*/
public function assign($var, $value = null)
{
if (is_array($var)) {
$this->_assign_arr($var);
} else {
$this->_assign_var($var, $value);
}
} /**
* 编译并显示模板
*
* @param string $string
*/
public function display($tpl_name)
{
$this->template_dir = rtrim($this->template_dir, '/');
$tpl = $this->template_dir . '/' . $tpl_name; if (is_file($tpl)) {
$this->tpl_name = $tpl_name;
$this->fetch(file_get_contents($tpl), true);
} else {
throw new Exception("Template \"{$tpl_name}\" does not exist");
}
} /**
* 编译字符串
*
* @param string $content
* @param bool $print 是否输出(true-输出)
* @param bool $print_exit 输出终止
* @return echo|string
*/
public function fetch($content, $print = false, $print_exit = true)
{
$contents = false;
if ($this->cacheing) {
$contents = $this->_get_cache();
}
if ($contents === false) {
$compile_file_path = $this->_compile($content);
extract($this->var_list); ob_start();
require_once($compile_file_path);
$content = ob_get_contents();
ob_end_clean(); if ($this->cacheing) {
$this->_set_cache($content);
}
} else {
$content = $contents;
} if ($print) {
echo $content;
if ($print_exit) exit;
} else {
return $content;
}
} /**
* 设置缓存
*
* @param string $content
* @return string
*/
private function _set_cache($content)
{
$cache_file = $this->tpl_name;
$cache_file_path = $this->cache_dir . '/' . $cache_file;
$this->_mkdir('cache_dir', '0777', true);
@file_put_contents($cache_file_path, $content . "<!--{/**{$this->cache_life}**/}-->");
} /**
* 获取缓存
*
* @return bool/string
*/
private function _get_cache()
{
$cache_file = $this->tpl_name;
$cache_file_path = $this->cache_dir . '/' . $cache_file;
if (is_file($cache_file_path)) {
$contents = file_get_contents($cache_file_path);
$cache_life_match = preg_match('#<\!--{\/\*\*(\d+)\*\*\/}-->#', $contents, $arr);
if (!$cache_life_match) {
return false;
}
$cache_life = $arr[1];
$filemtime = filemtime($cache_file_path);
if ($filemtime + $cache_life > time()) {
return file_get_contents($cache_file_path);
} else {
return false;
}
}
return false;
} /**
* 编译
*
* @param string $content
* @return string
*/
private function _compile($content)
{
$content = $this->_match($content);
$compile_file = md5_file($this->template_dir . '/' . $this->tpl_name) . $this->tpl_name . '.php';
$compile_file_path = $this->compile_dir . '/' . $compile_file; if ($this->force_compile || !is_file($compile_file_path)) { // 删除原编译文件
$unlink_file = '*' . $this->tpl_name . '.php';
if (strpos(strtolower(PHP_OS), 'win') === false) {
$cmd = "rm -f compile_dir/{$unlink_file}";
} else {
$cmd = "del compile_dir\\{$unlink_file}";
}
exec($cmd); $this->_mkdir('compile_dir', '0777', true);
@file_put_contents($compile_file_path, $content);
} return $compile_file_path;
} /**
* 匹配
*
* @param string $content
* @return string
*/
private function _match($content)
{
// if
$replace = array(
'#' . $this->left_delimiter . 'if[ ]+([^' . $this->right_delimiter . ']+)' . $this->right_delimiter . '#is' => '<?php if (\1) {?>',
'#' . $this->left_delimiter . '/if' . $this->right_delimiter . '#is' => '<?php }?>',
);
$content = preg_replace(array_keys($replace), array_values($replace), $content); // foreach
$replace = array(
'#' . $this->left_delimiter . 'foreach[ ]+from=\$([a-z_]+\w*)[ ]+item=([a-z_]+\w*)[ ]*' . $this->right_delimiter . '#is' => '<?php foreach ($\1 as $\2) {?>',
'#' . $this->left_delimiter . '/foreach' . $this->right_delimiter . '#is' => '<?php }?>'
);
$content = preg_replace(array_keys($replace), array_values($replace), $content); // variables
$content = preg_replace_callback('#' . $this->left_delimiter . '(((?!\?>).)*(\$(\.?\w+)+)[^' . $this->right_delimiter . ']*)' . $this->right_delimiter . '#i', array($this, '_back'), $content); return $content;
} private function _back($m)
{
$s = $m[1];
$r = '#\$(\.?\w+)+#i';
preg_match_all($r, $m[1], $list);
foreach ($list[0] as $val) {
$arr = explode('.', $val);
$var = '';
foreach($arr as $v) {
if (strpos($v, '$') === false) {
$var .= '[\'' . $v . '\']';
} else {
$var .= $v;
}
}
$s = strtr($s, array($val => $var));
}
return '<?php echo ' . $s . ';?>';
} /**
* 创建内部目录
*
* @param string $type 创建类型(compile_dir-编译目录|cache_dir-缓存目录)
* @param string $mode 权限
* @param bool $recursive 是否递归创建
* @return bool
*/
private function _mkdir($type, $mode = '0777', $recursive = false)
{
$allow_type = array('compile_dir', 'cache_dir');
if (!in_array($type, $allow_type)) {
throw new Exception("Not allowed to create the type of \"{$type}\".");
} $dir = null;
switch ($type) {
case 'compile_dir':
$dir = $this->compile_dir;
break;
case 'cache_dir':
$dir = $this->cache_dir;
break;
} if (!$dir) {
throw new Exception("Please specify the \"{$type}\" directory.");
}
if (!is_dir($dir) && !mkdir($dir, $mode, $recursive)) {
throw new Exception("\"{$dir}\" Failed to create \"{$type}\" directory");
}
return true;
} /**
* 检测变量名是否和服
*
* @param string $var 变量名
* @return bool
*/
private function _check_var($var)
{
$var_rule = '#^[a-z_]+\w*$#i';
if (preg_match($var_rule, $var)) {
return true;
}
return false;
} /**
* 单变量赋值
*/
private function _assign_var($var, $value)
{
if ($this->_check_var($var)) {
$this->var_list[$var] = $value;
}
} /**
* 多变量赋值
*/
private function _assign_arr($data)
{
foreach ($data as $var => $value) {
if ($this->_check_var($var)) {
$this->var_list[$var] = $value;
}
}
}
}$t = new Template;$t->template_dir = 'template';
$t->compile_dir = 'compile_dir';
$t->cache_dir = 'cache_dir';
$t->cacheing = false;
$t->cache_life = 5;
$t->force_compile = true;
$t->left_delimiter = '{';
$t->right_delimiter = '}';$c = array(
'user1' => array('username' => 'zhangsan', 'age' => 20),
'user2' => array('username' => 'thelisis', 'age' => 22)
);
$t->assign(array('a' => 123, 'b' => 'abc', 'c' => $c, 'x' => true));
$t->display('t.html');
{$c.user1.username}
<br><hr><br>
{$c.user1.age + $c.user2.age}<br><hr><br>
{foreach from=$c item=user}
{foreach from=$user item=v}
{$v}
{/foreach}
<br>
{/foreach}
<br><hr><br>{if $x}{$b}{/if}
<br><hr><br>{$x ? $a : $b}
assign赋值你这样分,不用担心覆盖?
<table>
{foreach from=$c item=user}
<tr>
{foreach from=$user item=v}
<td>{$v}</td>
{/foreach}
</tr>
{/foreach}
</table>
就无法整体编辑了,foreach 破坏了整体感观
当然你也可以用注释标记<!-- -->,但是模版变量部分该如何处理?
的确是这样的,我感觉我这一点上做的一团糟,想请教大大,这块我该如何改进呢,当我页面中出现了例如
function () {
var = "$ljsfd";
}的时候就会出现替换混乱的问题,而我却还想用php的东西,例如{if in_array()}等等
其实 smarty 同样存在这种毛病你的这个思路是沿袭了 php3 的 phplib 的 template 思路(目前能见到的都是这个模式,我的除外)
如果么有突破,很难有所建树
private function _check_var($var)
{
$var_rule = '#^[a-z_]+\w*$#i';
if (preg_match($var_rule, $var)) {
return true;
}
return false;
}
这个正则不够全面,变量名可以用 _ 开头的
private function _match($content)
这个方法:
$replace = array(
'#' . $this->left_delimiter . 'if[ ]+([^' . $this->right_delimiter . ']+)' . $this->right_delimiter . '#is' => '<?php if (\1) {?>',
'#' . $this->left_delimiter . '/if' . $this->right_delimiter . '#is' => '<?php }?>',
);
$this->left_delimiter 如果含有正则的特殊字符就挂了,应该 preg_quote 一下吧?
public function assign($var, $value = null)
{
if (is_array($var)) {
$this->_assign_arr($var);
} else {
$this->_assign_var($var, $value);
}
}
觉得这个里面有 点累缀
改一下:
public function assign(var, value)
{
if(is_array(var)) {
this->_assign(var) // _assign 只接受 array
}
elseif(is_object) {
this->_assign(object to array)
}
else {
this->assign(array(var=>value))
}
}
觉得这样可以写少你的 _assign_var 和array的方法,这个是ci里的思路
还不能说是“代码分离”。只是将 php 代码换个格式写在模板里
的确是这个样子,我感觉模板引擎最大的好处是缓存还有去除了N多的php标签如<?php、?>,这个一多起来就感觉乱糟糟的
因为 $this->left_delimiter 和 $this->right_delimiter 都无一例外的被替换成 <?php 和 ?>$content = preg_replace_callback("#$this->left_delimiter(.+)$this->right_delimiter#U", array($this, '_back'), $content);
只需扫描 $content 一次,而不是你现在的三次
这个是tp的,如果我要是按照这种模式写的话会很简单,只不过如果和弦可以运用php原生方法才是我这里的主旨,如果如同tp那样固定模式其实是很省功夫的
你的思路与楼主的完全不一样,你是直接将数据替换进模板里
而楼主是要将模板处理成混合编码的php程序你的需要每一运行都进行替换
而楼主的只需处理一次,一次以后就可以直接运行生成的 php 代码了
至于显示逻辑如何实现,就仁者见仁、智者见智了
smarty 为代表的是把显示逻辑推给了美工
而我倾向于由模板引擎自动完成,辅以 php 程序员的协助
要求变量名唯一,虽然无可厚非。php 不也是这样的吗?好在你没有考虑在模板里嵌入模板。不然的话,这个不可重名可是要大伤脑筋了