PHP模板引擎比较和给Template_PHPLIB增加cache缓存功能

  PHP下的模板解决方案很多,主流的有 PHPLIB、IT、Flexy、Smarty等,这些模板引擎各有所长,很多人推崇Smarty,根据个人使用感受来看,Smarty有以下特点:

1、模板里面支持语法丰富,方便“程序员”(注意)在模板中实现丰富灵活的逻辑;
2、使用“预编译模板”的概念,能使性能得到一定提升;
3、支持Cache功能。

  这几个特点我认为是最核心的部分,另外Smarty还提到所谓的模板FrameWork,个人认为价值不大,并非一个完整的PHP FrameWork,所以并不推崇。而就前面Smarty的几个特点来说,反倒成为我不选择Smarty的原因之一。

1、作为模板最大的作用就是MVC框架支持,Smarty的模板语法过分丰富,导致模板在View层与Model和Controller层模糊不清,在实际的PHP应用开发中,程序员实际上过多的参与到了View层的工作,不利于团队的分工。

2、而Smarty提供的“预编译模板”的功能,表面上提高了性能,但是在实际应用中,并非完全如此,由于PHP本身属于解释性语言,而Smarty的预编译功能也是使用了PHP本身来开发,并没有使用C/C++来开发成PHP的扩展,从性能上来讲,编译的开销会比较大,如果内容更新较频繁的应用,这样的功能并不适用,反倒增加性能开销,再有Smarty会把一个简单的模板和应用,编译后生成好几个复杂的php文件,对于存储成本的和性能成本的增加也是有影响的;

3、Smarty本身太大,核心的类文件达到160多k,如果加上Core和plugin的类,达到300多k的代码,每次用户请求产生的系统开销是很大的,比较适合中小应用。相比之下,PHPLib类仅一个文件14k左右,Flexy也很小,更适合大型应用。

  相比Smarty,个人更推荐使用Flexy和PHPLIB,Flexy具备了Smarty提供的大部分功能,也是预编译,但是性能经过我测试比Smarty要好,也同样有丰富的语法,对于喜欢同时开发PHP代码和模板文件的程序员来说,值得推荐。而PHPLIB是我最推荐使用的,主要考虑这样几个因素:

1、PHPLIB有很久远的历史,它的前身是Perl的模板引擎,然后迁移到php3时代的phplib里面,接而进入到PHP的PEAR框架下,从稳定性来说不用怀疑;

2、PHPLIB相当简单,全部代码一个类,仅14K左右,提供了最基本的MVC解决思路,虽然没有灵活多样的模板语法支持,但是根据我的经验,WEB应用上使用模板解决的地方,PHPLIB都能实现,同时由于代码量非常小,系统开销也会小;

3、性能优秀,网上曾经有人做过PHPLIB和Smarty的性能测试,在Smarty不打开编译缓存功能的情况下,PHPLIB比Smarty快20%,Smarty打开编译缓存情况下,比PHPLIB快10%,虽然这个测试我认为并不充分,并且和实际应用有差距,但是我认为至少从性能上来说,PHPLIB并不比Smarty慢,相反我认为在实际WEB应用中会更快,如果给PHPLIB增加一个Cache功能,那么性能能提升将近10倍(这个结果是我自己测试后得到的结果);

4、PHPLIB在模板的解析和实现上与别的模板没有什么差别,大家都是使用字符串替换操作来实现,而且都使用了preg_replace()函数来实现。

  通过前面的个人分析(个人意见,大家可以自己实际测试,也可以否定我的说法),我决定在我的WEB应用中使用基于PHPLIB的模板引擎来实现MVC模式,不过由于PHPLIB过分简单,不提供Cache功能,所以我自己动手扩展了PHPLIB类,增加缓存的实现,实际使用后发现效果非常好。

在我扩展的类 MyTemplate 中,主要实现下面功能需求:
1、自定义缓存开关
2、支持缓存超时判断
3、支持模板文件更新后更新缓存判断
4、支持程序文件更新后缓存判断
5、支持缓存文件散列存放自定义

  下面是我的 MyTemplate 类实现代码,里面包含了具体的使用说明和范例,有不明白的地方可以留言给我,欢迎大家和我交流PHPLIB相关的模板应用,和Smarty、Flexy相关的内容就希望别问我,本人不对此类问题给予回答,谢谢。

