Chinaunix首页 | 论坛 | 博客
  • 博客访问: 439638
  • 博文数量: 50
  • 博客积分: 5071
  • 博客等级: 大校
  • 技术积分: 1780
  • 用 户 组: 普通用户
  • 注册时间: 2006-01-20 10:36
文章分类

全部博文(50)

文章存档

2011年(2)

2010年(6)

2009年(12)

2008年(30)

我的朋友

分类:

2009-03-30 15:13:02

使用"模板驱动方式"简化开发 ----smarty插件的使用

关键词: 模板, smarty, 插件, PHP
作  者: 李晓军 (大师兄)
Email:  [email]teacherli@163.com[/email]
MSN:    [email]teacherli@ceua.org[/email]


      相信使用SMARTY开发PHP程序给大家带来了很大的方便, 今天我们来看另一种使用你的程序更加简炼的开发方式----smarty插件技术. 在这节里,我将使用smarty的插件来完成一个新闻页网站首页的示例, 而支撑这个首页的代码只有下面几行代码:
========================================================
[php]
    /**
   * fileName: index.php  
   * introduce: 一个新闻站点的首页
   *
   * @author: 李晓军
   * @email:  [email]teacherli@163.com[/email]
   * @date: 2004-12-22
   * @version 1.0
   * @copyright XXXXXXXX
   *
   */
        include_once("./comm/smarty/mySmarty.class.php");
        
        $smarty = new mySmarty();
        
        $smarty->display("index.html");
?>
[/php]
========================================================
考虑到多数人的实际情况,这里我附带将smarty生成静态页的方法也介绍一下.



(一)、基本知识:

    首先来介绍一下相关的知识:
    1. 什么是smarty?
     smarty是一个使用PHP写出来的模板PHP模板引擎, 是php.net推荐的一个模板系统.
    2. 什么是smarty的插件?
    smarty的插件是指smarty中的plugins, 是一些嵌入模板内的一些功能性控制语句, smarty中的Variable Modifiers(变量调节)实际就是一些内置的插件.
    3. 插件是怎么工作的?
    在smarty模板中使用了插件调用语句时动态的载入, 你可以将你写好的插件放入smarty解压目录中的lib目录下的plugins, 这样在模板中使用这些插件时它将会被自动载入.
    4. 插件有几种类型?
     function, modifier, block, compiler,prefilter, postfilter, outputfilter, resource, insert, 本文中我们只使用function型插件,其它类型的请读者自已练习使用.
    5. 如何命名插件?
     文件名形式: type.name.php, type指的是类型,上边的几种是它的选择范围; name: 自定义的插件名称,本文中使用showNews来命名.
     函数名称: smarty_type_name(), smarty:固定位置的固定名称; type与文件名的type一致, name与文件名中的name一致.
     看一个smarty中自带的例子:
=============================================
文件名:   modifier.cat.php
存放位置: smarty/lib/plugins/
=============================================
[php]
function smarty_modifier_cat($string, $cat)
{
    return $string . $cat;
}
?>

[/php]
=============================================
   这里展示的是一个变量调节器(modifier)的插件, 看它的文件名:modifier. + cat(调节器名称) + ".php", 看它的函数名称:smarty_ + modifier_ + cat(调节器名称).
  

   接下来说说为什么要使用插件来开发.
  大家先来看看这段代码, 这是我以前写的一个教程中的例子:
======================================================
index.php
======================================================
[php]
/*********************************************
*
* 文件名: index.php
* 作 用: 显示实例程序
*
* 作 者: 李晓军
* Email: [email]teacherli@163.com[/email]
*
*********************************************/
include_once("./comm/mySmarty.class.php"); //包含smarty的扩展类文件
include_once("./comm/adodb/adodb.inc.php"); //包含ADODB主执行文件
include_once("./comm/csubstr.inc"); //包含中文截取类

define ("NEWS_NUM", 5); //定义新闻列表显示数目

$smarty = new MySmarty(); //建立smarty实例对象$smarty

$conn = ADONewConnection("mysql"); //初始化ADODB
$conn->Connect("localhost", "root", "", "News"); //连接数据库


//这里将处理国内新闻部分
$strQuery = "SELECT iNewsID AS NewsID, vcNewsTitle AS NewsTitle FROM tb_news WHERE imTypeID = 1 ORDER BY iNewsID DESC";
$rs = &$conn->Execute($strQuery);
$smarty->assign("News_CH", $rs->GetArray(NEWS_NUM));
unset($rs);


