### 《PHP 程序设计》 第五章 中场一：数据库连接

第五章 中场一：数据库连接
前两章中集中介绍了 PHP 语言，现在停下来开始创建一个应用程序。在本章中将创建一个连接 MySQL 数据库的应用程序。
在学习过前面两章之后，你肯定已经学会了怎样处理 PHP 内部数据，以及怎样编写语句和函数。下一步从逻辑上讲，应该到了学习如何用 SQL（结构化查询语句）处理 PHP 的外部数据的时候了。但是，在开始学习这部分内容前，让我们暂时中断基本原理的学习，放松一下。
让我带你一道探究 PHP 应用程序的开发历程。从字面意义上讲，每一个应用程序都应该是独特的，但是每一个应用程序也都能在此前的工作基础上，即一系列通用功能的基础上构建。我建议将这两项技术混合在一起。盲目地使用前人已编写好的功能，会剥夺在程序中加入新特色，还会阻止为提高函数的效率而去修改旧函数。从另一方面讲，使用已有函数意味着能更快的开发出应用程序。所以必须在这两个极端中把握好自己，才能成为一个优秀的程序设计员。
注意：如果现在还对 HTML 不熟悉，那么现在就是开始学习的时间了。本书中假定你已经熟悉 HTML 了。如果还不熟悉 HTML 表格和表单的话，很快就会被搞糊涂的。
5.1 开端
每当开始一个新项目时，我喜欢从一个新的空目录开始。在这里，让我们把这个目录称为 phpbook/ch05。当然，这个目录必须在 Web 服务器的根目录下。如果你是按照第一章的指令安装 PHP 的话，那么，Web 服务器的根目录就应该为/usr/local/apache/htdocs。接着，我们将创建一个名为 menu.php3 的文件，其中包含有一个后台管理任务菜单，如清单 5.1 所示。
清单 5.1 menu.php3
<?php require('common.inc'); ?>
<?php affy_header('Administrative Menu') ?>
<h1>Administrative Menu</h1>
<ol>
<li><a href="connect.php3">Creat Database
Connection</a></li>
</ol>
<?php affy_footer() ?>
在文件 common.inc 中包含对函数 affy_header 和 affy_footer 的定义。这些函数在本章中稍后还会出现。
5.2 创建连接
当点击 Create Database Connect（创建数据库连接）联接时，将会执行 connect.php3 文件，该文件将尝试连接在第二章学习中安装的 MySQL 数据库服务器。
清单 5.2 显示了 connect.php3 文件使用用户名 codebits 和密码 codebits 尝试进行数据库连接，因为在安装 MySQL 时还没有创建该用户名，所以连接肯定失败。然而失败 -- 最起码在本例中 -- 却是一件好事，因为我们可以看看应该如何处理这个问题。图 5.1 给出了连接失败后将会显示的错误信息和表单。
清单 5.2 connect.php3
Page 107 -108 清单 5.2
Page 108 Figure 5.1
图 5.1 连接失败时的错误信息显示
当数据库连接失败时，程序会给出一个错误信息提示和一个表单，用户可以在里面输入 root 用户的密码。就像在本章稍后讲的那样，有了 root 的密码，就可以创建名为 codebits 的用户。现在先跳过有关$arr_request 数组的部分。
当函数 mysql_connect 被调用且连接失败时，该函数通常会显示如下信息：
Warnint: MySQL connection Failed: Access denied
for user: 'codebits@localhost' (Using password: YES)
绝大多数应用程序需要精确的控制显示的内容，尤其是高度图形化的应用程序。在函数 mysql_connect 前加上(@)符号将会抑制错误信息的显示。
注意表单语句的 action 属性指定点击 submit 按钮时，将会执行 connect.php3 文件。这是一个递归程序的例子，也就是说允许 PHP 文件调用它自己。
应用递归编程技术，可以将有关同一个主题的所有代码编制在同一个文件中。至于什么时候应该将函数组合成一个文件，或将程序分解成几个文件，这得凭经验。我的首要原则是：当实现一个特定功能所编的程序代码超过 100 行以上时，就要创建一个独立的文件。
5.3 获取 HTML 表单信息
即使输入一个密码并点击连接数据库,连接仍然会失败，原因是 connect.php3 还没有使用表单中的输入值去建立数据库连接。
PHP 引擎将每一个表单域放到一个叫做$HTTP_POST_VARS 的数组中。在上述给出的例子中，数组有两个元素：username 和 password。在此程序中可以通过$HTTP_POST_VARS['username']和$HTTP_POST_VARS['password']访问表单信息。
使用$HTTP_POST_VARS['password']获得表单中的信息看起来比较简单。但是仍有一些隐藏的问题。首先，要检查表单域的名字（本例中的 password）是大写、小写、还是大小写都有。
第二件问题包含的内容与本例关系不大。除了表单方法以外，还可以使用 URL 来运行 PHP 脚本，例如：
http://.../connect.php3?username=root&password=password
可以看到，用户名和密码通过 URL 进行传递，问号“？”标志着域信息的开始，“&”则是域的定界符。幸运的是，PHP 引擎也自动分析 URL 行，并将结果存入$HTTP_GET_VARS 数组中。
问题（如果你认为它是的话）在于，程序可以从不止一个地方获得信息 -- 数组$HTTP_GET_VARS 和数组 $HTTP_POST_VARS。
对待这些（或其它一些的）问题，我的解决办法是创建一个名为$arr_request 的数组，它从两个$HTTP 数组中获得初始化的信息。在 common.inc 中可以使用如下编码行对数组$arr_repuest 进行数值初始化。
// declare the request array which holds both
// url-based (get) and form-based (post) parameters.
$arr_request = array();
// move the url and form parameters into the
// request array. Form parameters supercede url
// parameters. Additionally, all keys are vonverted
// to lower-case.
If (count($HTTP_GET-VARS)) {
While (list($key, $value) = each ($HTTP_GET_VARS)) {
$arr_request[strtolower($key)] = $value;
}
}
if (count($HTTP_POST_VARS)) {
while (list($key, $value) = each ($HTTP_POST_VARS)) {
$arr_request[strtolower($key)] = $value;
}
}
如果在所有的 PHP 脚本中都包含有 common.inc 文件的话，那么不用担心脚本是怎么运行的。所有传过去的信息都以小写形式保存在数组$arr_request 中，这就意味着，可以使用$arr_request['username']得到用户名信息。
PHP 提供了数组$HTTP_GET_VARS 和数组$HTTP_POST_VARS 的替代方式，HTML 表单和基于 URL 的信息都可以直接做为 PHP 变量进行访问。例如，在 PHP 脚本中，一个定义为<input type = "input" name="last_name">的域信息可以直接在 PHP 程序中用$last_name 访问，同样的基于 URL 的信息，比方说，http://www.site.com?last_name=join，能由$last_name 获得。不过，我还是比较喜欢使用数组$arr_request,因为对于要循环使用传递给程序的所有信息来讲，这是非常有用的。如果该信息是一个标量，那么它就不适合被循环使用。例如：将所有参数名改为大写，以保证不致于因为使用换档键而破坏程序；或者在错误检测时，会需要显示所有的输入参数。
注意：本节只对 CGI（通用网关接口）协议作很简单的介绍，更详细的内容请参阅本书附录 A，“因特网资源”中所列内容。
5.4 使用 HTML 表单信息
既然可以很容易地从 PHP 脚本程序中存取表单信息，现在是利用这些信息连接数据库的时间了。第一步是检查连接数据库的代码：
$id_link = @mysql_connect('localhost', 'affy', 'affy');
在这行代码中，用户名和密码都是字符串数值。为了能利用表单中的信息，这行代码需要加以变动，用变量代替数值：
$id_link = @mysql_connect(
'localhost',
$username,
$password);
既然用到了变量，那么必须对变量进行初始化。以下代码将执行这种初始化：
if ( count($arr_request) ) {
$username = $arr_request['username'];
$password = $arr_request['password'];
}
else {
$username = 'phpuser';
$password = 'phpuser';
}
当表单信息可用时，函数 count 的结果将大于 1，使得 if 语句执行真条件的子句，此子句依次从$arr_request 数组中取出用户名和密码信息。
当没有表单信息存在时，用户名和密码仍可以用字符串数值进行初始化。
第三种可能性是一个表单有这两个域但没有表单信息。如果调用 connect.php3 的表单没有 username 和 password 域，会发生什么情况呢？如果这样，以上代码将失败。通过直接检查表单字段，而不是只依赖于$arr_request 数组的元素数目可以让这段代码的更强壮（即，能处理这种环境下的失败）。例如：
$username = $arr_request['username'];
$password = $arr_request['password'];
if (empty($username)) $username = 'phpuser';
if (empty($password)) $password = 'phpuser';
因为 PHP 对没有初始化的数组元素将返回空字符串，以上代码的适应性将更强。使用标量比使用数组可以使代码更容易理解，并且在某些方面更有效率。如果这两个变量有一个为空时，这意味着表单没有提供任何值，将使用缺省值。
清单 5.3 显示了有以上更改的 connect.php3 文件，可以在上下文看到这两个变化的描述。
清单 5.3 connect.php3 修订版
Page 112 - 113 清单 5.3
当正确的 root 密码如图 5.2 所示被输入到表单时，将会成功地建立数据库连接。
Page 113, Figure 5.2
图 5.2 证实成功地建立了数据库连接
5.5 common.inc 文件
清单 5.4 显示了本章需要的 common.inc 文件的版本。
清单 5.4 common.inc--多个应用程序使用的一套例程。
<?php
function affy_footer() {
echo '</body></html>';
}
function affy_header($title) {
echo '<html><head><title>';
echo "$title";
echo '</title></head><body>';
}
function affy_message($msg) {
echo '<table>';
echo '<tr><td>';
echo "$msg";
echo '</td></tr>';
echo '</table>';
}
?>
5.6 总结
本章介绍了如何使用 PHP 连接数据库，第一步创建了一个 Web 菜单页，用它可以运行连接数据库的脚本，然后创建了可以连接数据库的 connect.php3 文件。
当 connect.php3 文件使用 affy 用户名企图连接数据库时失败了，从而导致显示出了需要 root 用户密码的表单。
表单中的信息通过 common.inc 文件被放在$arr_request 数组中。并修改了 connect.php3 文件，使其使用变量$arr_request 中的用户名和密码来连接数据库。