浅析Savant模板引擎机制
要说到优秀的PHP模板引擎,PHPer响应最多的一定是Smarty,它使用自己的模板语言,所以需要将模板“编译”成PHP。
Savant也是面向对象的PHP模板引擎,但它使用PHP本身来作为它的模板语言,不需要“编译”,代码量更少、执行速度更快。Savant只包含了模板引擎的最基本功能(如数据格式化功能),但它有一个面向对象的模板插件系统和输出过滤器,可以让你快速为它新增新的行为。我想,这是Cal Henderson在《构建可扩展的Web站点》一书中推荐Savant的理由。
最新版的是Savant3,其使用及其简单、清晰,使用Demo见:http://phpsavant.com/docs/,API见:http://phpsavant.com/api/Savant3/。
Savant3的文件目录结构如下:
Savant3.php //包含Savant3类 Savant3 resources //此文件夹存放插件和过滤器 Savant3_Filter_trimwhitespace.php //trimwhitespace过滤器,其类Savant3_Filter_trimwhitespace继承了Savant3_Filter Savant3_Plugin_ahref.php //ahref插件,其类Savant3_Plugin_ahref继承了Savant3_Plugin,下同 Savant3_Plugin_date.php //date插件 Savant3_Plugin_htmlAttribs.php //htmlAttribs插件 Savant3_Plugin_image.php //image插件 Error.php //包含错误类Savant3_Error Exception.php //包含异常类Savant3_Exception Filter.php //包含抽象过滤器类Savant3_Filter,规定了编写“官方”过滤器须要遵循的接口。但函数或不是Savant3_Filter的子类方法也可以作为过滤器。 Plugin.php //包含抽象插件类Savant3_Plugin,规定了编写插件须要遵循的接口
Savant3.php是核心(或入口)文件,其默认只加载了Savant3_Filter类和Savant3_Plugin类的两个文件,如下:
include_once dirname(__FILE__) . ‘/Savant3/Filter.php’;
include_once dirname(__FILE__) . ‘/Savant3/Plugin.php’;
Savant3的执行流程? 假设在我们的项目中这样使用Savant引擎:
require_once 'Savant3.php';
$tpl = new Savant3(array('template_path'=>array('./templates/'), 'filters'=>array('Savant3_Filter_trimwhitespace','filter')));
$tpl->title = "Hello World!";
$tpl->display('books.tpl.php');
从$tpl->display(’books.tpl.php’);语句开始,主要按序执行了以下方法: 1、$tpl->display(’books.tpl.php’),输出模板books.tpl.php代码执行后的内容,用途同Smarty->display()。
public function display($tpl = null){
echo $this->__toString($tpl);//调用__toString()魔术方法
}
2、__toString()中调用了方法:$this->fetch(’books.tpl.php’),获取模板’books.tpl.php’代码执行后的内容,用途同Smarty->fetch()。
public function fetch($tpl = null) {
//more code here...
// get a path to the compiled template script
$result = $this->template($tpl);
//more code here...
$this->__config['fetch'] = $result;
unset($result);
unset($tpl);
// are we doing extraction?
if ($this->__config['extract']) {
// pull variables into the local scope.
// 如果执行了这一步,模板中将可以使用$this->title或$title这两种形式来获取值:“Hello World!”。
extract(get_object_vars($this), EXTR_REFS);
}
// buffer output so we can return it instead of displaying.
ob_start();
// Savant3默认不使用任何过滤器。但我们配置要使用trimwhitespace过滤器,用于清除获取的内容中的多余空白。当然此过滤过程势必影响执行速度。
if ($this->__config['filters']) {
// use a second buffer to apply filters.
ob_start();
include $this->__config['fetch'];
echo $this->applyFilters(ob_get_clean());
} else {
// no filters being used.
include $this->__config['fetch'];
}
// reset the fetch script value, get the buffer, and return.
$this->__config['fetch'] = null;
return ob_get_clean();
}
3、fetch()中调用了方法:$this->template(’books.tpl.php’)。Savant3使用PHP作为模板语言,所以默认不用“编译”,但执行过程中仍然提供了调用“编译”逻辑的机会,让我们能够自定义“编译”逻辑并在此执行,该“编译”逻辑必须返回(新的)模板文件的路径。比如可以像Smarty一样在’books.tpl.php’中使用自定义的模板标签,并编写相应的“编译”逻辑,“编译”生成一个新文件并返回新文件的路径。
protected function template($tpl = null){
//解析并获得模板文件'books.tpl.php'的绝对路径
$file = $this->findFile('template', $tpl);
// are we compiling source into a script?
if ($this->__config['compiler']) {
// compile the template source and get the path to the
// compiled script (will be returned instead of the
// source path)
$result = call_user_func(
array($this->__config['compiler'], 'compile'),
$file
);
} else {
// no compiling requested, use the source path
$result = $file;
}
//the result is a path to a script
return $result;
}
Savant3的过滤机制? 我们可以在$__config = array(’filters’ => array())中配置模板引擎执行过程中应用哪些过滤器,这些过滤器将在fetch()方法的以下代码中有被应用的机会。
if ($this->__config['filters']) {
// use a second buffer to apply filters.
ob_start();
include $this->__config['fetch'];
echo $this->applyFilters(ob_get_clean());
} else {
// no filters being used.
include $this->__config['fetch'];
}
而$this->applyFilters($buffer)会一一调用这些过滤器。如果继承了Savant3_Filter类的过滤器没有加载,则会使用$this->findFile()自动寻找并加载这些过滤器文件。
注意,并不一定要继承Savant3_Filter类并实现相应“接口”,才成为一个过滤器。applyFilters()中使用$buffer = call_user_func($callback, $buffer)来调用过滤器,只要符合call_user_func()规则的函数或类方法都可以作为过滤器,但必须手动加载这些文件。
Savant3的插件机制? 在’books.tpl.php’模板中,我们可以使用$this->image()来调用resource/Savant3_Plugin_image.php中的image()方法。这个$this等于$tpl,应该是调用Savant3中的image()方法啊?!为什么呢?
这就是它实现插件机制的关键所在。当调用Savant3类中不存在的image()方法时,将触发它的魔术方法__call(),该方法将会调用$this->plugin()来查找并加载Savant3_Plugin_image.php文件,并最终返回一个Savant3_Plugin_image对象供执行。最终效果是,$this->image()实际上是调用Savant3_Plugin_image->image()。
所以,与过滤器有两点不同: 1、插件必须继承自Savant3_Plugin类,严格遵循其制定的“接口”标准而过滤器则不一定要继承Savant3_Filter类。 2、插件在模板代码的执行过程中被加载并执行,而过滤器则作用于模板代码的执行结果上。
Savant3的错误处理机制? Savant3使用$this->error($code, $info = array(), $level = E_USER_ERROR, $trace = true)来处理错误。在处理逻辑中,Savant3可以输出Savant3_Error类包装的错误信息,也可以抛出Savant3_Exception异常。你可以通过配置$__config(’exceptions’ => false)来决定是输出错误信息还是抛出异常。
值得学习的思想还有,Savant3调用error()时,用$code来标识错误的不同类型,如ERR_PLUGIN表示找不到插件、ERR_TEMPLATE表示找不到模板文件、ERR_COMPILER表示编译错误等。
浙公网安备 33010602011771号