smarty程序應用范例:留言簿(guestbook)第一節
這是一個使用了smarty的php應用程序。目的是就如何在應用程序中使用smarty,以及如何分離你的“表現”(presentation)作一個示范。這個范例相當簡單,但包含了一個完整的迷你框架(mini-framework)用于快速簡單地生成一個smarty驅動的應用程序。一旦你理解了將“表現”分離的觀念,你也許會把它用在一些程序開發上。如果真是那樣,你需要根據你自己的判斷在你的程序中使用以下代碼,并為此負責。
你可以從這里下載這個范例的源代碼。http://www.phpinsider.com/php/code/guestbook/guestbook-1.0.tar.gz
你可以在這里先看看這個范例的演示。http://www.phpinsider.com/php/code/guestbook/
這篇文章不指導也不涉及如何安裝apache,pear和mysql。請確認你已經知道這些事情或者有可以直接使用的相關軟件環境。如果你的運行環境與范例所示有差別,你需要在給出的代碼中進行相應的調整。
接下來我們會開發一個沒有管理員界面的留言簿程序,用戶可以瀏覽也可以留言。會涉及到一些與smarty相關的編程知識,比如表單和數據庫數據的讀取與顯示。
這個范例是smarty安裝指南中留言簿程序的擴展,所以我們是基于此之上進行開發的。以下是我們這個程序用到的文件:
guestbook app files/directories:
/web/www.example.com/docs/
/web/www.example.com/docs/guestbook/
/web/www.example.com/docs/guestbook/index.php
/web/www.example.com/smarty/guestbook/
/web/www.example.com/smarty/guestbook/templates/
/web/www.example.com/smarty/guestbook/templates_c/
/web/www.example.com/smarty/guestbook/configs/
/web/www.example.com/smarty/guestbook/cache/
/web/www.example.com/smarty/guestbook/libs/
/web/www.example.com/smarty/guestbook/libs/guestbook_setup.php
/web/www.example.com/smarty/guestbook/libs/guestbook.lib.php
/web/www.example.com/smarty/guestbook/libs/sql.lib.php
下面,我們一個一個地解釋這些文件的用處:
/web/www.example.com/docs/
/docs/ 是我們web服務器的根目錄(apache httpd.conf中的documentroot)。
/web/www.example.com/docs/guestbook/
/guestbook/ 是能被瀏覽器訪問的一個相對于根目錄的下級目錄,存放著我們的程序。
/web/www.example.com/docs/guestbook/index.php
index.php 是我們程序的“大門”,web瀏覽器將通過http://www.example.com/guestbook/index.php訪問到這個腳本文件。
/web/www.example.com/smarty/guestbook/
這是存放我們這個程序(實現邏輯的)所有腳本文件的目錄,這些腳本文件不一定要存放在服務器根目錄下。是否將所有腳本文件都存放在服務器的根目錄下完全隨你所愿,但是這里我們只把允許web瀏覽器直接訪問的頁面文件放在web服務器的根目錄下。你可以使用apache的“.htaccess”方法或其他web服務器軟件的方法禁止web瀏覽器對存放在根目錄下的這些(不宜讓web瀏覽器直接訪問的)程序腳本文件的直接訪問。
/web/www.example.com/smarty/guestbook/templates/
這里存放我們的smarty模板文件。
/web/www.example.com/smarty/guestbook/templates_c/
this is where smarty places its compiled template files. if you installed this correctly, the web server user running php has write access here. for most intents and purposes you can just ignore this directory. 這里存放smarty編譯過的模板文件。如果你安裝正確,運行php的web服務器對這里有寫權限。出于一些偷懶的目的你可以忽略這個目錄。(瞎翻的,用我自己的話講:web服務器要具有這個目錄的寫權限,否則不能正確安裝。如果不想傷腦筋,就忽略它吧。)
/web/www.example.com/smarty/guestbook/configs/
用于存放我們程序的設置文件。設置文件包含著你對來自模板或者程序的訪問權限的設置信息。它們不是php腳本文件,而是一些可以被smarty的設置文件解析器解析的文本文件。
/web/www.example.com/smarty/guestbook/cache/
用于存放smarty的緩存文件。這個目錄僅僅當smarty的緩存功能被打開時才有用。如果你正確安裝了,運行php的web服務器對這里有寫權限。就像/templates_c/目錄一樣,也可以被忽略。(同/templates_c/目錄的翻譯)
/web/www.example.com/smarty/guestbook/libs/
/libs/ 我們將把程序的主要腳本文件存放這里。
/web/www.example.com/smarty/guestbook/libs/guestbook_setup.php
guestbook_setup.php 我們在該腳本文件中存放一些程序的初始化信息。
/web/www.example.com/smarty/guestbook/libs/guestbook.lib.php
guestbook.lib.php 我們在該腳本文件中存放大部分程序的實現邏輯。
/web/www.example.com/smarty/guestbook/libs/sql.lib.php
sql.lib.php 我們在該腳本文件中存放程序的數據庫訪問邏輯。
smarty程序應用范例:留言簿(guestbook)第二節
我們將從“index.php”腳本文件開始留言簿程序的編寫歷程,它將直接被web瀏覽器訪問,所以說是我們這個程序的“大門”。
/web/www.example.com/docs/guestbook/index.php
<?php/*** project: guestbook sample smarty application* author: monte ohrt <monte [at] ohrt [dot] com>* date: march 14th, 2005* file: index.php* version: 1.0*/// define our application directorydefine('guestbook_dir', '/web/www.example.com/smarty/guestbook/');// define smarty lib directorydefine('smarty_dir', '/usr/local/lib/php/smarty/');// include the setup scriptinclude(guestbook_dir . 'libs/guestbook_setup.php');// create guestbook object$guestbook =& new guestbook;// set the current action$_action = isset($_request['action']) ? $_r
equest['action'] : 'view';switch($_action) { case 'add': // adding a guestbook entry $guestbook->displayform(); break; case 'submit': // submitting a guestbook entry $guestbook->mungeformdata($_post); if($guestbook->isvalidform($_post)) { $guestbook->addentry($_post); $guestbook->displaybook($guestbook->getentries()); } else { $guestbook->displayform($_post); } break; case 'view': default: // viewing the guestbook $guestbook->displaybook($guestbook->getentries()); break; }?>
“index.php”扮演著整個程序的控制者這個角色。它掌控著所有來自web瀏覽器的訪問請求,并指導程序發生些什么相應的動作。它定義了程序目錄,包括程序的安裝腳本,以及根據全局變量$_request所定義的action值,并指導程序做出相應的動作。
這里有三個基本的動作設置(actions):
“添加”當用戶往留言簿里寫內容時;
“提交”當用戶寫完內容提交時;
“瀏覽”當用戶瀏覽留言簿時。
缺省情況是“瀏覽”。
/web/www.example.com/smarty/guestbook/libs/guestbook_setup.php
<?php/*** project: guestbook sample smarty application* author: monte ohrt <monte [at] ohrt [dot] com>* date: march 14th, 2005* file: guestbook_setup.php* version: 1.0*/require(guestbook_dir . 'libs/sql.lib.php');require(guestbook_dir . 'libs/guestbook.lib.php');require(smarty_dir . 'smarty.class.php');require('db.php'); // pear db// database configurationclass guestbook_sql extends sql { function guestbook_sql() { // dbtype://user:[email protected]/dbname $dsn = "mysql://guestbook:[email protected]/guestbook"; $this->connect($dsn); } }// smarty configurationclass guestbook_smarty extends smarty { function guestbook_smarty() { $this->template_dir = guestbook_dir . 'templates'; $this->compile_dir = guestbook_dir . 'templates_c'; $this->config_dir = guestbook_dir . 'configs'; $this->cache_dir = guestbook_dir . 'cache'; }} ?>
我們通過“guestbook_setup.php”進行一些基本的程序運行環境設置,比如設置程序的后臺數據庫和模板文件位置。我們使用pear的pear::db庫,請確認能夠通過你的php.ini中的“include_path”設置訪問“db.php”腳本文件,或者干脆使用“db.php”的絕對路徑。我們用mysql作為程序的后臺數據庫,在這里書寫恰當的“dsn”信息以便使用你自己的mysql數據庫。
注意:如果運行中你得到一個類似“call to undefined function: query()”樣的錯誤,說明“$dsn”不正確,請檢查“$dsn”是否正確,并測試是否數據庫已經連接上了。
我們需要安裝一個基本的數據庫結構。接下來這個命令行腳本會把我們的數據表導入mysql數據庫中。
mysql < guestbook.sql
注意,其中的“grant ...”語句修改了數據庫的用戶權限設置。
guestbook.sql
create database guestbook;
connect guestbook;
create table guestbook (
id int(11) not null auto_increment,
name varchar(255) not null default '',
entrydate datetime not null default '0000-00-00 00:00:00',
comment text not null,
primary key (id),
key entrydate (entrydate)
) type=myisam;
grant all on guestbook.* to [email protected] identified by 'foobar';
smarty程序應用范例:留言簿(guestbook)第三節
/web/www.example.com/smarty/guestbook/libs/sql.lib.php
<?php/*** project: guestbook sample smarty application* author: monte ohrt <monte [at] ohrt [dot] com>* date: march 14th, 2005* file: sql.lib.php* version: 1.0*/// define the query typesdefine('sql_none', 1);define('sql_all', 2);define('sql_init', 3);// define the query formatsdefine('sql_assoc', 1);define('sql_index', 2);class sql { var $db = null; var $result = null; var $error = null; var $record = null; /** * class constructor */ function sql() { } /** * connect to the database * * @param string $dsn the data source name */ function connect($dsn) { $this->db = db::connect($dsn); if(db::iserror($this->db)) { $this->error = $this->db->getmessage(); return false; } return true; } /** * disconnect from the database */ function disconnect() { $this->db->disconnect(); } /** * query the database * * @param string $query the sql query * @param string $type the type of query * @param string $format the query format */ function query($query, $type = sql_none, $format = sql_index) { $this->record = array(); $_data = array(); // determine fetch mode (index or associative) $_fetchmode = ($format == sql_assoc) ? db_fetchmode_assoc
: null; $this->result = $this->db->query($query); if (db::iserror($this->result)) { $this->error = $this->result->getmessage(); return false; } switch ($type) { case sql_all: // get all the records while($_row = $this->result->fetchrow($_fetchmode)) { $_data[] = $_row; } $this->result->free(); $this->record = $_data; break; case sql_init: // get the first record $this->record = $this->result->fetchrow($_fetchmode); break; case sql_none: default: // records will be looped over with next() break; } return true; } /** * connect to the database * * @param string $format the query format */ function next($format = sql_index) { // fetch mode (index or associative) $_fetchmode = ($format == sql_assoc) ? db_fetchmode_assoc
: null; if ($this->record = $this->result->fetchrow($_fetchmode)) { return true; } else { $this->result->free(); return false; } } }?>
sql.lib.php 是我們基于pear::db的數據庫操作類的集合。它有助于盡可能地簡化程序的數據庫操作語法和代碼。你可以拷貝以上代碼,而不用過分擔心是不是能理解它們,除非你覺得一定要。
接下來是相關參數的速成示例(crash course):
$guestbook->sql->query("select * from guestbook", sql_all);
print_r($guestbook->sql->record);
輸出結果:
array
(
[0] => array
(
[0] => 1
[1] => monte
[2] => 2005-03-12 17:23:32
[3] => test entry 1
)
[1] => array
(
[0] => 2
[1] => monte
[2] => 2005-03-12 17:23:33
[3] => test entry 2
)
[2] => array
(
[0] => 3
[1] => monte
[2] => 2005-03-12 17:23:35
[3] => test entry 3
)
)
整個留言簿的內容都顯示出來了?!皊ql_all”會得到所有的查詢記錄。
$guestbook->sql->query("select * from guestbook");
while($guestbook->sql->next()) {
print_r($guestbook->sql->record);
}
輸出結果:
array
(
[0] => 1
[1] => monte
[2] => 2005-03-12 17:23:32
[3] => test entry 1
)
array
(
[0] => 2
[1] => monte
[2] => 2005-03-12 17:23:33
[3] => test entry 2
)
array
(
[0] => 3
[1] => monte
[2] => 2005-03-12 17:23:35
[3] => test entry 3
)
使用循環的方式一個一個地顯示所有記錄。如果沒有設置query()的第二個參數,那么得到的數據庫記錄結果會被next()從頭到尾遍歷。
$guestbook->sql->query("select * from guestbook", sql_init);
print_r($guestbook->sql->record);
輸出結果:
array
(
[0] => 1
[1] => monte
[2] => 2005-03-12 17:23:32
[3] => test entry 1
)
輸出結果僅僅是一條數據庫記錄(第一條記錄)。 “sql_init”只得到一條記錄。
$guestbook->sql->query("select * from guestbook", sql_init, sql_assoc);
print_r($guestbook->sql->record);
輸出結果:
array
(
[id] => 1
[name] => monte
[entrydate] => 2005-03-12 17:23:32
[comment] => test entry 1
)
把“sql_assoc”作為第三參數傳遞給query()會使返回的結果是一個聯合數組,形如:fieldname => value。
$guestbook->sql->query("select * from guestbook");
while($guestbook->sql->next(sql_assoc)) {
print_r($guestbook->sql->record);
}
輸出結果:
array
(
[id] => 1
[name] => monte
[entrydate] => 2005-03-12 17:23:32
[comment] => test entry 1
)
array
(
[id] => 2
[name] => monte
[entrydate] => 2005-03-12 17:23:33
[comment] => test entry 2
)
array
(
[id] => 3
[name] => monte
[entrydate] => 2005-03-12 17:23:35
[comment] => test entry 3
)
把“sql_assoc”作為參數傳遞給next()也會使返回的結果是一個聯合數組。
smarty程序應用范例:留言簿(guestbook)第四節
/web/www.example.com/smarty/guestbook/libs/guestbook.lib.php
<?php/*** project: guestbook sample smarty application* author: monte ohrt <monte [at] ohrt [dot] com>* date: march 14th, 2005* file: guestbook.lib.php* version: 1.0*//*** guestbook application library**/class guestbook { // database object var $sql = null; // smarty template object var $tpl = null; // error messages var $error = null; /** * class constructor */ function guestbook() { // instantiate the sql object $this->sql =& new guestbook_sql; // instantiate the template object $this->tpl =& new guestbook_smarty; } /** * display the guestbook entry form * * @param array $formvars the form variables */ function displayform($formvars = array()) { // assign the form vars $this->tpl->assign('post',$formvars); // assign error message $this->tpl->assign('error', $this->error); $this->tpl->display('guestbook_form.tpl'); } /** * fix up form data if necessary * * @param array $formvars the form variables */ function mungeformdata(&$formvars) { // trim off excess whitespace $formvars['name'] = trim($formvars['name']); $formvars['comment'] = trim($formvars['comment']); } /** * test if form information is valid * * @param array $formvars the form variables */ function isvalidform($formvars) { // reset error message $this->error = null; // test if "name" is empty if(strlen($formvars['name']) == 0) { $this->error = 'name_empty'; return false; } // test if "comment" is empty if(strlen($formvars['comment']) == 0) { $this->error = 'comment_empty'; return false; } // form passed validation return true; } /** * add a new guestbook entry * * @param array $formvars the form variables */ function addentry($formvars) { $_query = sprintf( "insert into guestbook values(0,'%s',now(),'%s')", mysql_escape_string($formvars['name']), mysql_escape_string($formvars['comment']) ); return $this->sql->query($_query); } /** * get the guestbook entries */ function getentries() { $this->sql->query( "select * from guestbook order by entrydate desc", sql_all, sql_assoc ); return $this->sql->record; } /** * display the guestbook * * @param array $data the guestbook data */ function displaybook($data = array()) { $this->tpl->assign('data', $data); $this->tpl->display('guestbook.tpl'); }}?>
guestbook.lib.php 是我們這個程序的應用類。它包含了整個程序的實現邏輯。讓我們看看每一個方法。
類方法: guestbook()
/**
* class constructor
*/
function guestbook() {
// instantiate the sql object
$this->sql =& new guestbook_sql;
// instantiate the template object
$this->tpl =& new guestbook_smarty;
}
構造函數。每次我們使用這個留言簿對象時都執行。它把sql語句和smarty對象作為自己的屬性,我們以后可以通過這個對象的方法來訪問和使用它們(sql語句和smarty對象)。
類方法: displayform()
/**
* display the guestbook entry form
*
* @param array $formvars the form variables
*/
function displayform($formvars = array()) {
// assign the form vars
$this->tpl->assign('post',$formvars);
// assign error message
$this->tpl->assign('error', $this->error);
$this->tpl->display('guestbook_form.tpl');
}
displayform() 該方法用于顯示留言書寫表單。它指派了模板文件中留言書寫表單的變量和驗證表單時的出錯提示,然后把這個表單顯示出來。
類方法: mungeformdata()
/**
* fix up form data if necessary
*
* @param array $formvars the form variables
*/
function mungeformdata(&$formvars) {
// trim off excess whitespace
$formvars['name'] = trim($formvars['name']);
$formvars['comment'] = trim($formvars['comment']);
}
mungeformdata() 該方法刪掉來自表單輸入內容開頭和結尾的空白部分。這個方法在驗證表單輸入內容時最先調用。注意,表單信息是通過引用的辦法傳入本方法的,所以任何改變都會導致原始的數組內容(表單內容)發生改變。
類方法: isvalidform()
/**
* test if form information is valid
*
* @param array $formvars the form variables
*/
function isvalidform($formvars) {
// reset error message
$this->error = null;
// test if "name" is empty
if(strlen($formvars['name']) == 0) {
$this->error = 'name_empty';
return false;
}
// test if "comment" is empty
if(strlen($formvars['comment']) == 0) {
$this->error = 'comment_empty';
return false;
}
// form passed validation
return true;
}
isvalidform() 該方法驗證表單的輸入。這里僅僅簡單地驗證表單的‘name’和‘comment’控件是否為空。如果是空的,對應的出錯代碼將被指派為本類錯誤屬性的值。(這些錯誤代碼接下來將被模板文件使用用以顯示對應的錯誤提示。)
類方法: addentry()
/**
* add a new guestbook entry
*
* @param array $formvars the form variables
*/
function addentry($formvars) {
$_query = sprintf(
"insert into guestbook values(0,'%s',now(),'%s')",
mysql_escape_string($formvars['name']),
mysql_escape_string($formvars['comment'])
);
return $this->sql->query($_query);
}
addentry 該方法將向數據庫中插入一條新的留言簿條目。注意,插入數據庫的值已經進行必要操作,以避免sql語法沖突和注入攻擊。
類方法: getentries()
/**
* get the guestbook entries
*/
function getentries() {
$this->sql->query(
"select * from guestbook order by entrydate",
sql_all,
sql_assoc
);
return $this->sql->record;
}
getentries() 該方法將以“field => value”(效果同使用sql_assoc參數)的格式讀出數據庫中所有的留言簿條目。
類方法: displaybook()
/**
* display the guestbook
*
* @param array $data the guestbook data
*/
function displaybook($data = array()) {
$this->tpl->assign('data', $data);
$this->tpl->display('guestbook.tpl');
}
displaybook() 該方法將顯示出留言簿的條目。數組$data即存儲留言簿條目的數組,將用來指派給模板文件并在模板文件中顯示出來。
smarty程序應用范例:留言簿(guestbook)第五節
我們這個留言簿程序有兩個模板文件,一個用來顯示留言一個用來書寫留言。
/web/www.example.com/smarty/guestbook/templates/guestbook.tpl
{* smarty *}
<table border="0" width="300">
<tr>
<th colspan="2" bgcolor="#d1d1d1">guestbook entries (<a href="{$script_name}?action=add">add</a>)</th>
</tr>
{foreach from=$data item="entry"}
<tr bgcolor="{cycle values="#dedede,#eeeeee" advance=false}">
<td>{$entry.name|escape}</td>
<td align="right">{$entry.entrydate|date_format:"%e %b, %y %h:%m:%s"}</td>
</tr>
<tr>
<td colspan="2" bgcolor="{cycle values="#dedede,#eeeeee"}">{$entry.comment|escape}</td>
</tr>
{foreachelse}
<tr>
<td colspan="2">no records</td>
</tr>
{/foreach}
</table>
guestbook.tpl 是用于瀏覽留言簿的模板文件。它以一個foreach函數從頭到尾遍歷留言簿的數據,顯示出每個留言簿條目的‘name’、‘date’和‘comment’字段信息?!甦ate’字段信息經日期格式化調節器(date_format)格式化后顯示。 ‘name’和‘comment’字段信息使用轉碼調節器(escape)處理,以便原樣顯示html代碼和避免腳本攻擊。{cycle} 函數用來在表格中隔兩行顯示不同的表格背景色。
/web/www.example.com/smarty/guestbook/templates/guestbook_form.tpl
<form action="{$script_name}?action=submit" method="post">
<table border="1">
{if $error ne ""}
<tr>
<td bgcolor="yellow" colspan="2">
{if $error eq "name_empty"}you must supply a name.
{elseif $error eq "comment_empty"} you must supply a comment.
{/if}
</td>
</tr>
{/if}
<tr>
<td>name:</td>
<td><input type="text" name="name" value="{$post.name|escape}" size="40"></td>
</tr>
<tr>
<td valign="top">comment:</td>
<td><textarea name="comment" cols="40" rows="10">{$post.comment|escape}</textarea></td>
</tr>
<tr>
<td colspan="2" align="center"><input type="submit" value="submit"></td>
</tr>
</table>
</form>
guestbook_form.tpl 是用來書寫留言的模板文件。如果因為通不過表單驗證產生錯誤而重新顯示表單,已填寫的表單內容仍然存在,并且錯誤代碼和錯誤信息也會顯示出來。表單里的內容已經做了html轉碼處理,所以沒有了html標記或者引號字符的沖突問題。(這個非常重要?。?/p>
通過這個范例程序,我們熟悉了幾個開發smarty驅動的程序所需要的關鍵知識點。如下:
* 所有與頁面表現相關的元素都包含在模板文件里。我們沒有從模板文件外部指派html標簽或者其他任何與頁面表現相關的元素到模板文件中。唯一從外部指派到頁面的只有需要顯示的內容,在這里而言就是留言簿的條目。
* 錯誤提示也由模板文件來維護。我們沒有(從模板文件外部)指派錯誤提示本身,而是指派了錯誤代碼用于確定哪條錯誤提示需要被顯示出來。另一個維護錯誤提示的方法是使用smarty的設置文件(config files),在那里(在config file里),我們以“error_code = error message”的格式存儲錯誤提示,然后用{$smarty.config.$error_code} 方法根據錯誤代碼顯示錯誤提示。
* php 對象(們)相比于過程化的函數+沉悶的參數更能便捷靈活地傳遞信息從而(應)被廣泛使用。(如同sql/template 對象和錯誤代碼的使用)
希望這個范例能給你一個思路,一個在你的程序開發工作中使用smarty,把程序中的表現邏輯與實現邏輯干凈地分離開來的思路。
新聞熱點
疑難解答