//这里处理国际新闻部分
$strQuery = "SELECT iNewsID AS NewsID, vcNewsTitle AS NewsTitle FROM tb_news WHERE imTypeID = 2 ORDER BY iNewsID DESC";
$rs = &$conn->Execute($strQuery);
$smarty->assign("News_IN", $rs->GetArray(NEWS_NUM));
unset($rs);

//这里将处理娱乐新闻部分
$strQuery = "SELECT iNewsID AS NewsID, vcNewsTitle AS NewsTitle FROM tb_news WHERE imTypeID = 3 ORDER BY iNewsID DESC";
$rs = &$conn->Execute($strQuery);
$smarty->assign("News_MU", $rs->GetArray(NEWS_NUM));
unset($rs);

//这里将处理财经新闻部分
$strQuery = "SELECT iNewsID AS NewsID, vcNewsTitle AS NewsTitle FROM tb_news WHERE imTypeID = 4 ORDER BY iNewsID DESC";
$rs = &$conn->Execute($strQuery);
$smarty->assign("News_CJ", $rs->GetArray(NEWS_NUM));
unset($rs);

//这里将处理房产新闻部分
$strQuery = "SELECT iNewsID AS NewsID, vcNewsTitle AS NewsTitle FROM tb_news WHERE imTypeID = 5 ORDER BY iNewsID DESC";
$rs = &$conn->Execute($strQuery);
$smarty->assign("News_FC", $rs->GetArray(NEWS_NUM));
unset($rs);

//这里将处理体育新闻部分
$strQuery = "SELECT iNewsID AS NewsID, vcNewsTitle AS NewsTitle FROM tb_news WHERE imTypeID = 6 ORDER BY iNewsID DESC";
$rs = &$conn->Execute($strQuery);
$smarty->assign("News_TY", $rs->GetArray(NEWS_NUM));
unset($rs);

//这里将处理教育新闻部分
$strQuery = "SELECT iNewsID AS NewsID, vcNewsTitle AS NewsTitle FROM tb_news WHERE imTypeID = 7 ORDER BY iNewsID DESC";
$rs = &$conn->Execute($strQuery);
$smarty->assign("News_JY", $rs->GetArray(NEWS_NUM));
unset($rs);

$conn->close();

//编译并显示位于./templates下的index.tpl模板
$smarty->display("index.tpl");
?>
[/php]
==========================================
   请大家看仔细, 在index.php中大量的存在类似这样的代码:
=====================================================
[php]

//这里将处理教育新闻部分
$strQuery = "SELECT iNewsID AS NewsID, vcNewsTitle AS NewsTitle FROM tb_news WHERE imTypeID = 7 ORDER BY iNewsID DESC";
$rs = &$conn->Execute($strQuery);
$smarty->assign("News_JY", $rs->GetArray(NEWS_NUM));
unset($rs);

[/php]
======================='==============================
而类似的代码中只能少部分的参数不同,基本处理的方法都一样, 这个时候有的聪明的人就会想到使用函数来完成这样的功能, 那么就可能会使用一些像下面这样形式的函数:
   function assignNews($typeID, $listName);
当然,这也是一种解决办法, 但是我们今天不使用这种办法, 而是将这种函数写成插件, 置入模板中使用一个控制语句来完成同样的功能, 使index.php最简化,将以前复杂的工作交给美工做:)
   完成后的的index.php就像下面这样:

=====================================================
[php]
  /*********************************************
  *
  * 文件名: index.php
  * 作 用: 显示实例程序
  *
  * 作 者: 李晓军
  * Email: [email]teacherli@163.com[/email]
  *
  *********************************************/
  $smarty = new mySmarty();
  $smarty->display("index.tpl");
?>
[/php]
======================================================
而付出的代价仅在是模板index.tpl中在每个section语句前写一条控制语句: <{showNewsList size=xx length=yy type=kk}>, 是不是要简单很多啊?  好了, 让我们开始动手.


(二) 开始动手:
    先来看看我们的数据库结构:

主新闻表:
CREATE TABLE tb_eps_news (
  i_id int(11) NOT NULL auto_increment,
  im_type_id int(11) default NULL,
  vc_title varchar(50) NOT NULL default '',
  vc_sub_title varchar(50) default NULL,
  vc_author varchar(30) default NULL,
  vc_from varchar(100) default NULL,
  vc_main_pic varchar(100) default NULL,
  lt_context longtext,
  lt_summary longtext,
  vc_keyword varchar(200) default NULL,
  dt_add_date date default NULL,
  i_state int(11) default NULL,
  vc_file_name varchar(60) NOT NULL default '',
  PRIMARY KEY  (i_id),
  KEY im_type (im_type_id),
  KEY vc_keyword (vc_keyword)
) TYPE=MyISAM;

