在PHP中使用模板技术,一直是一个受欢迎的技术。自从2001年开始,PHP的很多爱好者,在PHP中引用了MVC开发模式后,模板技术就更为火热。
网上很多出名的模板引擎,如phplib的template、FastTemplate、easyTPL、BTemplate以及现在受php.net推宠的Smarty模板引擎。
我个人非常喜欢钟爱那些短小精悍的php代码,像Smarty这些动折2000多行的代码实现的模板引擎,一般不是我的钟爱。
前一段时间,看了phpe.net上有一篇文章叫<<超越模板引擎>>感觉那个老外所说确实极其有道理。他这样说:Smarty的目标是"把业务逻辑从表现中分离出来"而不是"PHP代码和HTML代码的分离"。但是Smarty确实也太庞大了,如果我在使用Smarty的话,我会感觉Smarty会不会影响php代码执行效率,首先在解析2000多行的Smarty代码就可能会带来很大延迟。
受<<超越模板引擎>>一文作者影响,我感觉到php本身就是一个嵌入式的脚本语言,如果我们能够使用用简单的php代码写成的模板,而在运行时直接将他include进来那不是更为快捷?比如像如下这个样子的代码:
My name is {myname}.
为什么我们不直接写成
My name is <?=$myname?>.
这样样子呢?
还有模板里常用的列表,其实也是简单的loop
<!-- BEGIN user_list -->
User ID : {var0}
User Name : {var1}
<!-- END user_list -->
为什么我们不直接写成如下的代码
PHP: |
<!---->foreach ( $user_list as $user ) { ?>
User ID : <!---->['id']?>
User Name : <!---->['name']?>
<!---->} ?> |
其实早在一年前,我就尝试用这种方法去做一些企业和政府的WebOS,只是当时,还没有意识这样做带来的好处。只是感觉这样的模板我不需要单独的代码去处理他,只是简单的include一下就ok了。而且,我记得当时在做这些用php语言直接写出的模板的时候,非常快捷,以至于到后来,参与开发几个美工都因此对php的程序开发,发生了很大的兴趣,开始学习php的脚本语言。
最近又受<<超越模板引擎>>一文影响,所以决定根据他的思想去实现一个模板处理的类,经过了二三天的每天像榨水果汁一样挤出的几个小时去完成了一个模板类。现在张贴在此,以便能有志同道合朋友能够与我一起讨论。
考虑了现在带会经常使用{name}的这样的标记去做模板,所以我写一个简单的ParseHTM()的方法,去解析这样的文件,将其转换为<?=$name?>这样的php模板。然后将转换后的文件存贮到相应的cache目录中去,然后提供一个ParsePHP()方法去简单的include文件然后在主程序中echo输出。在实现的时候,我为了简化调用方法.使用了一个Parse()方法去呼叫上述两个私有方法.
以下是代码示例:
首先给出目录结构:
\ 根目录
├─cache 存放php模板缓存的文件
├─includes 类库存放目录
│ └─template 模板处理template.php类库文件存放目录
└─templates 模板目录
├─imgs 模板文件的图片目录
│ └─ver.1
└─tpls 模板文件目录
└─ver.1
示例一:对于html类型的模板文件的处理.
test.htm.php 模板文件存放在 \templates\tpls\ver.1\目录下
<html>
<head>
<title>{title}</title>
</head>
<body>
<!-- Trim php code that can't occur in HTML type template file. Now , that will be disable . -->
<?
print_r($_ENV);
?>
<?=$test?>
<? if ( $a == $b ) echo 'a==b'; ?>
<!--IF condition-->
<table border="1" align="center">
<tr>
<td>$condition is TRUE</td>
</tr>
</table>
<!--ENDIF-->
<!--IF a == b-->
<table border="1" align="center">
<tr>
<td>$a == $b</td>
</tr>
</table>
<!--ENDIF-->
<!--IF a != b-->
<table border="1" align="center">
<tr>
<td>$a != $b</td>
</tr>
</table>
<!--ENDIF-->
<table border="1" align="center">
<tr>
<td>id</td>
<td>name</td>
</tr>
<!--LIST user_list AS user-->
<tr>
<td>{user[id]}</td>
<td>{user[name]}</td>
</tr>
<!--ENDLIST-->
</table>
</body>
</html>
test_htm.php文件对类库调用示例,本文件存放在 \ 目录下
下面是对这样的html类型的模板出理调用的具体过程.
PHP: |
<!---->
require_once('includes/template/template.php');
$tpl = new Template;
$tpl->SetFile('templates/tpls/ver.1/test.htm.php');
// 对于htm类型模板来说,通过此设置编译后的模板文件
$tpl->SetCacheDir('cache/');
$tpl->SetVar('title','I love jear');
$tpl->SetVar('condition',TRUE);
$tpl->SetVar('a','a');
$tpl->SetVar('b','a');
$user_list = array(
array(
'id' => '1',
'name' => 'Stangly.wrong'
),
array(
'id' => '2',
'name' => 'Jear'
),
);
$tpl->SetVar('user_list',$user_list);
echo $tpl->Parse(TPL_HTM, TRUE);
?> |
观察一下目录结构的变化
\
├─cache
│ └─templates
│ └─tpls
│ └─ver.1
├─includes
│ └─template
└─templates
├─imgs
│ └─ver.1
└─tpls
└─ver.1
程序会自动在cache目录下,建立与template文件存放的路径相同的目录路径去存放编译后的模板文件。这样个人感觉非常便于查找与排错。
示例二:对于php类型的模板文件的处理,观察一下与htm类型的模板文件有什么不同?是不是所有 {title} 这样的标记都被替换为 <?=$title?>这种形式,以及对于所谓的 block 也被直接替换为 foreach() 方式。
test.php.php 模板文件存放在 \templates\tpls\ver.1\目录下
<html>
<head>
<title><?=$title?></title>
</head>
<body>
<? if ($condition) { ?>
<table border="1" align="center">
<tr>
<td>$condition is TRUE !</td>
</tr>
</table>
<? } ?>
<? if ($a == $b) { ?>
<table border="1" align="center">
<tr>
<td>$a == $b</td>
</tr>
</table>
<? } ?>
<? if ($a != $b) { ?>
<table border="1" align="center">
<tr>
<td>$a != $b</td>
</tr>
</table>
<? } ?>
<table border="1" align="center">
<tr>
<td>id</td>
<td>name</td>
</tr>
<? foreach ( $user_list as $user ) { ?>
<tr>
<td><?=$user[id]?></td>
<td><?=$user[name]?></td>
</tr>
<? } ?>
</table>
</body>
</html>
test_htm.php文件对类库调用示例,本文件存放在 \ 目录下
下面是对这样的html类型的模板出理调用的具体过程.
PHP: |
<!---->
require_once('includes/template/template.php');
$tpl = new Template;
$tpl->SetFile('templates/tpls/ver.1/test.php.php');
// 对于 php 文件来说, 此设置无效
//$tpl->SetCacheDir('cache/');
//
$tpl->SetVar('title','I love jear');
$tpl->SetVar('condition',TRUE);
$tpl->SetVar('a','a');
$tpl->SetVar('b','a');
$users_list = array(
array(
'id' => '1',
'name' => 'Stangly.wrong'
),
array(
'id' => '2',
'name' => 'Jear'
),
);
$tpl->SetVar('user_list',$users_list);
echo $tpl->Parse(TPL_PHP);
?> |
以上就是模板类的调用示例,以及模板文件内容的格式。
最后给出template.php文件,他存放于 /includes/template/ 目录下。
PHP: |
<!---->
// -------------------------------------------------------
// Filename : template.php
// Created : 2004-11-6 13:46
// Author : Stangly.Wrong
// Version : 1.0.0
// Description : Parse template
// Modified : 2004-11-7 15:10
// -------------------------------------------------------
// 定义模板类型
define( 'TPL_HTM', 'htm' );
define( 'TPL_PHP', 'php' );
// 定义默认的缓存目录
define( 'TPL_CACHE_DIR', './cache/');
// 编译后的 TPL_HTM 类型缓存文件名 前缀
define( 'TPL_COMPLIE_FILE_PREFIX','_c_');
Class Template
{
// 模板文件存放的目录
var $_root_dir = NULL;
// 模板文件名
var $_file = NULL;
// 模板文件类型
var $_file_type = TPL_PHP;
// 存贮模板内定义的变量
var $_var = array();
// 编译和缓存文件存放目录
var $_cache_dir = TPL_CACHE_DIR;
// Private function
//
// 参数:
// $f 为PHP类型模板完整路径文件名
//
// 解析php模板文件
//
// 返回解析的结果, 用于直接输出到客户端
//
function &_ParsePHP( $f )
{
if ( !file_exists( $f ) )
{
die('Open file '.$f.' failed !');
}
extract($this->_var);
ob_start();
include($f);
$contents = &ob_get_contents();
ob_end_clean();
return $contents;
}
// Private function
//
// 参数:
// $f 为HTM类型模板完整路径文件名
// $c 确定是否需要重新编译htm模板
//
// 解析htm模板文件
//
// 返回解析的结果, 用于直接输出到客户端
function &_ParseHTM( $f, $c = FALSE )
{
if ( $c )
{
$f = $this->_CompileHTM( $f );
}
else
{
$f = $this->_cache_dir.$this->_root_dir.'/'.TPL_COMPLIE_FILE_PREFIX.$this->_file;
}
return $this->_ParsePHP( $f );
}
// Private function
//
// 参数:
// $f 为HTM类型模板完整路径文件名
//
// 编译htm模板文件
//
// 编译htm模板文件,将存贮到缓存目录中
// 同时返回编译后的文件名
//
function &_CompileHTM( $f )
{
if ( !file_exists( $f ) )
{
die('Open file '.$f.' failed !');
}
ob_start();
$fp = fopen( $f, 'r');
$c = fread($fp, filesize($f));
// 屏蔽php代码
$c = preg_replace('/(\<\?)(.*)(\?\>)/siU', '<!---->', $c);
$c = preg_replace('/<!---->/', '<!---->} ?>', $c);
$c = preg_replace('/<!---->/', '<!---->} ?>', $c);
$p = array(
0 => '/\{([^}]+)\}/',
1 => '/<!---->/',
2 => '/<!---->/',
3 => '/<!---->/',
4 => '/<!---->/',
);
$s = array(
0 => '<!---->\$\1?>',
1 => '<!---->\$\1) { ?>',
2 => '<!---->\$\1 as \$\2 ) { ?>',
3 => '<!---->\$\1 \2 \$\3) { ?>',
4 => '<!---->\$\1 as \$\2 => \$\3 ) { ?>',
);
echo $c = preg_replace($p, $s, $c);
$c = &ob_get_contents();
ob_end_clean();
// 生成需要创建的缓存目录
$d = $this->_cache_dir.$this->_root_dir;
$this->_MakeDirectory( $d );
// 创建编译后的模板文件
$cf = $d.'/'.TPL_COMPLIE_FILE_PREFIX.$this->_file;
if(!@$fp = fopen($cf, 'w'))
{
die('Open file '.$cf.' failed !');
}
flock( $fp, 3 );
fwrite($fp, $c);
fclose($fp);
return $cf;
}
// Private function
//
// 参数
// $d 要创建的目录名
//
// 创建目录
//
function _MakeDirectory( $d )
{
if (is_null($d))
{
return TRUE;
}
$p = explode('/', $d);
foreach ( $p as $k => $v )
{
$prefix .= $v.'/';
if ( is_dir( $prefix ) )
continue;
if (!empty($v) && !@mkdir($prefix, 0777))
die('Cant create cache dir : '.$prefix);
}
return TRUE;
}
/******************************************************************************************/
// 公共成员方法 调用接口
// 初始化成员变量
function Template() {}
// public function
//
// 参数:
// $d 缓存目录路径
//
// 设置缓存和编译文件存放目录
//
function SetCacheDir( $d )
{
$this->_cache_dir = $d;
return TRUE;
}
// Public function
//
// 参数:
// $f 模板文件存放的完整路径名
// $t 模板类型
// TPL_HTM
// TPL_PHP
//
// 设置模板文件存放的目录和文件名以及模板的类型
//
function SetFile( $f = NULL, $t = TPL_HTM )
{
$this->_root_dir = dirname($f);
$this->_file = basename($f);
$this->_file_type = $t;
return TRUE;
}
// Public function
//
// 参数:
// $n 可以为 模板内在{}号内定义的名称
// 或为数组
// $n[]['id'], $n[]['name'], $n[]['sex']
//
// $v 当 $n 为数组时, $v 为空值
//
// 设置模板内定义变量
//
function SetVar( $n, $v = NULL )
{
if ( is_array( $n ) )
{
foreach ( $n as $k => $v )
{
$this->_var[$k] = $v;
}
}
else
{
$this->_var[$n] = $v;
}
return TRUE;
}
// Public function
//
// 参数说明:
// $t 为处理的模板的类型
//
// 对指定的模板进行处理解析 根据 $t 的类型返回不同的结果
//
// $t == TPL_PHP 立即解析返回 html 文件 ( parse -> result )
// $t == TPL_HTM 立即解析返回 html 文件 ( compile -> cache compiled -> parse -> result )
//
// $e == TRUE 时重新编译模板文件 FALSE 使用上一次的编译过的文件
//
function &Parse( $t, $e = FALSE)
{
switch ( $t )
{
case TPL_PHP :
$c = $this->_ParsePHP( $this->_root_dir.'/'.$this->_file );
break;
case TPL_HTM :
$c = $this->_ParseHTM( $this->_root_dir.'/'.$this->_file, $e );
break;
}
return $c;
}
}
?> |