[coolcode lang=”php” linenum=”off”]
4.3.4 & 5.x |
// +———————————————————————-+
// | Copyright (c) 2006-2007 toplee.com |
// +———————————————————————-+
// | 本文件包含PEAR的PHPLIB模板类扩展功能类定义 |
// | 本类包含对PEAR的PHPLIB Template类继承,并实现cache、utf8等支持 |
// +———————————————————————-+
// | Authors: Michael Lee |
// +———————————————————————-+
//
// $Id: MyTemplate.class.php,v 1.0 2006/08/28
//

/**
* 使用帮助:
* 实例化类 $TPL = new MyTemplate(array $tpl_config)
* 调用模板 $TPL->setFile($handle, $filename=””)
* 设置block $TPL->setBlock($parent, $handle, $name=””)
* 解析变量 $TPL->setVar($varname, $value=””, $append=false)
* 解析页面 $TPL->parse($target, $handle, $append=false)
* 输出页面 $TPL->p($varname) = echo $TPL->get($varname);
* 检查cache $TPL->cacheCheck()
* 写入cache $TPL->cache($data)
* 输出cache $TPL->pCache()
*
* 实例化模板类时配置选项$tpl_config格式和说明
* $tpl_config = array(
* ‘debug’ => false, //是否显示debug信息
* ‘root’ => ‘tpl’, //模板存放路径,目前是相对路径,末尾不包含/
* ‘unknowns’ => ‘remove’,//模板中未解释的标记是否保留输出
* ‘cache’ => array(
* ‘cache’ => true, //是否打开cache支持
* ‘root’ => ‘tpl_cache/’,//cache存放路径,目前支持相对路进,末尾包含/
* ‘hash’ => 3, //cache缓存文件散列目录级数,比如 a/3/d
* ‘life_time’ => 10, //cache文件默认失效时间,单位秒
* ‘file_ext’ => ‘tpl.html’, //cache文件扩展名
* ),
* );
*/

require_once(‘HTML/Template/PHPLIB.php’);