新闻类别表:
CREATE TABLE tb_eps_news_type (
  i_type_id int(11) NOT NULL auto_increment,
  vc_type_name varchar(20) NOT NULL default '',
  vc_parent_id int(11) default '0',
  vc_type_caption varchar(20) NOT NULL default '',
  PRIMARY KEY  (i_type_id)
) TYPE=MyISAM;
   

    <{showNewsList size=xx length=yy type=kk}>这样的控制语句,就是我们今天要讲的重点, 在这个例中中,它以smarty的function插件来实际, 上面我们已经说明过插件的命名方式, 先来看看这个插件的源码:
==============================================================
文件名: function.showNews.php
==============================================================
[php]
/*********************************************
*
* FileName: function.showNews.php
* function: 显示新闻
*
* @Author:  李晓军
* @Email:   [email]teacherli@163.com[/email]
* @Date:    2004-12-22
*
* 使用说明:
* <{showNews type='xxx' length=x size=y listName='yyy'} >
* @param string type: 要显示新闻的类型 length
* @param int length: 每条新闻的长度
* @param int size:   要显示多少条新闻
* @param string listName: 模板循环section的名称
*
*   模板中可用到的变量:
*   @param ID 新闻编号
*   @param title 新闻标题
*   @param addDate 添加时间
*   @param typeName 所属类别名称
*   @param fileName 链接地址
*
* *******************************************/
require_once ("./comm/smarty/csubstr.inc"); //对中文字数进行控制

function smarty_function_showNews($params, &$smarty)
{  
   extract($params);
  
   $type = (null != $type) ? $type : "1"; //默认提取新闻类型为1
   $size = (null != $size) ? $size : 5; //默认显示新闻条数为5条
   $length = (null != $length) ? $length : 20; //默认每行显示20个汉字
   $listName = (null != $listName) ? $listName : "newsList"; //默认的section名称
   
   $strQuery =  "SELECT i_id AS ID, " .
                "vc_title AS title, " .
                "dt_add_date AS addDate, " .
                "vc_file_name AS fileName," .
                "vc_type_caption AS typeName  " .
                "FROM tb_eps_news, tb_eps_news_type " .
                "WHERE tb_eps_news_type.i_type_id = tb_eps_news.im_type_id " .
                "AND tb_eps_news_type.vc_type_name='$type' " .
                "ORDER BY i_id DESC ";
   
     $rs = $smarty->conn->SelectLimit($strQuery, $size);
     while ($row = $rs->FetchRow())
     {
         $newsList[] =    array("ID"     => $row['ID'],
                                "title"  => csubstr($row['title'], 0, $length),
                                "addDate"=> $row['addDate'],
                                "fileName" => $row['fileName'],
                                "typeName" => $row['typeName']);
     }

   $smarty->assign($listName, $newsList);
   
   unset($newsList);
}
?>
[/php]
==============================================================
我先来讲讲这个源码:
   
   1. csubstr.inc.php:这个文件我在以前的教程中多次用到,使用它来进行中文字串的截取
   2. smarty_function_showNews($params, &$smarty): 函数的命名使用smarty中对插件的规定, 参数也是smarty插件所规定的函数, $params为一个关联数组, &$smarty为调用当前插件的smarty类对象的引用。
   3. extract($params): 将$params关联数组导出符号表, 就是将它包含的参数导出, 这个程序中我要用到4个参数来完成对插件的调用,它们分别为:
    type='xxx': 指新闻类型, 在数据库中使用一个数据字段来表示
    length=x:    每条新闻的长度, 这里就使用了csubstr.inc.php中的函数
    size=y:      显示多少长新闻, 由它来控制从数据库里提取多少长新闻,这里为简单, 所有的新闻都使用降序排列。
    listName='yyy': 模板中使用的{section name=loop loop=$yyy}中loop的值,实际上指的就是一个循环块的名称
   4. ?:三元运算符: 这几行用来完成对在没有指定参数的情况下的默认值
   5. $strQuery: 一条SQL查询语句, 用来完成对指定新闻类型的指定条数的查询
   6. $smarty->conn: 这个例子中,我们对smarty对象进行了重新封装,给它加了一个成员变量为$conn, 然后在类进行初始化时让它指向一个ADODB类对象,所以才会有后边的SelectLimit()函数的调用, 等会讲到对smarty对象的重新封装时就会看到.
   7. while()语句:这段语句用来完成对$newsList二维数组进行初始化,所使用的方法也是smarty上的标准方法,大家查看相关的ADODB操作资料.
   8. $smarty->assign(): 这么眼熟?对了,就是smarty程序文件中的assign(), 实际这里的这个函数在smarty进行模板解析时遇到:       <{showNews type='xxx' length=x size=y listName='yyy'}>这样的调用时就会在当前插入这段函数
   
   其实这里除了对参数进行分析外与以前在程序文件中的写法没什么两样, 只不过是Smarty使用另一种更加简单的办法来实现罢了。
   
   9. 本文件存放在: smarty/lib/plugins/

   刚才说到我们对smarty进行得新封装, 现在我们来看看封装的源码,其实很简单:
