經過上次我給你的訓練之后,你可能或者對建立另外一個PHP應用摩拳擦掌或者你已經決定放棄PHP編程而以嘗試種植黃瓜來代替。如果是后者,那么你應該立即停止閱讀,因為我可以向你保證PHP 101的結束部分絕對與教授你關于蔬菜耕作方面的內容沒有一點關系。
然而,如果是前者,那么你將會享受接下來的內容。通過接下來的幾頁,我將會使用PHP、SQLite和SimpleXML來開發一個簡單的RSS新聞聚合器。使用這種新聞聚合器,你可以從所有的Web頁面中插入RSS新聞信源,從而為你的Web站點創建一個反映你的需求和興趣的新聞廣播。其中最好的部分是:每次你瀏覽它的時候,其采用最新的消息自動更新。
請跟著來,讓我們開始吧!
字母湯
我將會從基礎開始。RSS到底是什么呢?
RSS(該縮寫詞代表RDF站點摘要--RDF Site Summary)是一個最初由Netscape設計的格式,它用來分發關于Netscape的My.Netscape.Com門戶上的內容的信息。該格式自其在1997年初期出現以來已經經過了許多反復(請訪問http://backend.userland.com/stories/rss091以獲取關于RSS的長期和復雜的歷史方面的信息),但是大部分信源使用RSS 1.0或者RSS 0.91,這兩個版本均是輕量級但具有全部特征。
RSS使得Web站點管理員在特定站點特定時間公布和分發新的和有趣的信息變得可能。該信息范圍從新聞文章列表到股票市場數據或者天氣預報,該信息以格式化良好的XML文檔發布,而且因此它可以被任意XML解析器(包括為PHP 5的一部分的SimpleXML)解析、處理和呈現。
相當多的流行Web站點使得RSS或者RDF新聞信源普遍對公眾可用。Freshmeat和Slashdot兩個均有一個,而且許多其他站點也是這樣包括PEAR、PECL和Zend站點。一個對于公眾RSS信源的快捷的Google搜索將使你得到數不清的鏈接。
一個RSS文檔通常情況下包含采用描述性元數據標注的資源列表(URLs)。請看下面的例子:
<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF xmlns:rdf="<title>Trog</title>
<description>Well-written technical articles and tutorials on web technologies</description>
<link>http://www.melonfire.com/community/columns/trog/</link>
<items>
<rdf:Seq>
<li rdf:resource="<li rdf:resource="<li rdf:resource="</rdf:Seq>
</items>
</channel>
<item rdf:about="<title>Building A PHP-Based Mail Client (part 1)</title>
<link>http://www.melonfire.com/community/columns/trog/article.php?id=100</link>
<description>Ever wondered how web-based mail clients work? Find out here.</description>
</item>
<item rdf:about="<title>Using PHP With XML (part 1)</title>
<link>http://www.melonfire.com/community/columns/trog/article.php?id=71</link>
<description>Use PHP's SAX parser to parse XML data and generate HTML pages.</description>
</item>
<item rdf:about="<title>Access Granted</title>
<link>http://www.melonfire.com/community/columns/trog/article.php?id=62</link>
<description>Precisely control access to information with the SQLite grant tables.</description>
</item>
</rdf:RDF>
正如你所見,一個RDF文件被劃分為明顯地以分割線劃分的段落。第一部分以文檔序言、命名空間聲明以及根元素開始。第一部分后面緊跟著一個<channel>塊,它包含了關于該RDF文件所描述的頻道方面的概要信息。在上述例子中,這個頻道是Melonfire's Trog專欄,它每周以新的技術文章和教程而更新。
<channel>塊包含一個<items>塊,該塊包含了RDF文檔內所描述的所有資源的順序列表。該塊內的每個資源對應于其后續的<item>塊內詳細描述的資源。每一個<item>塊更加詳細地描述了一單一資源,提供了該資源的題目、一個URL地址和描述。我們的應用程序將會用來產生個性化新聞信源的就是該信息。
奠定基礎
既然你已經知道了RSS和RDF是關于什么的,那么是開始工作的時候了。我將會以坐在窗戶附近的桌子上然后在一頁紙上漫無目的地亂畫開始,直到我逐漸地準確勾畫出我的應用程序應該做什么事情為止(實際上,在這個實例中,需求實際上是非;镜):
1.應用程序必須支持一或多個RSS兼容的新聞信源。在啟動階段,應用程序應該獲取這些新聞信源的最新版本,解析它們然后以一種易于閱讀的方式顯示它們的內容。SQLite數據庫是存儲這些信源列表的較好的選擇。
2.用戶應當能夠控制她/他從每個信源挑選的消息的數量。舉例而言,一個用戶可能想顯示比商業新聞更多的科學和健康新聞。
3 .應用程序應該為用戶提供一個基于Web的界面以增加和刪除新聞信源。該界面將使用PHP的SQLite API來在SQLite數據庫文件上運行適當的SQL查詢和修改存儲于數據庫中的信息。
請記住這些需求,設計一個簡單的數據庫表格以保存(用戶可定制的)RSS新聞信源列表是可能的。下面是它可能看上去的樣子: CREATE TABLE rss (
id INTEGER NOT NULL PRIMARY KEY,
title varchar(255) NOT NULL,
url varchar(255) NOT NULL,
count INTEGER NOT NULL
);
從上面的表格可以看出,很明顯每個新聞信源將會有三個屬性:描述性題目、信源自身的URL以及指示你愿意在你自己定制的新聞頁面上顯示多少條信源的信息的值。讓我們增加一些數據來開始做些事情。
INSERT INTO rss VALUES(1, 'Slashdot', 'http://slashdot.org/slashdot.rdf', 5);
INSERT INTO rss VALUES(2, 'Wired News', 'http://www.wired.com/news_drop/netcenter/netcenter.rdf', 5);
INSERT INTO rss VALUES(3, 'Business News', 'http://www.npr.org/rss/rss.php?topicId=6', 3);
INSERT INTO rss VALUES(4, 'Health News',
'http://news.bbc.co.uk/rss/newsonline_world_edition/health/rss091.xml', 3);
INSERT INTO rss VALUES(5, 'Freshmeat', 'http://www.freshmeat.net/backend/fm-releases.rdf', 5);
你可以使用SQLite命令直接從schema文件rss.sql中來創建所有這些。如果你仍然在使用來自第九章的命令行客戶端,那么請從中讀取。實際上,現在是你下載用于該應用程序的所有源代碼的好時機,這樣可以使得你在完成該教程的過程中容易地檢查和參考。請注意,你將需要一個支持PHP 5的Web服務器來運行該代碼。
重要的消息
將數據庫安全的放置在其Web不可訪問的目錄內,下一步就是編寫使用該數據庫內數據連接至每個新聞信源,將其解析用于新聞數據然后展現一個定制的新聞頁面的代碼。
下面就是該代碼usrer.php:
<?php
// PHP 5
// include configuration file
include('config.php');
// open database file
$handle = sqlite_open($db) or die('ERROR: Unable to open database!');
// generate and execute query
$query = "SELECT id, title, url, count FROM rss";
$result = sqlite_query($handle, $query) or die("ERROR: $query. ".sqlite_error_string(sqlite_last_error($handle)));
// if records present
if (sqlite_num_rows($result) > 0) {
// iterate through resultset
// fetch and parse feed
while($row = sqlite_fetch_object($result)) {
$xml = simplexml_load_file($row->url);
echo "<h4>$row->title</h4>";
// print descriptions
for ($x = 0; $x < $row->count; $x++) {
// for RSS 0.91
if (isset($xml->channel->item)) {
$item = $xml->channel->item[$x];
}
// for RSS 1.0
elseif (isset($xml->item)) {
$item = $xml->item[$x];
}
echo "<a href=\"$item->link\">$item->title</a><br />$item->description<p />";
}
echo "<hr />";
// reset variables
unset($xml);
unset($item);
}
}
// if no records present
// display message
else {
?>
<font size = '-1'>No feeds currently configured</font>
<?php
}
// close connection
sqlite_close($handle);
?>
下面是一種可能的輸出(請注意,在產生頁面的時候會有一個時間延遲,因為PHP將會悄悄地打開到每個URL的HTTP鏈接以檢索相應的RSS信源):
完成這項工作的代碼可能看上去比較簡單,但實際上,在系統后臺,還有很多操作正在發生。第一步就是從SQLite數據庫中獲取用戶配置的RSS信源列表。為了達到這個目的,初始化一個SQLite數據庫句柄,然后執行一個SQL SELECT查詢。While()循環用于遍歷該查詢結果的記錄集。
對于每個因此得到的URL,使用simplexml_load_file()函數以檢索和讀取RSS信源。取決于所顯示的信息數目,執行一個for()循環,然后解析信源中<item>元素的適當數目。請注意,訪問一個<item>元素的路徑根據信源是RSS 0.91還是RSS1.0而不同。
請注意,如果數據庫為空,那么會出現一條錯誤消息。在這個例子中,既然我已經插入了一系列的記錄到數據庫中,所以你根本不會看到錯誤消息;然而,保證所有的可能發生的事情甚至細微的事情都得到解決是良好的編程習慣。
正如之前一樣,文件config.php被包含在每個腳本的頂部。該文件包含數據庫訪問參數,如下所示:
<?php
// database details
// always use a directory that cannot be accessed from the web
$path = $_SERVER['DOCUMENT_ROOT'].'/../';
$db = $path.'rss.db';
?>
點和點擊
隨著新聞顯示已不再是問題,所剩下的就是增加一個簡單的管理工具來管理SQLite數據庫的內容。這里的代碼和你在PHP 101第14章所看到的將會十分相似:一個提供了當前數據庫的快照以及增加新條目的表單的被稱為admin.php的起始頁面。此處為其全文:
<html>
<head><basefont face = 'Arial'></head>
<body>
<h2>Feed Manager</h2>
<h4>Current Feeds:</h4>
<table border = '0' cellspacing = '10'>
<?php
// PHP 5
// include configuration file
include('config.php');
// open database file
$handle = sqlite_open($db) or die('ERROR: Unable to open database!');
// generate and execute query
$query = "SELECT id, title, url, count FROM rss";
$result = sqlite_query($handle, $query) or die("ERROR: $query. ".sqlite_error_string(sqlite_last_error($handle)));
// if records present
if (sqlite_num_rows($result) > 0) {
// iterate through result set
// print article titles
while ($row = sqlite_fetch_object($result)) {
?>
<tr>
<td><?php echo $row->title; ?> (<?php echo $row->count; ?>)</td>
<td><font size = '-2'><a href="delete.php?id=<?php echo $row->id; ?>">delete</a></font></td>
</tr>
<?php
}
}
// if there are no records present, display message
else {
?>
<font size = '-1'>No feeds currently configured</font>
<?php
}
// close connection
sqlite_close($handle);
?>
</table>
<h4>Add New Feed:</h4>
<form action = 'add.php' method = 'post'>
<table border = '0' cellspacing = '5'>
<tr>
<td>Title</td>
<td><input type = 'text' name = 'title'></td>
</tr>
<tr>
<td>Feed URL</td>
<td><input type = 'text' name = 'url'></td>
</tr>
<tr>
<td>Stories to display</td>
<td><input type = 'text' name = 'count' size = '2'></td>
</tr>
<tr>
<td colspan = '2' align = 'right'><input type = 'submit' name = 'submit' value = 'Add RSS Feed'></td>
</tr>
</table>
</form>
</body>
</html>
下面是結果顯示的樣子:
正如你所看到的那樣,該腳本中有兩個部分。前半部分連接至數據庫然后打印所有當前配置的新聞信源的列表,每個新聞信源后面緊跟著“刪除”連接。后半部分包含了一個用于管理員增加一個新的信源及其屬性的表單。
一旦表單被遞交,數據就被遞交給腳本add.php,該腳本驗證數據然后將其保存到數據庫中,下面是add.php腳本中的代碼:
<html>
<head><basefont face = 'Arial'></head>
<body>
<h2>Feed Manager</h2>
<?php
// PHP 5
if (isset($_POST['submit'])) {
// check form input for errors
// check title
if (trim($_POST['title']) == '') {
die('ERROR: Please enter a title');
}
// check URL
if ((trim($_POST['url']) == '') || !ereg("^http\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(:[a-zA-Z0-9]*)?/?([a-zA-Z0-9\._\?\,\'/\\\+&%\$#\=~\-])*$", $_POST['url'])) {
die('ERROR: Please enter a valid URL');
}
// check story number
if (!is_numeric($_POST['count'])) {
die('ERROR: Please enter a valid story count');
}
// include configuration file
include('config.php');
// open database file
$handle = sqlite_open($db) or die('ERROR: Unable to open database!');
// generate and execute query
$query = "INSERT INTO rss (title, url, count) VALUES ('".$_POST['title']."', '".$_POST['url']."', '".$_POST['count']."')";
$result = sqlite_query($handle, $query) or die("ERROR: $query. ".sqlite_error_string(sqlite_last_error($handle)));
// close connection
sqlite_close($handle);
// print success message
echo "Item successfully added to the database! Click <a href = 'admin.php'>here</a> to return to the main page";
}
else {
die('ERROR: Data not correctly submitted');
}
?>
</body>
</html>
腳本的下半部分對你來說應該是熟悉的:它包含了常用的函數調用來打開SQLite數據庫然后執行一條插入查詢語句來保存用戶數據到數據庫中。然而有趣的是腳本的上半部分,它包含了一些輸入測試以確保正在被保存的數據不包含無用數據。
這里有三個測試。一個檢查描述性題目是否存在,另外一個使用is_numeric()函數來驗證用于信息數目的輸入值是有效的數字,而第三個使用ereg()函數來檢查URL的格式。如果你閱讀了第十三章,那么你將會完全了解驗證用戶輸入的重要性;在這里,該理論在起作用。
上面的代碼負責增加新的RSS信源,F在,怎樣去除它們呢?
回憶一下在admin.php文件中列表中顯示的每個信源如何擁有一個“刪除”鏈接,該鏈接指向了腳本delete.php。假定給出一個信源ID號(通過鏈接傳遞),該delete.php腳本負責從表格中刪除一個新聞信源。請看代碼然后事情會變得更加清楚:
<html>
<head><basefont face = 'Arial'></head>
<body>
<h2>Feed Manager</h2>
<?php
// PHP 5
if (isset($_GET['id']) && is_numeric($_GET['id'])) {
// include configuration file
include('config.php');
// open database file
$handle = sqlite_open($db) or die('ERROR: Unable to open database!');
// generate and execute query
$query = "DELETE FROM rss WHERE id = '".$_GET['id']."'";
$result = sqlite_query($handle, $query) or die("ERROR: $query. ".sqlite_error_string(sqlite_last_error($handle)));
// close connection
sqlite_close($handle);
// print success message
echo "Item successfully removed from the database! Click <a href = 'admin.php'>here</a> to return to the main page";
}
else {
die('ERROR: Data not correctly submitted');
}
?>
</body>
</html>
通過URL GET方法傳遞的記錄ID號由delete.php腳本獲得,然后使用DELETE SQL查詢來刪除對應的記錄。請親自試驗然后查看結果。
那就是我為你準備的一切。我希望你喜愛這總共15章的PHP 101旅程,而且希望你覺得它既有教育意義也有趣(我知道,我是這么覺得的,而且有你在前行中伴隨是一種樂趣)。如果你期望閱讀更多的關于PHP的特定方面內容,請訪問www.melonfire.com然后瀏覽部分更多我的教程和文章。到那時… 編碼快樂!