技术产品   

浅谈模板引擎工作原理及自定义标签

时间:2012-12-24 15:57:00

编译式模板与解析式模板

编译式模板是提前生成html页面,而解析式模板则是在执行时逐个语句解析,这里解释的对象是标签。两者在解释原理上有不同,在语法上、表达能上几乎相同,但也略有区别。现在编译式模板还较少,但将来编译式模板会逐步取代解析式模板

这篇文章主要讨论编译式模板与解析式模板运作的基本原理,以及如何自定义标签。这对使用好标签有一定意义。

1编译式模板

先来写个程序(以后root代表根目录)

root/code.php

复制代码
<?php
    //利用dedecms写php时,基本都要引入common.inc.php
    require_once (dirname(__FILE__) . '/include/common.inc.php');
    //利用编译式模板所需的文件
    require_once (DEDEINC.'/dedetemplate.class.php');
    
    //生成编译模板引擎类对象
    $tpl = new DedeTemplate(dirname(__file__));
    //装载网页模板
    $tpl->LoadTemplate('code.tpl.htm');
    
    //把php值传到html
    $title = 'Hello World 网页';
    $tpl->SetVar('title',$title);
    $tpl->Display();
    //把编译好的模板缓存做成code.html,就可以直接调用
    $tpl->SaveTo(dirname(__FILE__).'/code.html');
?>
复制代码

root/code.tpl.htm

复制代码
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

 <head>

       <!— var标签 -->

  <title> {dede:var.title/} </title>

       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

 </head>

 <body>

 </body>

</html>
复制代码

打开http://localhost/code.php查看网页

由上面的例子可以看出编译式模板运作的套路:

1php文件调用网页模板,并显示。

2htm文件提供网页的大体框架,等待数据来完善网页的具体内容,称为网页模板。

 

之后可以看一下具体语法文件夹中的编译式标签语法 手册

 

会发现编译式模板中有四种“势力”

在include/dedetemplate.class.php中定义的编译引擎类,负责操作编译,它提供负责非自定义标签的编译。

标签翻译需要一些转换规则,一般标签的定义在dedetemplate.class.php中实现,自定义标签则在include/tpllib中。(编译式模板的标签就仅有文件夹里为数不多的几个,可见编译式标签现在还不好用),上面的例子中的SetVar就属于这部分。

待显示的php创建编译引擎类对象,对模板进行编译,在Display时,产生缓存,并显示(include)缓存文件。

htm模板,调用标签,用html的形式写出动态网页的效果,属于被翻译的部分。利用标签封装内部处理。

理论上有了这四种元素,就能写出网页。但由于现在的标签以解析式居多,所以还是以解析式的这四种组分(解析式中同样也有这几种组分)为主框架构造网页(换句话说,现在的编译式模板还派不上用场)。

编译式Display时,只是把编译好,放在cache中的结果给include一下而已。

 

之后就是自定义标签。

具体做法,在include/tpllib中找一个文件来仿制。如plus_ask

root/include/tpllib/plus_hello

复制代码
<?php
if(!defined('DEDEINC')) exit('Request Error!');
/**
 * 动态模板hello标签
 *
 * @version        $Id: plus_ask.php 1 13:58 2010年7月5日Z tianya $
 * @package        DedeCMS.Tpllib
 * @copyright      Copyright (c) 2007 - 2010, DesDev, Inc.
 * @license        http://help.dedecms.com/usersguide/license.html
 * @link           http://www.dedecms.com
 */
 
function plus_hello(&$atts,&$refObj,&$fields)
{
    global $dsql,$_vars;
    
    //给出标签的属性默认参数值列表,以’,’分隔,即使不设置默认参数也要给出属性名
    $attlist = "name=";

    FillAtts($atts,$attlist);
    FillFields($atts,$fields,$refObj);
    extract($atts, EXTR_OVERWRITE);
    
    //返回处理结果,以替换标签
    return '你好!'.$name;
}
?>
复制代码

在刚才的root/code.tpl.htm 加入这个标签

<!—rstype不用再默认参数表中给出,它默认为array,除此以外还能是string -->