============================================================================
mySmarty.php
============================================================================
[php]
/*********************************************
*
* 文件名: mySmarty.php
* 作  用: Smarty的子类
* 作  者: 李晓军
* Email:  [email]teacherli@163.com[/email]
*
*********************************************/
require_once ("./comm/smarty/Smarty.class.php");
require_once ("./comm/smarty/config.class.php");
require_once ("./comm/adodb/adodb.inc.php");

class MySmarty extends Smarty
{
        public $conn;
        
        public function __construct()
        {
                $this->template_dir  =   "./templates";           //设置模板目录
                $this->compile_dir   =   "./templates_c";      //设置编译目录
                $this->cache_dir     =   "./cache";          //设置缓存目录
                $this->cache_lifetime  =   60 * 60 * 24;           //设置缓存时间
                $this->caching         =  false;            //这里是调试时设为false,发布时请使用true
                $this->left_delimiter  =  "<{";                //设置左边界符
                $this->right_delimiter =  "}>";                 //设置右边界符
               
                $this->debugging = false;
               
               
                $this->conn = &ADONewConnection(DB_TYPE);
                $this->conn->debug = false;     

                $this->conn->PConnect (DB_HOST, DB_USER, DB_PASSWD, DB_NAME);
                $this->conn->SetFetchMode(ADODB_FETCH_ASSOC);  
                if(null === $this->conn)
                {
                        die("不能连接到数据库服务器:
dbHost: "
                        . DB_HOST . "
用户名: " . DB_USER . "密码: " . "******" . "
数据库名称: " . DB_NAME);
                }
        }

        //设置模板目录
        public function setTemplate_dir($dir = "./templates")
        {
                $this->template_dir = $dir;
        }

                  
        /**
        * 生成静态页
        * @param string $tplName
        * @param string $typeName
        *
        */
         public function makeHtml($tplName, $typeName)
         {
                 $htmlContext = $this->fetch($tplName);
                 if ($fp = fopen($typeName .".html", "w"))
                 {
                        if (fwrite($fp, $htmlContext))
                        {
                                fclose($fp);
                        }else
                        {
                                fclose($fp);
                                die("对不起,无法写入指定文件: " . $typeName . ".html");
                        }      
                 }else
                 {
                        die("对不起,无法打开指定文件: " . $typeName . ".html");
                 }               
         }
         
         /**
          * 析构函数
          */
         public function __destruct()
         {
                unset($this->conn);
         }
}
?>
[/php]
对代码做一个简单的说明:
   
    1. config.class.php: 对于这个文件, 它将提取数据库连接信息, DB_HOST, DB_USER, DB_PASSWD, DB_NAME在这个文件中定义.
    2. public $conn:     本类的成员变量, 为一个ADODB对象, 在插件中我们要使用
    3. function __construct(): php5的构造函数, 在这里完成对smarty类的初始化,包括左右界定符及缓存时间等, 另一个就是初始化$conn, 让它指向一个ADODB的实例.
    4. makeHtml($tplName, $typeName): 这一句就是使用smarty生成静态文件的函数了,其实很简单,这里要知道就是只有一点: 在smarty中,要取页面将要输出的内容的字串,使用fetch($tplName)来取取得, 将页面输出的内容取到了,你再将它写入一个以.html结尾的文件中, 这就是生成静态页. :)
    5. function __destruct(): 析构函数, 释放$conn.
   

    怎么样? 看明白了吗? 完成这些工作,剩下的事情我们就要体验"模板驱动"了!

   因为我们要用到一些数据,现在先来看看tb_eps_news_type中的数据:
