浅析Savant模板引擎机制

原文地址:http://wu2yong3jinh.ixiezi.com/2010/06/23/%E6%B5%85%E6%9E%90savant%E6%A8%A1%E6%9D%BF%E5%BC%95%E6%93%8E%E6%9C%BA%E5%88%B6/

要说到优秀的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表示编译错误等。

posted @ 2013-05-19 18:26  花开花落云卷云舒  阅读(417)  评论(0)    收藏  举报