{dede:hello name='Erik' rstype='string'/}<br/>

打开http://localhost/code.php查看网页

 

以上是短标签,对于块标签如下

root/include/tpllib/plus_hello

复制代码
<?php
if(!defined('DEDEINC')) exit('Request Error!');
/**
 * 动态模板ask标签
 *
 * @version        $Id: plus_ask.php 1 13:58 2010年7月5日Z tianya $
 * @package        DedeCMS.Tpllib
 * @copyright      Copyright (c) 2007 - 2010, DesDev, Inc.
 * @license        http://help.dedecms.com/usersguide/license.html
 * @link           http://www.dedecms.com
 */
 
function plus_hello(&$atts,&$refObj,&$fields)
{
    global $dsql,$_vars;

    FillAtts($atts,$attlist);
    FillFields($atts,$fields,$refObj);
    extract($atts, EXTR_OVERWRITE);

    //为什么要用这种格式的数组呢?因为,这刚好和数据库表一个个元组的格式对应
    $rearray=array(0=>array('name'=>'Jack','age'=>19),1=>array('name'=>'Mary','age'=>21));
    
    return $rearray;
}
?>
复制代码

在刚才的root/code.tpl.htm 加入这个标签

复制代码
<!—返回array的经常对应于块标签-->

       {dede:hello}

<!—注意,这些InnerText中来自function plus_hello的返回值的属性的标签名一定要用field,name和age这些名字,是和上面返回的数组要对应。-->

              {dede:field.name/} is {dede:field.age/} years old<br/>

       {/dede:hello}
复制代码

编译后的php代码(缓存)会放在root/data/tplcache/中,形如code.tplXXXX.inc,看编译后的代码是调试好方法。

要了解编译式模板的语法最好的方法还是看源文件。推荐看一下root/include/dedetemplate.

class.php和root/include/tpllib/memberlist.php。其中的命名比较清晰,基本都能望文生义,

详见带注释的重要文件。作为dedecms的开发者应该对dedetemplate.class.php的内容很熟悉。

对于memberlist标签,有点像一个控件,用户调用它时不用管它的实现。但是它的作用不是丰富UI,而是提供数据层数据。memberlist标签封装了数据层的实现,而且这个标签专门为member相关的数据表服务。

 

除了上面讲到的4大“势力”外,还有一个势力,视图类。

视图类是包含一个编译引擎对象的类,它封装了编译引擎对象,而且扩展出新的特性。由此得出dedecms网页运行的结构:

 

                     

视图类一般在include/里(不在子文件夹中的部分),如datalistcp.class.php。(因为解析式的视图类也在include/里,要区分两者只要看视图类里包含的引擎是tpl还是dtp。)

 

2解析式模板

由于现在的编译是标签还很少,所以写网站时还是用解析式的。但了解上面编译式的内容并不是无意义的,首先日后dedecms会逐渐向编译式模板过渡。其次,解析式模板和编译式模板用法上差别不大,上面讲的网页运作原理对解析式模板完全适用。而且学习编译式模板的过程中对标签语法会有感性的认识,则学解析式标签时自然水到渠成。

先来写个hello world 程序

root/test.php  封面php

复制代码
<?php
    require_once (dirname(__file__).'/include/common.inc.php');
    //利用解析式模板所需的文件
    require_once (dirname(__file__).'/include/dedetag.class.php');

$dtp=new DedeTagParse();
//设置命名空间,由于下面的标签用tianya命名空间,所以要设置一下。
$dtp->SetNameSpace('tianya');
$dtp->LoadTemplate(dirname(__file__).'\test.tpl.htm ');

foreach ($dtp->CTags as $id=>$tag)
{
    if($tag->GetName()=='my')
        //把id为$id的tag翻译成这是my标签<br/>
        $dtp->Assign($id,'这是my标签<br/>');    
    else if($tag->GetName()=='test')
        $dtp->Assign($id,'这是test标签<br/>');
}

$dtp->Display(); 
?>
复制代码