[php]
+-----------+----------------+--------------+-----------------+
| i_type_id | vc_type_name   | vc_parent_id | vc_type_caption |
+-----------+----------------+--------------+-----------------+
|         1 | calling        |            0 | 行业信息        |
|         2 | exhibition     |            0 | 展会信息        |
|         3 | technology     |            0 | 技术专栏        |
|         4 | product        |            0 | 产品展示        |
|         5 | market         |            0 | 市场行情        |
|         6 | enterprise     |            0 | 企业名录        |
|         7 | printing       |            0 | 印刷世界        |
|         8 | provide        |            0 | 供求信息        |
+-----------+----------------+--------------+-----------------+
[/php]
首先来看看index.php:

[php]
==============================================================
index.php
=============================================================
  /**
   * fileName: index.php  
   * introduce:
   *
   * @author: 李晓军
   * @email:  [email]teacherli@163.com[/email]
   * @date: 2004-12-22
   * @version 1.0
   * @copyright XXXX
   *
   */
        include_once("./comm/smarty/mySmarty.class.php");

        $smarty = new mySmarty();

        $smarty->display("test.html");
?>
==============================================================
[/php]
test.html就是我们的模板文件, 在程序文件中没有任何其它的处理语句.

再来看看模板文件:
[php]
==============================================================
test.html
==============================================================



新闻首页




这是行业信息新闻列表


              <{showNews type="calling" length="10" size="20" listName="callingList"}>
              <{section name=loop loop=$callingList}>
              
               
              
              <{/section}>
<{$callingList[loop].title}>


这是展会信息新闻列表


              <{showNews type="exhibition" length="5" size="10" listName="exhibitionList"}>
              <{section name=loop loop=$exhibitionList}>
              
               
              
              <{/section}>
<{$exhibitionList[loop].title}>


这是技术专栏新闻列表


              <{showNews type="technology" length="25" size="30" listName="technologyList"}>
              <{section name=loop loop=$technologyList}>
              
               
              
              <{/section}>
<{$technologyList[loop].title}>



==============================================================
[/php]
好了,执行index.php, 页面就会按我们想要的形式出现, 而我们所做的,不是费力地在index.php使用assign,而是将这种方法转变到了模板中. 在模板中,我们会看到这样的一行控制语句:
   <{showNews type="calling" length="10" size="20" listName="callingList"}>
   这样的形式就是我们调用插件的形式, 这一句的意思是: 调用showNews插件, 要显示的新闻类型为calling, 每条新闻的标题长度是10, 要显示的的个数为20条, 要处理的循环块是名称为$callingList的section, 这里的名为$callingList的section在下面进行了定义:
   <{section name=loop loop=$callingList}>
   
在这里要注意的就是listName的值一定要与section中的loop对应, 要不然不会出任何结果.

看出什么好处来了吗?程序部分我们很简单, 没有任何的数据库读取语句, 所有的工作都在模板中进行, 而在模板中进行的也只不过是新加一条控制语句罢了,这就是所谓的"模板驱动".

上面的mySmarty中我还有一个makeHtml函数, 就是一个生成静态页的函数, 怎么用呢? 看这一段:
[php]
==============================================================
index.php
==============================================================
  /**
   * fileName: index.php  
   * introduce:
   *
   * @author: 李晓军
   * @email:  [email]teacherli@163.com[/email]
   * @date: 2004-12-22
   * @version 1.0
   * @copyright XXXX
   *
   */
        include_once("./comm/smarty/mySmarty.class.php");

        $smarty = new mySmarty();

        $smarty->makehtml("test.html", "index");

        header("location:test.html");
?>
==============================================================
[/php]
将index.php的输出写入静态文件test.html, 然后重定向到test.html, 就完成了动态转静态的动作.


(三): 一些可能的用途

    1. 看过几个CMS, 它们基本上都使用模板技术进行页面显示, 虽然实现的技术不同,但基本的原理是相同的,所以在这里我就使用了smarty的插件技术来实现同样的功能, 这将在CMS中有重要的的用途.
   
    2. 简化程序代码输入量.

    从于效率上讲, smarty在介绍自已的插件时这样介绍:
        Plugins are always loaded on demand. Only the specific modifiers, functions, resources, etc invoked in the templates scripts will be loaded. Moreover, each plugin is loaded only once, even if you have several different instances of Smarty running within the same request.

(四): 后记
    以上仅仅是一个示例, 你在开发自已的插件时可以将其中的参数换为你自己的参数, 将其中的SQL语句换为适合自己的SQL语句, 这样,就形成适合自己网站情况的插件.

    最后祝大家开始快乐的Smarty插件之旅!!
阅读(928) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~