【IT168 专稿】真实世界
在本系列教程期间,我已经带你浏览了PHP,同时向你讲授了开始使用该十分强大的工具之前你所需要知道的一切。你已经学习了如何处理数组、编写函数、构造对象以及抛出异常。你也已经学习了如何从表单中读取用户输入、查询数据库以及使用Cookies和Sessions来维护状态。你已经不再是之前的那个羞怯的PHP新手了而是大胆的、强大的PHP勇士了,准备承担世界(或者你的老板)扔向你的一切……
这里只有一个不足。的确,你拥有了所有的武器装备……但是你不曾在真实世界中使用过它。那就是PHP 101的本结束部分所将要涉及的内容。
通过本教程的最后两章,我将指导你完成创建两个现实世界中的PHP应用程序的整个过程。这不仅向你介绍了使用PHP的实际的应用开发而且它也会给你一个试验你在数周之前所掌握的所有理论知识的机会。
司机们,请发动你们的引擎,让我们开始吧!
急待解决的问题
第一个应用相当简单。该应用是一个用于Web站点的投票系统,它允许你快速测量你的访问者对于争议问题(Kerry对Bush、to-mah-to对to-mae-to等类的事情)的想法。这种在线投票机制相当流行,因为它允许你发现你的访问者正在想什么且使得你的Web站点更加动态和交互。
我确信你已经在许多Web门户站点上看到了这样的系统在运行,而且在内心中对其如何工作有了一个十分清楚的描绘。然而,在你开始编写即使一行代码之前准确的写下最终产品所应该做的事情是良好的习惯做法(技客将这个称为“定义需求”)。
1. 这里需要一种机制,通过该机制用户可以查看问题然后从可能的答案列表中进行选择。该“投票”接着需要被系统捕获然后增加到那个问题现有的总票数中去。
2. 这里需要一种用于站点管理员增加新的或者删除旧问题的方法。MySQL数据库是存储这些问题和对其回答的好地方,但是管理员可能不是必然地足够熟练地使用SQL语言来手动改变这些数据。因此,应该提供一个基于表单的界面以使得管理任务变得简单和不出现错误。
3. 很显然地,也需要一种方法来查看为每个问题所递交的投票以及问题答案的报告。该报告将包含一个问题所登记的总投票数以及每个回答所接收到的投票的详细分类。
此处一个重要的问题是:为每个问题固定可用选项的数目是否有意义?按照我的观点,它没有意义。因为可用选项的数目可能会随着每一问题而改变。允许该数字变化以及允许投票系统管理员为每个问题增加适当多的选择会更好。然而,我们可以为每个问题可能的选项的数目定义一个上限(由于参数的缘故,让我们假设为5)。
在脑中记住这个基本要点,下一个步骤就是设计一个支持这些需求的数据库。
设计者数据库
现在正是你下载该应用源代码的好时机,这样使得你在完成该教程过程中对其进行参考(注意,你将需要一个MySQL服务器和支持PHP的Web服务器来运行该代码)。
下面就是我将用于该应用程序中的数据库,它存储于db.sql文件中:
# # Table structure for table `questions` # CREATE TABLE `questions` ( `qid` tinyint(3) unsigned NOT NULL auto_increment, `qtitle` varchar(255) NOT NULL default '', `qdate` date NOT NULL default '0000-00-00', PRIMARY KEY (`qid`) ); # # Table structure for table `answers` # CREATE TABLE `answers` ( `aid` tinyint(3) unsigned NOT NULL auto_increment, `qid` tinyint(4) NOT NULL default '0', `atitle` varchar(255) NOT NULL default '', `acount` int(11) NOT NULL default '0', PRIMARY KEY (`aid`) );
正如你所看到的那样,它十分简单:一个表格用于问题,一个用于回答。这两个表格通过qid字段这种方法互相连接。采用这种结构,每个问题具有无数个的回答实际上也是可能的(这不是我们想要的-我们更愿意这个数字是5或者更少,但是实现该规则的逻辑放在应用层要比放在数据库层更好)。
为了开始这件事以及给你一个关于该结构在你的现实生活中如何实施的更好的概念,让我们插入一个问题和三个可能的回答一起到数据库中:
INSERT INTO `questions` VALUES (1, 'What version of PHP are you using?', '2004-10-15'); INSERT INTO `answers` VALUES (1, 1, 'PHP 3.x', 0); INSERT INTO `answers` VALUES (2, 1, 'PHP 4.x', 0); INSERT INTO `answers` VALUES (3, 1, 'PHP 5.x', 0);
或者,你可以创建一个新的数据库然后在命令提示符下输入source db.sql来直接加载表格结构和数据。
摇动投票
随着数据库的加入,是时候将用户所看到的页面整合起来了。这些页面中的第一个是user.php文件,其连接到数据库以从中取得最新的投票问题然后和它所有可能的回答一起显示。请看:
<html> <head><basefont face = 'Arial'></head> <body> <?php // include configuration file include('config.php'); // open database connection $connection = mysql_connect($host, $user, $pass) or die('ERROR: Unable to connect!'); // select database mysql_select_db($db) or die('ERROR: Unable to select database!'); // generate and execute query $query = "SELECT qid, qtitle FROM questions ORDER BY qdate DESC LIMIT 0, 1"; $result = mysql_query($query) or die("ERROR: $query.".mysql_error()); // if records are present if (mysql_num_rows($result) > 0) { $row = mysql_fetch_object($result); // get question ID and title $qid = $row->qid; echo '<h2>'.$row->qtitle .'</h2>'; echo "<form method = post action = 'user_submit.php'>"; // get possible answers using question ID $query = "SELECT aid, atitle FROM answers WHERE qid = '$qid'"; $result = mysql_query($query) or die("ERROR: $query.".mysql_error()); if (mysql_num_rows($result) > 0) { // print answer list as radio buttons while ($row = mysql_fetch_object($result)) { echo "<input type = radio name = aid value = '".$row->aid."'>'".$row->atitle."'</input><br />"; } echo "<input type = hidden name = qid value = '".$qid."'>"; echo "<input type = submit name = submit value = 'Vote!'>"; } echo '</form>'; } // if no records present, display message else { echo '<font size="-1">No questions currently configured</font>'; } // close connection mysql_close($connection); ?> </body> </html>
请特别注意我正在运行的SQL查询:我正在使用ORDER BY、DESC和LIMIT关键字来确保我从问题表格中得到了最新的记录(问题)。一旦查询返回一条结果,记录ID用于从答案表格中获得其对应的答案列表。然后用一个while()循环以一系列单选按钮来打印答案。对应于每个答案的记录ID附着其单选按钮;当表单被递交时,这个标识符用于确保正确的计数器得到更新。
请注意,如果数据库是空的,那么将会显示一条错误消息。在这个例子中,我们已经在数据库中插入了一条问题,因此你根本不会看见错误;然而,确保所有可能发生的事情都得到解决是良好的编程习惯,即使是不经常发生的事情。
包含在脚本顶部的config.php文件包含了对MySQL数据库的访问参数。该数据被放置在独立的文件中,这使得当你将应用程序移到一个新的服务器上的时候容易改变该数据。请仔细看下面的代码:
<?php
// database access parameters
$host = 'localhost';
$user = 'guest';
$pass = 'guessme';
$db = 'db3';
![]()
?>
下面是表单看上去的样子:
好的,现在你已经将投票展现出来了。用户正在排队参与投票而且产生了数百万的点击。你能用它们来做什么呢?
答案存在于当用户投票或者递交之前描述的表单时所激活的脚本中。该脚本(user_submit.php)负责为适当的问题/答案组合更新投票计数器。请看:
<html> <head><basefont face = 'Arial'></head> <body> <?php if (isset($_POST['submit'])) { if (!isset($_POST['aid'])) { die('ERROR: Please select one of the available choices'); } // include configuration file include('config.php'); // open database connection $connection = mysql_connect($host, $user, $pass) or die('ERROR: Unable to connect!'); // select database mysql_select_db($db) or die('ERROR: Unable to select database!'); // update vote counter $query = "UPDATE answers SET acount = acount + 1 WHERE aid = ".$_POST['aid']." AND qid = ".$_POST['qid']; $result = mysql_query($query) or die("ERROR: $query. ".mysql_error()); // close connection mysql_close($connection); // print success message echo 'Your vote was successfully registered!'; } else { die('ERROR: Data not correctly submitted'); } ?> </body> </html>
该脚本首先通过验证答案ID $_POST['aid']的存在性以检查确认一个答案已经得到选择。假设该ID是存在的,那么脚本更新数据库以反映新的投票然后显示一条适当的消息。
现在,返回浏览你的笔记本然后看看最初的需求列表。是的,你可以除去1#条目。向前继续2#条目…
现在可能是本教程最有趣的一部分:第3条。很明显地,一旦你有用户和投票到来,你会打算看看投票是如何分布的报告。这个会涉及到连接至数据库,使用问题ID号得到正确的记录集,计算投票的总数以及每一选项在所有投票中的比例,然后在一个表格中显示该信息。
这里是在PHP中其看上去的样子:
下面是输出可能看上去的样子的一个例子:<html> <head><basefont face = 'Arial'></head> <body> <h2>Administration</h2> <?php if ($_GET['qid'] && is_numeric($_GET['qid'])) { // include configuration file include('config.php'); // open database connection $connection = mysql_connect($host, $user, $pass) or die('ERROR: Unable to connect!'); // select database mysql_select_db($db) or die('ERROR: Unable to select database!'); // get the question $query = "SELECT qtitle FROM questions WHERE qid = '".$_GET['qid']."'"; $result = mysql_query($query) or die("ERROR: $query. ".mysql_error()); $row = mysql_fetch_object($result); echo '<h3>'.$row->qtitle.'</h3>'; // reset variables unset($query); unset($result); unset($row); // find out if any votes have been cast $query = "SELECT qid, SUM(acount) AS total FROM answers GROUP BY qid HAVING qid = ".$_GET['qid']; $result = mysql_query($query) or die("ERROR: $query. ".mysql_error()); $row = mysql_fetch_object($result); $total = $row->total; // if votes have been cast if ($total > 0) { // reset variables unset($query); unset($result); unset($row); // get individual counts $query = "SELECT atitle, acount FROM answers WHERE qid = '".$_GET['qid']."'"; $result = mysql_query($query) or die("ERROR: $query. ".mysql_error()); // if records present if (mysql_num_rows($result) > 0) { // print vote results echo '<table border=1 cellspacing=0 cellpadding=15>'; // iterate through data // print absolute and percentage totals while($row = mysql_fetch_object($result)) { echo '<tr>'; echo '<td>'.$row->atitle.'</td>'; echo '<td>'.$row->acount.'</td>'; echo '<td>'.round(($row->acount/$total) * 100, 2).'%</td>'; echo '</tr>'; } // print grand total echo '<tr>'; echo '<td><u>TOTAL</u></td>'; echo '<td>'.$total.'</td>'; echo '<td>100%</td>'; echo '</tr>'; echo '</table>'; } } // if votes have not been cast else { echo 'No votes cast yet'; } // close connection mysql_close($connection); } else { die('ERROR: Data not correctly submitted'); } ?> </body> </html>
该脚本view.php以与delete.php非常相同的方式自admin.php激活(问题ID号作为输入参数传递给view.php脚本然后该ID被用于获取对应的答案以及每个问题所采集的投票)。一旦获得答案集,递交的投票总数就可以被计算出来,然后可以获得每个选项占总票数的百分比。该数据然后以简单的HTML表格显示出来。
当将绝对数据转换为百分比时,你需要小心(如果仍然没有投票的话,那么你会获得一些十分奇怪的除零错误)。为了避免这种情况,脚本中的第二个查询使用MySQL的SUM()函数和GROUP BY字句来获得特殊问题的投票总数。如果该总数为0,那么仍然没有投票,然后一条带有这种意思的消息得到显示;如果该总数大于0,那么就计算各自的百分比。
数字游戏
现在可能是本教程最有趣的一部分:第3条。很明显地,一旦你有用户和投票到来,你会打算看看投票是如何分布的报告。这个会涉及到连接至数据库,使用问题ID号得到正确的记录集,计算投票的总数以及每一选项在所有投票中的比例,然后在一个表格中显示该信息。
这里是在PHP中其看上去的样子:
<html> <head><basefont face = 'Arial'></head> <body> <h2>Administration</h2> <?php if ($_GET['qid'] && is_numeric($_GET['qid'])) { // include configuration file include('config.php'); // open database connection $connection = mysql_connect($host, $user, $pass) or die('ERROR: Unable to connect!'); // select database mysql_select_db($db) or die('ERROR: Unable to select database!'); // get the question $query = "SELECT qtitle FROM questions WHERE qid = '".$_GET['qid']."'"; $result = mysql_query($query) or die("ERROR: $query. ".mysql_error()); $row = mysql_fetch_object($result); echo '<h3>'.$row->qtitle.'</h3>'; // reset variables unset($query); unset($result); unset($row); // find out if any votes have been cast $query = "SELECT qid, SUM(acount) AS total FROM answers GROUP BY qid HAVING qid = ".$_GET['qid']; $result = mysql_query($query) or die("ERROR: $query. ".mysql_error()); $row = mysql_fetch_object($result); $total = $row->total; // if votes have been cast if ($total > 0) { // reset variables unset($query); unset($result); unset($row); // get individual counts $query = "SELECT atitle, acount FROM answers WHERE qid = '".$_GET['qid']."'"; $result = mysql_query($query) or die("ERROR: $query. ".mysql_error()); // if records present if (mysql_num_rows($result) > 0) { // print vote results echo '<table border=1 cellspacing=0 cellpadding=15>'; // iterate through data // print absolute and percentage totals while($row = mysql_fetch_object($result)) { echo '<tr>'; echo '<td>'.$row->atitle.'</td>'; echo '<td>'.$row->acount.'</td>'; echo '<td>'.round(($row->acount/$total) * 100, 2).'%</td>'; echo '</tr>'; } // print grand total echo '<tr>'; echo '<td><u>TOTAL</u></td>'; echo '<td>'.$total.'</td>'; echo '<td>100%</td>'; echo '</tr>'; echo '</table>'; } } // if votes have not been cast else { echo 'No votes cast yet'; } // close connection mysql_close($connection); } else { die('ERROR: Data not correctly submitted'); } ?> </body> </html>
下面是输出可能看上去的样子的一个例子:
该脚本view.php以与delete.php非常相同的方式自admin.php激活(问题ID号作为输入参数传递给view.php脚本然后该ID被用于获取对应的答案以及每个问题所采集的投票)。一旦获得答案集,递交的投票总数就可以被计算出来,然后可以获得每个选项占总票数的百分比。该数据然后以简单的HTML表格显示出来。
当将绝对数据转换为百分比时,你需要小心(如果仍然没有投票的话,那么你会获得一些十分奇怪的除零错误)。为了避免这种情况,脚本中的第二个查询使用MySQL的SUM()函数和GROUP BY字句来获得特殊问题的投票总数。如果该总数为0,那么仍然没有投票,然后一条带有这种意思的消息得到显示;如果该总数大于0,那么就计算各自的百分比。
退出投票
运用事物当前建立的方式,一个用户可以为一特定的选项投票多次,因此违反了民主的基本原则之一:一个公民,一张投票。虽然不大可能有很多用户会有耐心或者爱好做这样的事情;然而,这是一个漏洞,而且应该被堵住。
我已经决定一旦成功地进行了投票就在投票者的计算机系统中建立一个cookie。对脚本增加几行代码,无论何时用户试图投票的时候,我现在可以检查该cookie的存在与否,然后据此决定是否接受该投票。
下面是代码,它被添加到user_submit.php脚本的最顶部。
<?php // check if a cookie exists for this question // deny access if it does if (isset($_COOKIE) && !empty($_COOKIE)) { if ($_COOKIE['lastpoll'] && $_COOKIE['lastpoll'] == $_POST['qid']) { die('ERROR: You have already voted in this poll'); } } // set cookie setCookie('lastpoll', $_POST['qid'], time() + 2592000); ?>
将该代码放置在合适的地方,当用户投票时,一个cookie被建立在客户端浏览器中,它包含着用户投票的问题ID号。在之后每次的投票企图中,该脚本将会首先检查cookie的存在与否,若该cookie存在,那么检查cookie变量——$_COOKIE['lastpoll']的存在与否。只有在该cookie不存在(意味着这是第一次投票)或$_COOKIE['lastpoll']的值不同于当前投票问题的ID(意味着用户之前投过票,但该ID对应的是不同的问题)时,该投票才会被接受。
这绝不是十分安全的:任意相当熟练的用户可以从客户端的缓冲区里面删除cookie然后再次投票(但它确实为进程增加了一层安全)。当然,理想的方法就是在服务器端追踪投票者的信息然后拒绝已经投过票的人投票;而且确实,如果站点在接受其在线投票之前需要用户注册一个唯一的用户名的话,那么该方法是一种可行的选择。
恩,那就是本章所讲的内容。希望该练习已经使你深入了解了如何将PHP用于建立简单的Web应用,而且解释说明了其作为Web媒体的快速开发工具的能力和灵活性。请快速回来阅读最后部分的PHP 101,以及一个更加DIY的应用程序。