class MyTemplate extends Template_PHPLIB
{
var $cache = array(); //和cache相关的配置信息,在tpl_config里面设置

var $cache_dir = ”; //当前请求对应的cache存放目录,相对路径末尾包含 /
var $cache_md5 = ”; //根据script_path得到的md5值,用于路径和cache文件名
var $cache_file = ”; //包含完整路径的cache文件
var $cache_ini = ”; //包含网站路径的cache配置文件

var $root_path = ”; //从当前路径开始相对于网站根目录的路径信息
var $script_path = ”; //当前请求页面的绝对路径,如/test.php?a=b,支持POST
var $php_self = ”; //当前请求页面的绝对路径,如/test.php

/**
* $cache_parse = array(
* ‘md5’ => ”,
* ‘tpl_count’ => 2,
* 0 => array(
* ‘tpl’ => ‘aa.tpl’,
* ‘md5’ => ‘md51’,
* ),
* 1 => array(
* ‘tpl’ => ‘bb.tpl’,
* ‘md5’ => ‘md52’,
* ),
* );
*/
var $cache_parse = array();

/**
* @Purpose:构造函数
* @Param array $tpl_config
* @Author Michael Lee * @Return: NULL
*/
function MyTemplate($tpl_config=””)
{
$debug = isset($tpl_config[‘debug’]) ? $tpl_config[‘debug’] : false;
$root = isset($tpl_config[‘root’]) ? $tpl_config[‘root’] : “.”;
$unknowns = isset($tpl_config[‘unknowns’]) ? $tpl_config[‘unknowns’] : ‘remove’;
$this->cache = $tpl_config[‘cache’];

$this->Template_PHPLIB($root,$unknowns);
$this->debug = $debug;

//仅在打开了cache支持的配置情况下才启用和cache相关的功能
if ($this->cache[‘cache’]) {
//初始化得到一些当前访问请求对应的路径信息
$this->_getScriptPath();
$this->_getRootpath();
$this->_getPhpSelf();

//取得当前请求对应的cache_dir和cache_md5
$this->_getCacheFile();
}

if ($this->debug) {
if ($this->cache[‘cache’])
echo ‘Current Cache md5:’.$this->cache_md5.’
‘;
else
echo ‘Cache Disabled! Just use PHPLIB!
‘;
}
}

/**
* 把当前访问请求结果页面写入cache
* 首先要得到当前页面根据php_self得到的md5_file值
* 然后根据$this->file数组得到各个模板文件名和路径,分别得到他们的md5_file值
* 把以上值写入到cache的ini配置文件,同时把cache页面内容写入cache文件
*
* @access public
* @param string $data
* @return true
*/
function cache($data)
{
if (!$this->cache[‘cache’]) return;

$ini = “”;
$file = $this->php_self;
//为了支持cli-cgi模式的php运行环境
if (substr($file,0,1) == “/”) $file = $this->root_path.$file;
$md5 = md5_file($file);
$ini .= “md5 = $md5\r\n”;

$count = count($this->file);
$ini .= “tpl_count = $count\r\n”;

$i = 0;
foreach ($this->file AS $k=>$v) {
$ini .= “[$i]\r\n”;
$ini .= “tpl = \”$v\”\r\n”;
$ini .= “md5 = \””.md5_file($v).”\”\r\n”;
$i++;
}
//echo $ini; exit; //for debug

$this->_mkdir2($this->cache_dir);
if (!$this->_writeToFile($this->cache_ini,$ini)) return false;
if (!$this->_writeToFile($this->cache_file,$data)) return false;

return true;
}

/**
* 检查当前请求相关的cache是否可用
* 1.$this->cache[‘cache’]是否为true 2.是否存在 3.是否过期
* 4.是否当前php程序文件有改变 5.是否相关的tpl文件有变动
*
* @access public
* @return bool true/false
*/
function checkCache()
{
//1.判断当前是否允许cache
if (!$this->cache[‘cache’]) {
if (DEBUG) echo ‘cache disabled’;
return FALSE;
}

//2.判断当前cache_file是否存在
if (!file_exists($this->cache_file) || !file_exists($this->cache_ini)) {
if (DEBUG) echo ‘cache not exists’;
return FALSE;
}

//3.判断是否过期
$now = time();
$orig = filemtime($this->cache_file);
$chk = $now-$orig;
if ($chk > $this->cache[‘life_time’]) {
if (DEBUG) echo ‘cache expired’;
return FALSE;
}

//解析cache的ini配置文件
$this->_parseCacheIni();

//判断当前php文件是否有改变
//取得当前php文件的md5_file值
$php_self = $this->root_path.substr($this->php_self,1);
$md5_script = @md5_file($php_self);
if ($md5_script != $this->cache_parse[‘md5’]) {
if ($this->debug) echo ‘md5 bad’;
return false;
}

//判断相关的tpl文件是否有变化,方法和前面类似
for($i=0;$i<$this->cache_parse[‘tpl_count’];$i++) {
$tpl_file = $this->cache_parse[$i][‘tpl’];
$tpl_md5 = $this->cache_parse[$i][‘md5’];
$tpl_md5_cur = @md5_file($tpl_file);
if ($tpl_md5_cur != $tpl_md5) {
if ($this->debug) echo ‘tpl md5 bad: ‘.$tpl_file.$tpl_md5_cur.’#’.$tpl_md5;
return false;
}
}

return true;
}

/**
* 取得cache,用于页面输出
* 去掉cache前面的配置行
*
* @access public
* @return true
*/
function pCache()
{
@readfile($this->cache_file);
return true;
}

/**
* 删除cache
*
* @access public
* @return NULL
*/
function rmCache()
{
@unlink($this->cache_file);
@unlink($this->cache_ini);
return;
}

/**
* 得到cache文件名,包括路径信息,如./cache/ab/cd/ef/abcdef…..tpl.html
*
* @access private
*/
function _getCacheFile()
{
if (empty($this->script_path)) $this->_getScriptPath();
$md5_string = md5($this->script_path);
$path = “”;
for ($i=0;$i<$this->cache[‘hash’];$i++)
$path .= substr($md5_string,$i,1).’/’;

$path = $this->cache[‘root’].$path;
$this->cache_dir = $path;
$this->cache_md5 = $md5_string;
$this->cache_file = $path.$md5_string.’.’.$this->cache[‘file_ext’];
$this->cache_ini = $path.$md5_string.’.ini’;
}

/**
* 解析cache文件的头四行,得到下列信息
* $this->cache_file_parse[‘php_self_md5’] 当前php程序的原始md5值
* $this->cache_file_parse[‘tpls_count’]和[‘tpls’]
*
* @access private
*/
function _parseCacheIni()
{
$this->cache_parse = @parse_ini_file($this->cache_ini,true);
return true;
}

/**
* 把内容写入指定文件
*
* @access private
*/
function _writeToFile($file,$content,$mode=’w’)
{
$oldmask = umask(0);
$fp = fopen($file, $mode);
if (!$fp) return false;
@fwrite($fp,$content);
@fclose($fp);
@umask($oldmask);
return true;
}

/**
* 创建多级目录
*
* @access private
*/
function _mkdir2($dir)
{
$dir = @preg_replace(“/\\\/”,”/”,$dir);
$dir = @preg_replace(“/\/{2,}/”,”/”,$dir);
$dir = @explode(‘/’,$dir);

$path = “”;
for ($i=0;$i‘);
$replace = array(‘"’, ‘<‘, ‘>’);
$sp = str_replace($find, $replace, $sp);
$sp = xss_clean($sp);

if ($_SERVER[‘REQUEST_METHOD’] == ‘POST’) {
$p = @serialize($_POST);
$sp .= “?$p”;
}
return $sp;
}

/**
* 取得当前请求的脚本文件绝对路径
* 用于得到当前文件的md5值,判断当前文件是否被修改过
* 当前使用public_setting.inc.php里面得到的PHP_SELF值
*
* @access private
*/
function _getPhpSelf()
{
global $_ENV,$_SERVER;

if ($_ENV[‘PHP_SELF’] OR $_SERVER[‘PHP_SELF’])
$p = $_ENV[‘PHP_SELF’] ? $_ENV[‘PHP_SELF’] : $_SERVER[‘PHP_SELF’];
elseif ($_ENV[‘SCRIPT_NAME’] OR $_SERVER[‘SCRIPT_NAME’])
$p = $_ENV[‘SCRIPT_NAME’] ? $_ENV[‘SCRIPT_NAME’] : $_SERVER[‘SCRIPT_NAME’];
else
$p = preg_replace(‘#(\?.*)#’, ”, $this->script_path);

return $p;
}

/**
* 取得当前请求的脚本文件相对于根目录的相对路径,如 ../../
* 当前使用public_setting.inc.php里面得到的ROOT_PATH值
*
* @access private
*/
function _getRootPath()
{
$a = @explode(“/”,$this->script_path);
$c = @count($a);
$p = “”;
for ($i=0; $i < $c-2; $i++) $p = "../".$p; if ($p == "") $p="./"; return $p; } } ?>
[/coolcode]

13 thoughts on “PHP模板引擎比较和给Template_PHPLIB增加cache缓存功能”

  1. phplib作为一个整体的包来说,已经很少用到了,取而代之的是PEAR,phplib是php3时代杰出的产品。

    项目无论大小,都可以用PEAR,至少可以单独使用里面的部分模块。

  2. 您好,我现在安装的是php5,里面有PEAR,我想我已经在不知不觉用了PEAR,但我自已不知道罢了。

    我原来做过一段时间的php开发,但不是cvs结构的,现在自已构化出一个小项目,我想练习使用cvs的手段,使我和美工的工作能够完全分离,但我真的心里没有底,如果美工对代码是一无所知,只否从页面到后台的开发工作就都是我的了。:(

    我想我应该会有很多问题请教,但我需要先从网上先搜一搜。

    谢谢您对一只小菜鸟的指导。

  3. 我在网上搜了一些关于pear的资料,可是还是得不到要领,请教一下,如果想用pear的模板功能,我应该从那里入手比较好,有没有相关的例子程序?

    我图省事,装的是xampp,里面有php5,好象带pear,我也下载了一个pear的英文手册,但是没有找到相关templant的资料。不知道从那里下手?

  4. 我现在还有一个特别矛盾的问题,想征求一下您的意见。

    我现在是在开发一个针对公司的任务流管理的应用程序,如果我用DW开发的话,开发速度会很快,但这样,我就没有学习新技术的机会了。但我也很怕用新技术会把开发进度变得太慢。并且,使用这种新技术,将来找虚拟空间,也会比较不好找,您有什么好的建议呢?

Leave a Reply

Your email address will not be published. Required fields are marked *

*
To prove you're a person (not a spam script), type the security word shown in the picture. Click on the picture to hear an audio file of the word.
Anti-spam image