## 快速入門四、制作留言本
本章讓我們使用sp框架來制作一個簡單的留言本程序,在實例中學習程序開發是非常有效的。同時,我們也可以體會一下實際網絡項目的開發流程。
**新版手冊我們打算用另一個教程方式,讓大家能更容易體驗到web開發的過程**。
新的方式主要是將原始的HTML頁面提供下載,大家可以先下載全部頁面,跟著教程來制作程序。如果期間有不明白的地方,可以下載完整的例子程序作為參考。
[下載HTML頁面壓縮包](images/2.zip)
[下載完整例子壓縮包](images/3.zip)
> 一般在團隊內進行php開發工作時,HTML頁面均是由前端開發人員協助完成,然后php開發人員拿到的已經是頁面成品。
> 之后php開發人員再把這些頁面,使用框架等技術做成真正可以運行的web站點。
### 一、代碼準備
首先,解壓“HTML頁面壓縮包”,我們可以看到一個html文件(guestbook.html)以及一個資源目錄(i目錄)。
1. 把資源目錄i放到之前介紹的Helloworld程序的index.php同級目錄。大概會是這樣子。

2. 然后把guestbook.html文件剪切到protected/view目錄下。

這樣圖片/CSS/JS等就可以正常顯示了。
---
### 二、數據庫準備
首先我們來看看,留言本的需求,也就是我們的程序將要實現什么功能:
- 訪問者可以查看留言首頁。
- 訪問者可以進行留言。
> 請注意,在入門教程接下來的章節里面,我們還會介紹如何分頁顯示留言、用戶點贊、管理員管理留言等功能的實現方法,本章僅是介紹簡單留言本的制作。
> 大家可以發現留言本的HTML頁面中,已經包含了這些功能的樣式。那就努力完成本章并且接著學習吧。
我們來看看,在上面需求中所涉及的數據有哪些?
對,僅是留言信息。那么我們大致考慮,留言信息會包括:留言標題,留言內容,留言者名字。另外,我們留言的信息還需要包括一個唯一的標志,以區分每一條留言。好了,那么我們就可以得出,留言本程序的數據表僅有一個“留言表”(起個英文名叫guestbook),它的大致結構是:
- 留言標題,字符串(也就是中英文)形式,大概不會多于50字。用title做數據表的字段名稱。
- 留言內容,字符串,也不會多于200字吧,用contents做字段名。
- 留言者名字,字符串,大概在20個字以內,用username做字段名。
- 唯一標志,一般用數字形式的ID。用id做字段名。
- 另外還有個留言時間要記錄的,一般php開發我們會用“時間戳”來記錄時間。時間戳是11位的數字。
按以上的說明,我們可以得出以下的數據表結構。
CREATE TABLE `guestbook` (
`id` INT(11) NOT NULL AUTO_INCREMENT ,
`title` VARCHAR(50),
`contents` VARCHAR(200),
`username` VARCHAR(20),
`createtime` INT(11),
PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
> 對于數據庫知識,我們提倡的是按需學習,也就是用到哪些就學哪些,畢竟“書上講得深奧,實際沒有用到”,那就是費力不討好。并且,在實際工作中,PHP程序員所需要的,也僅僅是一小部分的數據庫知識,萬一遇到更復雜的數據庫需求我們也有非常多的解決途徑,比如同事之間討論,SpeedPHP論壇求助,找GOOGLE大神等等。所以除非是很有必要(比如工作是程序員兼數據庫管理員DBA的人員),不然數據庫知識方面,我們也是淺嘗輒止即可。
> 初學PHP,我們建議可以了解一下以下的數據庫知識就足夠,不需要給自己太多的學習壓力。
> - 在類似PhpMyAdmin的數據庫管理工具中,如何建表。
> - CREATE,UPDATE,DELETE,SELECT四個語言的簡單用法。
> - 懂得思考和想方法解決遇到的問題。
> - 使用數據庫管理工具而不用純粹SQL語句和命令行并沒有什么荒廢知識的問題,畢竟那會更簡單快捷并且避免許多問題,這是提高開發效率的必然選擇。
在PhpMyAdmin中建好了guestbook表:

然后,我們可以在protected/config.php文件中對程序進行數據庫的配置:
$domain = array(
"localhost" => array( // 域名必須匹配
'debug' => 1,
'mysql' => array(
'MYSQL_HOST' => 'localhost', // 數據庫地址
'MYSQL_PORT' => '3306', // 數據庫端口,一般是3306
'MYSQL_USER' => 'root', // 數據庫用戶名
'MYSQL_DB' => 'test', // 數據庫庫名稱
'MYSQL_PASS' => '', // 數據庫密碼
'MYSQL_CHARSET' => 'utf8', // 編碼,一般utf8即可
),
),
);
這里必須注意幾點:
1. $domain下面是域名,必須跟當前web站點的域名相同,比如說本機學習測試時,一般會用localhost;如果是發布到網上,如果你的域名是http://php.test.com ,那么就必須設置成"php.test.com"。否則會出現500錯誤。
2. 數據庫的配置資料,一般網上購買的主機空間都會提供,請聯系主機提供商。
---
### 三、顯示頁面
接下來,我們打開protected/controller/MainController.php文件,把原來的
function actionIndex(){
echo "Hello World";
}
改成
function actionIndex(){
$this->display("guestbook.html");
}
打開瀏覽器看看http://localhost :

> 如果提示找不到服務器,那么請檢查你的服務器有沒有開啟,比如說打開xampp等。
我們已經把頁面給顯示出來了。
---
### 四、發表留言
這里我們先來開發“發表留言”的功能,畢竟要先有數據,才好顯示。
還是protected/controller/MainController.php,我們加入新的方法actionWrite()
<?php
class MainController extends BaseController {
function actionIndex(){
$this->display("guestbook.html");
}
function actionWrite(){
dump(arg());
}
}
actionWrite()方法還是以action開頭,代表了這是一個頁面,里面現在只是通過dump(arg())函數把提交的數據輸出一下,方便我們驗證表單提交是否正確。
> 這種dump()提交數據的方式建議大家多用用,這是非常有益的小習慣。很多時候我們會發現表單失效首先是因為提交數據不對引起的。一開始就驗證提交數據會保證數據不會錯。
接下來修改模板,打開protected/view/guestbook.html模板,大概在109行左右,我們可以看到有填寫留言的表單form。
<form method="POST" action="#">
把它的action,也就是指向地址,改為我們的提交留言地址。
<form method="POST" action="<{url c="main" a="write"}>">
保存文件后打開瀏覽器刷新一下localhost,右鍵查看源代碼,會發現這個地址已經變成了:

> 這里是url模板函數的功能,具體可以參考手冊關于url函數的介紹。
我們點擊“發布留言”,并且隨便輸入點什么,再點擊“提交”按鈕。

提交后會發現瀏覽器已經來到了 http://localhost/main/write

這個地址對應的就是MainController內的actionWrite()方法。
然后我們繼續來編寫actionWrite()方法:
function actionWrite(){
dump(arg());
// 構造新建留言的數據
$newrow = array(
"title" => arg("title"), // 字段一一對應
"contents" => arg("contents"),
"username" => arg("username"),
"createtime" => time(), // time()函數可以產生當前時間戳
);
// 實例化一個guestbook的模型類
$guestbook = new Model("guestbook");
// 使用Model的create方法把前面的數據插入到數據表中
$result = $guestbook->create($newrow);
// 輸出一下結果,如果是1,那證明已經插入了1條新的數據
dump($result);
}
我們來看看:
1. $newrow是一個字段(key)和數據表guestbook字段對應的php數組,如title,contents等字段。
2. $newrow各字段的值,是提交上來的值或者是時間戳。如arg("title")對應的就是模板中提交的title數據。
<input type="text" name="title" class="form-control" id="inputTitle" placeholder="請輸入標題...">
3. time()函數會產生一個11位的時間戳,我們經常用其來存儲php的時間,這里將time()的結果存到createtime字段上面去。
4. new Model("guestbook")的方法可以實例化一個Model類,這個類對應的數據表是剛才我們建的guestbook表。
5. 調用Model類的create()方法來插入新記錄,create方法接收一個數組,數組是新記錄的數據。
6. create()方法返回的結果是“影響行數”,比如說新增了一行數據,那么返回就是1。這里可以把返回1當作新增記錄成功的表現即可。
7. 我們來看看數據庫,發現記錄已經新增了。

> 和舊版有點像又有點區別的地方是,Model類本身已經可以直接輸入表名稱,然后對該表進行實例化。而舊版是通過spDB()這個函數。
接下來我們加個“留言成功”的提示上去,把dump()輸出的內容刪除,那么“填寫留言”的功能就完成了。
function actionWrite(){
dump(arg());
// 構造新建留言的數據
$newrow = array(
"title" => arg("title"), // 字段一一對應
"contents" => arg("contents"),
"username" => arg("username"),
"createtime" => time(), // time()函數可以產生當前時間戳
);
// 實例化一個guestbook的模型類
$guestbook = new Model("guestbook");
// 使用Model的create方法把前面的數據插入到數據表中
$result = $guestbook->create($newrow);
// 輸出一下結果,如果是1,那證明已經插入了1條新的數據
// dump($result);
$this->tips("留言成功!", url("main", "index"));
}

> $this->tips()方法是BaseController類自帶的方法,只是彈出一個JS提示。如果需要更漂亮的提示,可以自行修改該方法的實現。
---
### 五、留言列表
接下來是顯示留言列表,還是protected/controller/MainController.php,我們修改一下actionIndex()方法。
function actionIndex(){
// 實例化一個guestbook的模型類
$guestbook = new Model("guestbook");
// 用findAll()方法查詢guestbook表的全部數據
$this->records = $guestbook->findAll();
// 輸出看看
dump($this->records);
// 為了清楚看到輸出,我們先屏蔽頁面輸出
//$this->display("guestbook.html");
}
1. 跟actionWrite()方法一樣,我們先實例化一個guestbook表的模型類。
2. 然后調用模型類的findAll()方法,得到結果是$this->records。
3. 輸出$this->records看看。

這里我們可以看到,變量$this->records的內容是一個多維數組,分別對應了數據表guestbooke里面的兩條記錄,每條記錄的字段(key)是數據表的字段名稱,對應值的是數據本身。
> $this->records這樣的變量是控制器的成員變量,而這個寫法跟普遍變量(比如說$records)不一樣的地方是,$this->records可以在模板內直接使用。
> 也就是說,在控制器內,通過$this->賦值的變量,都可以在模板內使用;而普通的變量并沒有這樣的功能。
> 這也是最基礎的模板引擎的用法之一。
這里看到我們希望的數據已經出現在$this->records變量中了,那么我們在模板里面來使用這些數據吧。
打開protected/view/guestbook.html文件,大概在27行左右,找到
<div class="panel panel-default">
...
</div>
這個div和它的范圍(結束的</div>大概在52行)。可以看出來下面還有一個相同的。這里一塊div就是頁面上顯示的一塊內容:

這里我們來做個循環,把$this->records變量的內容循環顯示出來。
**請注意在模板內,$this->records變量對應的內容變量是$records。**
<{foreach $records as $r}>
<div class="panel panel-default">
<div class="panel-body">
<div class="media">
<div class="media-left">
<a href="#">
<img class="media-object" src="/i/img/1.gif" alt="...">
</a>
</div>
<div class="media-body">
<h4 class="media-heading"><{$r.title}><button type="button" class="close" aria-label="Close"><span aria-hidden="true">×</span></button></h4> <{$r.contents}>
<blockquote class="blockquote-reverse small">
<ul class="list-inline text-muted">
<li>by</li>
<li><{$r.username}></li>
<li><{date("Y年m月d日 H:i:s", $r.createtime)}></li>
<li>
<button type="button" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span> 100</button>
</li>
</ul>
</blockquote>
</div>
</div>
</div>
</div>
<{/foreach}>
模板內的內容,可以通過變量進行替換輸出的。所以我們主要關注用<{和}>包住的部分。
1. 首先是一個大的<{foreach}><{/foreach}>,這里代表了整個區域的HTML代碼會被“循環”輸出,循環的次數根據$records的記錄條數而定。(我們可以看到循環了兩次,證明數據庫里面guestbook表是有兩條記錄的)
2. foreach的語法跟php本身的foreach是差不多的,<{foreach $records as $r}>指的是將$records循環,然后每次循環出來的值都賦值給$r。那么每次循環里面,$r實際上是代表了一個一維數據,字段(key)名對應數據表的字段名,值是數據表的記錄內容。而通過點號(.)可以把值取出來顯示在頁面上,如<{$r.title}>就能取到title字段的值了。
3. 然后標題,內容,用戶名的位置,分別被<{$r.title}>,<{$r.contents}>,<{$r.username}>所替換,分別輸出對應的值。
4. <{date("Y年m月d日 H:i:s", $r.createtime)}>是顯示時間,使用了php函數date來格式化我們的createtime存儲的時間戳。
> date函數的用法可以參考http://cn.php.net/manual/zh/function.date.php
這里我們先把MainController里面的dump()輸出去掉,恢復display顯示模板。
function actionIndex(){
// 實例化一個guestbook的模型類
$guestbook = new Model("guestbook");
// 用findAll()方法查詢guestbook表的全部數據
$this->records = $guestbook->findAll();
// 輸出看看
// dump($this->records);
$this->display("guestbook.html");
}
另外把剛才的div下面的另一個div整個刪除,這樣我們就可以看到數據庫輸出的內容了。

這樣我們就完成了初步的留言本開發了,現在可以多發點留言上去試試了哦。
接下來的教程,我們會繼續介紹如何擴展這個留言本,讓它變得更有趣。
- 自述
- 一、入門教程
- 1. 開始使用SpeedPHP
- 2. Hello World
- 3. 理解MVC
- 4. 制作留言本
- 5. 數據操作及Ajax
- 二、框架概述
- 1. 特色
- 2. 版權及開源協議
- 3. 開發環境
- 4. 編碼版本
- 5. SAE平臺使用
- 三、開發指南
- 1. 開發流程
- 2. 架構及擴展
- 3. 程序目錄結構
- 4. 命名建議
- 5. 安全建議
- 6. 用戶自定義
- 7. 模塊modules
- 四、訪問交互
- 1. 表單提交及數據獲取
- 2. session/cookie的使用
- 3. 偽靜態及URL跳轉
- 4. 使用frameset
- 5. 模板引擎特性和使用方法
- 五、數據操作
- 1. 建立數據模型類
- 2. 數據操作教程
- 3. 分頁
- 4. SQL支持及關聯實現
- 5. 多數據庫、主從庫配置