root/test.tpl.htm 网页模板

    {tianya:my att1=1 att2='2'}
        [field:my/]
    {/tianya:my}
    {tianya:test att1=1 att2='2'}
        [field:test/]
    {/tianya:test}

执行root/test.php

发现和编译式运作套路大致相同,php文件调用网页模板,htm文件提供网页的大体框架。但有两点不同:

1多了foreach对标签的处理。这涉及对标签的处理,本来是解析引擎的事,而且即使不在封面(index)php中自己写标签处理,也能解析好网页模板。那为什么还要拿出来讲?主要因为自定义标签时要用到这些语法。

2InnerText的格式略有不同,但这不重要。

上面的代码就是把第一个标签(my标签)显示为这是my标签<br/>;第二个标签显示为这是test标签<br/>。

上面的代码是怎么办妥的

更改***root/test.php如下

复制代码
<?php

       require_once (dirname(__file__).'/include/common.inc.php');

       //利用解析式模板所需的文件

       require_once (dirname(__file__).'/include/dedetag.class.php');

 

$dtp=new DedeTagParse();

//设置命名空间,由于下面的标签用tianya命名空间,所以要设置一下。

$dtp->SetNameSpace('tianya');

$dtp->LoadTemplate(dirname(__file__).'\test.tpl.htm ');

dump($dtp);   //这是查看计析结果的重要方法

?>
复制代码

可以看到$dtp对象的内部结构,其中有一个DedeTag类的数组CTags,DedeTag类的定义见root/include/ dedetag.class.php。最好不要直接用DedeTag类的字段,而用DedeTag提供的函数。比如用tag1->GetName()而不是用tag1->TagName。花一小段时间就能把DedeTag类看完,这些语法在以后自定义标签时会有用。

再看一个例子

root/test.php

复制代码
<?php
    require_once (dirname(__file__).'/include/common.inc.php');
    require_once (dirname(__file__).'/include/dedetag.class.php');

function lib_my($att1,$att2)
{    
    return '这是my标签<br/>属性值'.$att1.$att2.'<br/>';
}

$dtp=new DedeTagParse();
$dtp->SetNameSpace('tianya');
$dtp->LoadTemplate(dirname(__file__).'\test.htm');

foreach ($dtp->CTags as $id=>$tag)
{
    if($tag->GetName()=='my')    
$dtp->Assign($id , lib_my($tag->GetAtt('att1'),$tag->GetAtt('att2')));
}

$dtp->Display(); 
?>
复制代码

这个例子中多了一个处理函数,lib_my,很容易看懂。经过这个例子,在编译式模板中提到的四大“势力”都出现了。

解析引擎类:DedeTagParse,这个在定义见root/include/ dedetag.class.php,完成了一般的解析,如dede:global等。还支持了自定义标签。DedeTagParse、SetNameSpace、

LoadTemplate就是封面php对它的调用。和dedetemplate.class.php一样,dedetag.class.php很重要,可以继续研究一下。

标签翻译的转换规则:在taglib/里。看arclist.lib,发现提供了一个处理标签,返回html字符串的函数,和lib_my()很想。

待显示的php 和htm模板在之前已经讲过

 

而foreach的那部分则是把标签关联上处理函数,这个功能由DedeTagParse类实现。由于调用比较复杂,而且涉及分析引擎的重要原理—钩子技术,所以记录一下。

调用到的几个函数中DedeTagParse(),SetNameSpace ()只是填一下初始化的值。

LoadTemplate() 一方面读取网页模板代码,另一方面调用ParseTemplet()把网页模板分解标签,属性,底层模板等。

Display()调用echo GetResult()的结果。GetResult()就是由分解好的标签,属性,底层模板等算出结果html。由于dedecms标记满足树形的语法规则(像html一样),所以,计算标签是一个遍历树的过程。至于每个标签的值的计算,就调用了AssignSysTag(),它处理了global等标记。对于自定义标记,通过调用IncludeFile处理(灰色部分尚不能确定),这个函数又通过了复杂的调用,最后调用了include\helpers\channelunit.helper中的MakeOneTag()的函数,这个函数就用到了钩子技术。详见link

讲了一堆东西后,发现解析式引擎与编译式引擎翻译原理的确不同。解析式是在要显示网页时,动态计算出html值,它不需要像编译式引擎一样,引入大量可能根本用不上的文件。但执行速度可能会慢些,但我们也知道解析不一定慢于编译的。

 

再来看看如何处理底层模板([field:my/]等,field是一个关键字,在实际应用中,常常是数据库元组中的字段)

root/test.php

复制代码
<?php
    require_once (dirname(__file__).'/include/common.inc.php');
    require_once (dirname(__file__).'/include/dedetag.class.php');

function lib__zoo($att1,$att2,$inner)
{    
    $reval='这是my标签<br/>属性值'.$att1.$att2.'<br/>';

    $dtp=new DedeTagParse();
    $dtp->SetNameSpace('field','[',']');
    $dtp->LoadSource($inner);
    foreach ($dtp->CTags as $id=>$tag)
    {
        if($tag->GetName()=='name')
            $dtp->Assign($id,'Snoopy');
        else if($tag->GetName()=='animal')
            $dtp->Assign($id,'dog');
    }
    $reval.=$dtp->GetResult().'<br/>';
    return $reval;
}

$dtp=new DedeTagParse();
$dtp->SetNameSpace('tianya');
$dtp->LoadTemplate(dirname(__file__).'\test.htm');

foreach ($dtp->CTags as $id=>$tag)
{
    if($tag->GetName()=='my')
    $dtp->Assign($id,MyFunc($tag->GetAtt('att1'),$tag->GetAtt('att2'),$tag->GetInnerText()));
}

$dtp->Display(); 
?>
复制代码

root/test.tpl.htm

       {tianya:zoo att1=1 att2='2'}

              [field:name/] is a [field:animal/]

       {/tianya:my}

可见,解析底层模板和解析标签是一样的,只不过把底层模板当作是以field为命名空间,‘{’和‘}’为边界的标签而已。

关于自定义标签,可以参照taglib中的例子,如arclist

 

还有一个问题,视图类。include/中以arc开头的文件都是解析引擎的视图类。在下载的cms默认模板中,root/index.php就用了PartView这个视图类,解析了templets/default/index.htm

 

解析式和编译式的不同

1翻译原理不同

2调用语法(模板引擎使用上的不同),InnerText的语法

cache机制不是两种解释的区别,两种解释都用到cache机制

 

以上是解析式标签的运作原理和自定义标签的方法。但学习dedecms应该从网页模板修改与编写开始,应该先掌握好学习标签的语法。

 

下面是两种快速显示网页模板的方法

1 root/index.php 中SetTemplet的文件改成想要的模板。如,dirname(__FILE__) .'/templets

/default/footer.htm'。(要注意保存原来的路径,以备还原)

2或者用下面这个简单的程序段

root/test.php

复制代码
<?php

require_once (dirname(__FILE__) . "/include/common.inc.php");

require_once (dirname(__file__).'/include/dedetag.class.php');

$dtp = new DedeTagParse();

$dtp->SetNameSpace("dede","{","}");

$dtp->LoadTemplet(dirname(__FILE__) .'/templets/default/index.htm');

$dtp->Display();

?>

 

来源:PHPchina

上一篇:特牛的PHP分页 2012-12-24

下一篇:PHP单例模式经典讲解 2012-12-24

Notice: Constant RUNTIME already defined in /srv/html/srccn/news/config.php on line 15 Notice: Constant ROOTDIR already defined in /srv/html/srccn/news/config.php on line 16 Notice: Constant SITEDIR already defined in /srv/html/srccn/news/config.php on line 17 Notice: Constant DATAURL already defined in /srv/html/srccn/news/config.php on line 20 Notice: Constant VERSION already defined in /srv/html/srccn/news/system/kernel.php on line 17 Notice: Constant COREDIR already defined in /srv/html/srccn/news/system/kernel.php on line 18 Fatal error: require(): Cannot redeclare class mysql in /srv/html/srccn/news/system/kernel.php on line 22