连接到MySQL
与MySQL交互时,第一步(连接到服务器)需要适当命名的mysqli_connect()函数:
$dbc = mysqli_connect (hostname, username, password, db_name);
发送给该函数的前三个参数(主机、用户名和密码)基于MySQL内建立的用户和特权(见附录A,以了解更多信息)。通常(但并非总是这样),主机值是localhost。
第四个参数是要使用的数据库的名称。这相当于在MySQL客户端内使用USE databasename命令。
如果建立了连接,$dbc变量(database connection的简写)将成为所有后续数据库交互的一个参考点。用于处理MySQL的大多数PHP函数都可以把这个变量作为它的第一个参数。
在把这些知识用于测试之前,还要学习另一个函数。如果发生一个连接问题,可以调用mysqli_connect_error(),它返回连接错误消息。它不带参数,因此只使用如下代码即可调用它:
mysqli_connect_error();
为了开始结合使用PHP和MySQL,让我们创建一个建立连接的特殊脚本。然后,需要MySQL连接的其他PHP脚本可以包含这个文件。
连接到并选择数据库
(1)在文本编辑器或IDE中创建一个新的PHP文档,命名为mysqli_connect.php(参见脚本9-2)。
脚本9-2 mysqli_connect.php脚本将被这个应用程序中的所有其他脚本使用。它建立了一条到达MySQL的连接,并选择该数据库
1 <?php # Script 9.2 – mysqli_connect.php
2
3 // This file contains the database access information.
4 // This file also establishes a connection to MySQL,
5 // selects the database, and sets the encoding.
6
7 // Set the database access information as constants:
8 DEFINE (‘DB_USER’, ‘username’);
9 DEFINE (‘DB_PASSWORD’, ‘password’);
10 DEFINE (‘DB_HOST’, ‘localhost’);
11 DEFINE (‘DB_NAME’, ‘sitename’);
12
13 // Make the connection:
14 $dbc = @mysqli_connect (DB_HOST, DB_USER, DB_PASSWORD, DB_NAME) OR die (‘Could not connect to
MySQL: ‘ . mysqli_connect_error() );
15
16 // Set the encoding…
17 mysqli_set_charset($dbc, ‘utf8’);
其他PHP脚本将包含这个文件,因此它无需包含任何HTML。
(2)将MySQL主机、用户名、密码和数据库名称设置为常量。
DEFINE (‘DB_USER’, ‘username’);
DEFINE (‘DB_PASSWORD’, ‘password’);
DEFINE (‘DB_HOST’, ‘localhost’);
DEFINE (‘DB_NAME’, ‘sitename’);
出于安全考虑,我更喜欢把这些变量创建为常量(这样,就不能更改它们),但是,这不是必需的。一般来讲,把这些值设置为某种变量或常量比较有意义,这样,你就能够把配置参数与使用它们的函数隔离开,但是,重申一遍,这不是必需的。
在编写脚本时,可以把这些值更改为处理数据库的那些值。如果已经给你提供了MySQL用户名/密码组合和一个数据库(比如用于托管站点),可以在这里使用该信息。或者,如果可能,可遵循附录A中的步骤,创建有权访问sitename数据库的用户,并在此插入那些值。无论你做什么,都不要只使用这些值,除非你确切知道它们将在服务器上工作。
(3)连接到MySQL。
$dbc = @mysqli_connect (DB_HOST, DB_USER, DB_PASSWORD, DB_NAME) OR die (‘Could not connect to MySQL: ‘ . mysqli_connect_error() );
如果成功连接到MySQL,则mysqli_connect()函数将返回对应于打开连接的资源链接。这个链接将被赋予$dbc变量,以便其他函数可以利用这个连接。
在函数调用前面放置一个错误控制运算符(@),可以防止在Web浏览器中显示PHP错误。这是一种首选的做法,因为错误将由OR die()子句处理。
如果mysqli_connect()函数不能返回有效的资源链接,那么就会执行语句的OR die()部分(因为OR的第一部分将为假,因此第二部分必须为真)。如上一章中所讨论的,die()函数会终止脚本的执行。该函数还可以取一个将打印到Web浏览器的字符串作为参数。在这种情况下,这个字符串是Could not connect to MySQL:和特定的MySQL错误的组合(参见图9-2)。在开发站点时,使用这个迟钝的错误管理系统,可以使得调试要容易得多。
图9-2 如果在连接到MySQL时有问题,就会显示包含丰富信息的消息并中止脚本
(4)将文件另存为mysqli_connect.php。
因为这个文件包含私有的信息(数据库访问数据),它将使用.php扩展名。利用.php扩展名,即使有恶意的用户在他们的Web浏览器中运行这个脚本,也不会看到页面的实际内容。
(5)将这个文件存放在Web文档目录的外面(参见图9-3)。
图9-3 服务器的Web文档的形象表示,其中没有把mysqli_connect.php存储在主目录(htdocs)内
因为这个文件包含敏感的MySQL访问信息,所以应该安全地存储它。如果可以的话,就把它放在Web目录的上一级目录中,或者把它放在Web目录的外面。这样,就不能从Web浏览器访问该文件。参见框注“组织文档”,以了解更多信息。
(6)在Web目录中临时存放一份这个脚本的副本,并在Web浏览器中运行这个脚本(参见图9-4)。
图9-4 如果MySQL连接脚本正确地工作,那么最终的结果将是一个空白页面(该脚本没有生成任何HTML)
为了测试这个脚本,把它的一份副本放在服务器上,以使得可以从Web浏览器访问它(这意味着它必须位于Web目录中)。如果脚本正确地工作,其结果应该是一个空白页面(参见图9-4)。如果看到Access denied…或者类似的消息(参见图9-2),这意味着用户名、密码和主机的组合不具有访问特定数据库的权限。
(7)从Web目录中删除临时的副本。
√提示
如果你使用的PHP版本不支持mysqli_set_charset()函数,就需要执行SET NAMES *encoding*来代替:
mysqli_query($dbc, ‘SET NAMES utf8’);
在第5章中用于登录MySQL客户端的相同值对于你的PHP脚本也应该有效。
如果接收到一个错误,声称mysqli_connect()是一个未定义的函数,这意味着没有包含对Improved MySQL Extension的支持来编译PHP。见附录A,以了解有关安装的信息。
在运行脚本时,如果看到Can’t connect…错误消息(参见图9-5),它很可能意味着MySQL没有运行。
图9-5 PHP也许不能连接到MySQL的另一个原因(除了使用无效的用户名/密码/主机名/数据库信息之外)是MySQL目前没有运行
为了满足你的好奇心,图9-6显示了如果在mysqli_connect()前面没有使用@并且发生一个错误时,会出现什么情况。
图9-6 如果没有使用错误控制运算符(@),你将同时看到PHP错误和自定义的OR die()错误
如果在建立到达MySQL的连接时不需要选择数据库,可以从mysqli_connect()函数中省略该参数:
$dbc = mysqli_connect (hostname, username, password);
然后,在合适时,可以使用以下代码选择数据库:
mysqli_select_db($dbc, db_name);
组织文档
在开发第一个Web应用程序时,我在第3章中介绍了站点结构的概念。既然页面将开始使用数据库连接脚本,这个主题就显得更重要了。
万一数据库连接信息(用户名、密码、主机和数据库)落入有恶意的人的手中,它就有可能被用于窃取信息,或者从整体上严重破坏数据库。因此,不得不极其安全地保存像mysqli_connect.php这样的脚本。
关于安全保存这样一个文件的最重要的建议是,将其存储在Web文档目录的外面。例如,如果图8-3中的htdocs文件夹是Web目录的根目录(换句话说,这里的www.example.com这个URL所导向的目录),那么不要把mysqli_connect.php存储在html目录内的任意位置,这意味着永远不能够通过Web浏览器访问它。当然,也不能够从Web浏览器查看PHP脚本的源代码(只能查看通过脚本发送到浏览器的数据),但是,也不需要过于小心。如果不允许你把文档存放在Web目录的外面,那么把mysqli_connect.php存放在Web目录中会不太安全,但是这并不是世界末日。
其次,建议为连接脚本使用.php扩展名。正确配置和工作的服务器将会执行这样一个文件中的代码,而不会显示它。与之相反,如果你只使用.inc作为扩展名,那么如果直接访问它,则会在Web浏览器中显示页面的内容。
9.3 执行简单的查询
一旦你成功地连接到并选择一个数据库,就可以开始执行查询。这些查询可以是诸如插入、更新和删除之类的基本查询,或者是返回许多行的复杂联结中所涉及的查询。无论如何,用于执行查询的PHP函数都是mysqli_query():
result = mysqli_query(dbc, query);
该函数获取数据库连接作为它的第一个参数,并把查询本身作为第二个参数。我通常会把查询赋予另一个变量$query(或者简称为$q)。因此,可运行如下查询:
$r = mysqli_query($dbc, $q);
对于像INSERT、UPDATE、DELETE等简单的查询(它们不会返回记录),$r变量(result的简写)将返回TRUE或FALSE,这取决于查询是否执行成功。记住,“执行成功”意味着它没有错误地运行,并不意味着它必须具有想要的结果,你将需要对此进行测试。
对于确实会返回记录的复杂查询(SELECT、SHOW、DESCRIBE和EXPLAIN),如果查询有效,则$r变量将是一个指向查询结果的资源链接;如果查询无效,则$r变量将为FALSE。因此,可以在条件语句中使用这一行代码测试查询是否成功运行:
$r = mysqli_query ($dbc, $q);
if ($r) { // Worked!
如果查询没有成功运行,必定会发生某种MySQL错误。为了查明错误是什么,可以调用mysqli_error()函数:
echo mysqli_error($dbc);
你的脚本中的最后(虽然是可选的)一步是,一旦使用完现有的MySQL连接,就要关闭它:
mysqli_close($dbc);
这个函数不是必需的,因为在脚本的末尾,PHP会自动关闭连接,但是纳入这个函数确实是一种良好的编程风格。
为了演示这个过程,让我们创建一个注册脚本,在第一次访问时它将显示表单(参见图9-7),处理表单提交,并在验证所有的数据之后把注册信息插入到sitename数据库的users表中。
图9-7 注册表单
执行简单的查询
(1)在文本编辑器或IDE中创建一个新的PHP脚本,命名为register.php(参见脚本9-3)。
脚本9-3 这个注册脚本通过运行INSERT查询向数据库中添加一条记录
1 <?php # Script 9.3 – register.php
2 // This script performs an INSERT query to add a record to the users table.
3
4 $page_title = ‘Register’;
5 include (‘includes/header.html’);
6
7 // Check for form submission:
8 if ($_SERVER[‘REQUEST_METHOD’] == ‘POST’) {
9
10 $errors = array(); // Initialize an error array.
11
12 // Check for a first name:
13 if (empty($_POST[‘first_name’])) {
14 $errors[] = ‘You forgot to enter your first name.’;
15 } else {
16 $fn = trim($_POST[‘first_name’]);
17 }
18
19 // Check for a last name:
20 if (empty($_POST[‘last_name’])) {
21 $errors[] = ‘You forgot to enter your last name.’;
22 } else {
23 $ln = trim($_POST[‘last_name’]);
24 }
25
26 // Check for an email address:
27 if (empty($_POST[’email’])) {
28 $errors[] = ‘You forgot to enter your email address.’;
29 } else {
30 $e = trim($_POST[’email’]);
31 }
32
33 // Check for a password and match against the confirmed password:
34 if (!empty($_POST[‘pass1’])) {
35 if ($_POST[‘pass1’] != $_POST[‘pass2’]) {
36 $errors[] = ‘Your password did not match the confirmed password.’;
37 } else {
38 $p = trim($_POST[‘pass1’]);
39 }
40 } else {
41 $errors[] = ‘You forgot to enter your password.’;
42 }
43
44 if (empty($errors)) { // If everything’s OK.
45
46 // Register the user in the database…
47
48 require (‘../mysqli_connect.php’); // Connect to the db.
49
50 // Make the query:
51 $q = “INSERT INTO users (first_name, last_name, email, pass, registration_date) VALUES
(‘$fn’, ‘$ln’, ‘$e’, SHA1(‘$p’), NOW() )”;
52 $r = @mysqli_query ($dbc, $q); // Run the query.
53 if ($r) { // If it ran OK.
54
55 // Print a message:
56 echo ‘<h1>Thank you!</h1>
57 <p>You are now registered. In Chapter 12 you will actually be able to log in!</p><p> <br
/></p>’;
58
59 } else { // If it did not run OK.
60
61 // Public message:
62 echo ‘<h1>System Error</h1>
63 <p class=”error”>You could not be registered due to a system error. We apologize for any
inconvenience.</p>’;
64
65 // Debugging message:
66 echo ‘<p>’ . mysqli_error($dbc) . ‘<br /><br />Query: ‘ . $q . ‘</p>’;
67
68 } // End of if ($r) IF.
69
70 mysqli_close($dbc); // Close the database connection.
71
72 // Include the footer and quit the script:
73 include (‘includes/footer.html’);
74 exit();
75
76 } else { // Report the errors.
77
78 echo ‘<h1>Error!</h1>
79 <p class=”error”>The following error(s) occurred:<br />’;
80 foreach ($errors as $msg) { // Print each error.
81 echo ” – $msg<br />\n”;
82 }
83 echo ‘</p><p>Please try again.</p><p><br /></p>’;
84
85 } // End of if (empty($errors)) IF.
86
87 } // End of the main Submit conditional.
88 ?>
89 <h1>Register</h1>
90 <form action=”register.php” method=”post”>
91 <p>First Name: <input type=”text” name=”first_name” size=”15″ maxlength=”20″ value=”<?php if
(isset($_POST[‘first_name’])) echo $_POST[‘first_name’]; ?>” /></p>
92 <p>Last Name: <input type=”text” name=”last_name” size=”15″ maxlength=”40″ value=”<?php if
(isset($_POST[‘last_name’])) echo $_POST[‘last_name’]; ?>” /></p>
93 <p>Email Address: <input type=”text” name=”email” size=”20″ maxlength=”60″ value=”<?php if
(isset($_POST[’email’])) echo $_POST[’email’]; ?>” /> </p>
94 <p>Password: <input type=”password” name=”pass1″ size=”10″ maxlength=”20″ value=”<?php if
(isset($_POST[‘pass1’])) echo $_POST[‘pass1’]; ?>” /></p>
95 <p>Confirm Password: <input type=”password” name=”pass2″ size=”10″ maxlength=”20″ value=
“<?php if (isset($_POST[‘pass2’])) echo $_POST[‘pass2’]; ?>” /></p>
96 <p><input type=”submit” name=”submit” value=”Register” /></p>
97 </form>
98 <?php include (‘includes/footer.html’); ?>
这个脚本的基本内容(使用包含文件、具有显示和处理表单的相同页面以及创建黏性表单)来自于第3章。如果你对其中任何概念感到混淆,可参见第3章中的相应内容。
注意:如果在某个表单值中使用撇号,它很可能中断查询(参见图9-11)。9.5节确保SQL安全将介绍如何防止这一点。
√提示
在运行脚本之后,通过使用MySQL客户端或phpMyAdmin来查看users表中的值,你总是可以确保它将会工作。
在PHP中,不应该用分号结束查询,就像你使用MySQL客户端时所做的那样。当使用MySQL时,这是一个经常(虽然不会造成损害)犯的错误。当使用其他的数据库应用程序(例如,Oracle)时,这样做会使查询不起作用。
提醒一下,如果能够在数据库上无错地执行查询,mysqli_query()函数就会返回TRUE。这不一定意味着查询的结果就是你所期待的。后面一些脚本将演示如何更准确地度量查询的成功执行。
不必完全像我所做的那样创建一个$q变量(可以直接把查询文本插入到mysqli_query()中)。不过,随着查询的构造变得更复杂,使用变量将是唯一的选择。
实际上,也可以使用mysqli_query()来执行将在MySQL客户端中运行的任何查询。
Improved MySQL Extension超过标准扩展的另一个好处是:mysqli_multi_query()函数允许你同时执行多个查询。这样做的语法更复杂一点,尤其是当查询返回结果时,如果你有这种需要,可以查看PHP手册。
9.4 检索查询结果
在本章的前一节中,讨论并演示了如何在MySQL数据库上执行简单的查询。可以把简单的查询(我这样称呼它)定义为以INSERT、UPDATE、DELETE或ALTER开头的一个查询。所有这4种查询的一个共同点是,它们不返回任何数据,而只返回一个它们成功执行的指示。与之相反,SELECT查询会生成必须由其他PHP函数处理的信息(即它会返回几行记录)。
处理SELECT查询结果的主要工具是mysqli_fetch_array(),它带有一个查询结果变量(我曾称为$r),并以数组格式一次返回一行数据。你将希望在一个循环内使用这个函数,只要有更多的行,循环就会持续访问返回的每一行。从查询中读取每条记录的基本构造如下:
while ($row = mysqli_fetch_array($r)) {
// Do something with $row.
}
你几乎总是希望使用while循环从SELECT查询中获取结果。
mysqli_fetch_array()函数带有一个可选的参数,用于指定返回的数组的类型:关联数组、索引数组,或者这两者。关联数组允许通过名称引用列值,而索引数组则要求只使用数字(对于返回的第一列,从0开始)。每个参数都是通过表9-1中列出的常量定义的。与其他选项相比,MYSQLI_NUM设置要快那么一点点(并且使用更少的内存)。与之相反,MYSQLI_ASSOC更明确($row[‘column’]比$row[3]更明确),即使表结构或查询发生变化,它也会继续工作。
表9-1 mysqli_fetch_array()常量
常量 示例
MYSQLI_ASSOC $row[‘column’]
MYSQLI_NUM $row[0]
MYSQLI_BOTH $row[0]或$row[‘column’]
使用mysqli_fetch_array()函数时,可以采取的一个可选的步骤是:一旦使用查询结果信息完成了工作,即可释放这些信息:
mysqli_free_result ($r);
这一行会消除$r占用的系统开销(内存)。该步骤是可选的,因为PHP将在脚本末尾自动释放资源,但是就像使用mysqli_close()一样,这是一种良好的编程风格。
为了演示如何处理查询返回的结果,将创建一个脚本,用于查看当前注册的所有用户。
检索查询结果
(1)在文本编辑器或IDE中创建一个新的PHP文档,命名为view_users.php(参见脚本9-4)。
脚本9-4 view_users.php脚本在数据库上运行一个静态查询,并打印出返回的所有行
1 <?php # Script 9.4 – view_users.php
2 // This script retrieves all the records from the users table.
3
4 $page_title = ‘View the Current Users’;
5 include (‘includes/header.html’);
6
7 // Page header:
8 echo ‘<h1>Registered Users</h1>’;
9
10 require (‘../mysqli_connect.php’); // Connect to the db.
11
12 // Make the query:
13 $q = “SELECT CONCAT(last_name, ‘, ‘, first_name) AS name, DATE_FORMAT(registration_date, ‘%M %d,
%Y’) AS dr FROM users ORDER BY registration_date ASC”;
14 $r = @mysqli_query ($dbc, $q); // Run the query.
15
16 if ($r) { // If it ran OK, display the records.
17
18 // Table header.
19 echo ‘<table align=”center” cellspacing=”3″ cellpadding=”3″ width=”75%”>
20 <tr><td align=”left”><b>Name</b></td><td align=”left”><b>Date Registered</b></td></tr>
21 ‘;
22
23 // Fetch and print all the records:
24 while ($row = mysqli_fetch_array($r, MYSQLI_ASSOC)) {
25 echo ‘<tr><td align=”left”>’ . $row[‘name’] . ‘</td><td align=”left”>’ . $row[‘dr’] .
‘</td></tr>
26 ‘;
27 }
28
29 echo ‘</table>’; // Close the table.
30
31 mysqli_free_result ($r); // Free up the resources.
32
33 } else { // If it did not run OK.
34
35 // Public message:
36 echo ‘<p class=”error”>The current users could not be retrieved. We apologize for any
inconvenience.</p>’;
37
38 // Debugging message:
39 echo ‘<p>’ . mysqli_error($dbc) . ‘<br /><br />Query: ‘ . $q . ‘</p>’;
40
41 } // End of if ($r) IF.
42
43 mysqli_close($dbc); // Close the database connection.
44
45 include (‘includes/footer.html’);
46 ?>
这里的查询将返回两列(参见图9-12):用户姓名(格式化为Last Name, First Name)和他们的注册日期(格式化为Month DD, YYYY)。由于使用MySQL函数来格式化这两列,所以会把别名提供给返回的结果(相应地为name和dr)。如果你对这种语法的任何方面感到糊涂,参见第5章。
接下来,使用mysqli_fetch_array()遍历结果,并打印每个获取的行。注意,在while循环中,引用返回值的代码使用了特有的别名:$row[‘name’]和$row[‘dr’]。脚本不能引用$row[‘first_name’]或$row[‘date_registered’],因为没有返回这样的字段名(参见图9-12)。
9.5 确保SQL安全
关于PHP的数据库安全可归结为三大类问题:
(1)保护MySQL访问信息;
(2)不要呈现关于数据库的过多信息;
(3)在运行查询时要小心谨慎,对于那些涉及用户提交数据的查询尤其需要这样。
可以通过确保Web目录外面的MySQL连接脚本的安全来达到第一个目标,这样,永远都不能通过Web浏览器查看到它(参见图9-3)。在本章前面比较详细地讨论了这一点。通过不允许用户查看PHP的出错消息或者你的查询来达到第二个目标(在这些脚本中,将出于调试目的打印出这些信息;在活动站点上永远都不要想这样做)。
对于第三个目标,可以并且应该采取许多步骤来达到,它们都基于永远不要信任用户提供的数据这个前提。第一,验证提交了某个值,或者验证它是否具有正确的类型(数字、字符串等)。第二,使用正则表达式确保提交的数据与你所期待的匹配(在第14章中介绍了这个主题)。第三,可以强制转换某些值的类型以保证它们是数字(在第13章中讨论了这个主题)。第四,通过mysqli_real_escape_string()函数处理用户提交的数据。这个函数通过转义那些可能有问题的字符来清理数据,其用法如下:
$safe = mysqli_real_escape_string ($dbc, data);
要理解为什么这是必须的,请参见图9-11。在用户的姓氏中使用的单引号使查询语法失效:
INSERT INTO users (first_name, last_name, email, pass, registration_date) VALUES (‘Peter’, ‘O’Toole’,
‘pete@example.net’, SHA1(‘aPass8’), NOW() )
在这个特殊的例子中,有效的用户数据破坏查询,这是不对的。但是,如果你的PHP脚本允许这种可能性,恶意用户就有可能提交有问题的字符(撇号就是一个例子),侵入或破坏你的数据库。为了安全起见,应该为表单中的所有文本输入使用mysqli_real_escape_string()函数。为了说明这一点,修改register.php(脚本9-3)。
因为mysqli_real_escape_string()函数需要一条数据库连接,所以在脚本中调用这个函数之前,必须有mysqli_connect.php脚本。
更改验证例程,以使用mysqli_real_escape_string()函数,用$var = mysqli_real_escape_string($dbc, trim($_POST[‘var’]))代替出现的每个$var = trim($_POST[‘var’])。
$fn = mysqli_real_escape_string ($dbc, trim($_POST[‘first_name’]));
$ln = mysqli_real_escape_string ($dbc, trim($_POST[‘last_name’]));
$e = mysqli_real_escape_string ($dbc, trim($_POST[’email’]));
$p = mysqli_real_escape_string ($dbc, trim($_POST[‘pass1’]));
这样做不是只把提交的值赋予每个变量($fn、$ln等),而是首先用mysqli_real_escape_string()函数处理这些值。仍然使用trim()函数删除任何不必要的空格。
√提示
mysqli_real_escape_string()函数依照所用的语言对字符串进行转义,这是它超过替代解决方案的另外一个优点。
如果看到了类似于图9-16中的那些结果,则意味着mysqli_real_escape_string()函数不能访问数据库(因为它没有连接,如$dbc)。
图9-16 由于mysqli_real_escape_string()函数需要一条数据库连接,如果在没有连接的情况下(例如,在包括连接脚本之前)使用它,则可能导致其他错误
如果在服务器上启用Magic Quotes,那么在使用mysqli_real_escape_string()函数之前,需要删除Magic Quotes添加的任何斜杠。代码(看上去有些麻烦)如下:
$fn = mysqli_real_escape_string ($dbc, trim(stripslashes($_POST[‘first_name’])));
如果没有使用stripslashes()并且启用了Magic Quotes,将对表单值进行双重转义。
如果你看一下存储在数据库中(使用MySQL客户端,phpMyAdmin的或其他工具)的值,你将不会看到前面带有反斜杠的撇号和其他有问题的字符,这是正确的。反斜杠会阻止有问题的字符破坏查询,但是反斜杠本身并不会被存储。
9.6 统计返回的记录
要讨论的下一个逻辑函数是mysqli_num_rows()。这个函数返回SELECT查询检索的行数,并将查询结果变量作为一个参数:
$num = mysqli_num_rows($r);
尽管有意使其简单,但是这个函数确实非常有用。如果你想分页显示查询结果(可以在下一章中找到这样的一个示例),就需要使用它。另外,在尝试使用while循环获取任何结果之前,最好也使用这个函数(因为如果没有结果的话,就无需获取它,并且尝试这样做可能会引发错误)。在下面的步骤序列中,让我们修改view_users.php来列出注册用户的总数。参见框注“修改register.php”,可以看到使用mysqli_num_rows()的另一个示例。
修改register.php
可以对register.php应用mysqli_num_rows()函数,防止某个人用相同的电子邮件地址注册多次。尽管数据库中那一列上的UNIQUE索引将会阻止这种情况的发生,但这种尝试还是会产生一个MySQL错误。为了使用PHP阻止这种情况,可以运行一个SELECT查询确认电子邮件地址目前尚未注册。可以将该查询简单地编写为:
SELECT user_id FROM users WHERE email=’$e’
你将运行这个查询(使用mysqli_query()函数),然后调用mysqli_num_rows()。如果mysqli_num_rows()返回0,你就知道电子邮件地址尚未注册,并且可以安全地运行INSERT查询。
9.7 利用PHP更新记录
本章中介绍的最后一种技术说明了如何利用PHP脚本来更新数据库记录。这需要使用UPDATE查询,并且可以利用PHP的mysqli_affected_rows()函数来验证查询的成功执行。
虽然mysqli_num_rows()函数会返回SELECT查询生成的行数,但是mysqli_affected_rows()函数会返回受INSERT、UPDATE或DELETE查询影响的行数。其用法如下:
$num = mysqli_affected_rows($dbc);
与mysqli_num_rows()函数不同的是,这个函数所带的一个参数是数据库连接($dbc),而不是前一个查询的结果($r)。
下面的示例是一个脚本,允许注册用户更改他们的密码。它将演示两个重要的思想:
针对注册值检查提交的用户名和密码(以及登录系统的关键条件);
通过把主键用作参考点来更新数据库记录。
与注册示例一样,这个PHP脚本将显示表单(参见图9-18)并处理它。
图9-18 用于更改用户密码的表单并处理它
利用PHP更新记录
(1)在文本编辑器或IDE中创建一个新的PHP脚本,命名为password.php(参见脚本9-7)。
脚本9-7 password.php脚本在数据库上运行一个UPDATE查询,并使用mysqli_affected_rows()函数来确认更改
1 <?php # Script 9.7 – password.php
2 // This page lets a user change their password.
3
4 $page_title = ‘Change Your Password’;
5 include (‘includes/header.html’);
6
7 // Check for form submission:
8 if ($_SERVER[‘REQUEST_METHOD’] == ‘POST’) {
9
10 require (‘../mysqli_connect.php’); // Connect to the db.
11
12 $errors = array(); // Initialize an error array.
13
14 // Check for an email address:
15 if (empty($_POST[’email’])) {
16 $errors[] = ‘You forgot to enter your email address.’;
17 } else {
18 $e = mysqli_real_escape_string($dbc, trim($_POST[’email’]));
19 }
20
21 // Check for the current password:
22 if (empty($_POST[‘pass’])) {
23 $errors[] = ‘You forgot to enter your current password.’;
24 } else {
25 $p = mysqli_real_escape_string($dbc, trim($_POST[‘pass’]));
26 }
27
28 // Check for a new password and match
29 // against the confirmed password:
30 if (!empty($_POST[‘pass1’])) {
31 if ($_POST[‘pass1’] != $_POST[‘pass2’]) {
32 $errors[] = ‘Your new password did not match the confirmed password.’;
33 } else {
34 $np = mysqli_real_escape_string($dbc, trim($_POST[‘pass1’]));
35 }
36 } else {
37 $errors[] = ‘You forgot to enter your new password.’;
38 }
39
40 if (empty($errors)) { // If everything’s OK.
41
42 // Check that they’ve entered the right email address/password combination:
43 $q = “SELECT user_id FROM users WHERE (email=’$e’ AND pass=SHA1(‘$p’) )”;
44 $r = @mysqli_query($dbc, $q);
45 $num = @mysqli_num_rows($r);
46 if ($num == 1) { // Match was made.
47
48 // Get the user_id:
49 $row = mysqli_fetch_array($r, MYSQLI_NUM);
50
51 // Make the UPDATE query:
52 $q = “UPDATE users SET pass=SHA1(‘$np’) WHERE user_id=$row[0]”;
53 $r = @mysqli_query($dbc, $q);
54
55 if (mysqli_affected_rows($dbc) == 1) { // If it ran OK.
56
57 // Print a message.
58 echo ‘<h1>Thank you!</h1>
59 <p>Your password has been updated. In Chapter 12 you will actually be able to log
in!</p><p><br /></p>’;
60
61 } else { // If it did not run OK.
62
63 // Public message:
64 echo ‘<h1>System Error</h1>
65 <p class=”error”>Your password could not be changed due to a system error. We apologize
for any inconvenience.</p>’;
66
67 // Debugging message:
68 echo ‘<p>’ . mysqli_error($dbc) . ‘<br /><br />Query: ‘ . $q . ‘</p>’;
69
70 }
71
72 mysqli_close($dbc); // Close the database connection.
73
74 // Include the footer and quit the script (to not show the form).
75 include (‘includes/footer.html’);
76 exit();
77
78 } else { // Invalid email address/password combination.
79 echo ‘<h1>Error!</h1>
80 <p class=”error”>The email address and password do not match those on file.</p>’;
81 }
82
83 } else { // Report the errors.
84
85 echo ‘<h1>Error!</h1>
86 <p class=”error”>The following error(s) occurred:<br />’;
87 foreach ($errors as $msg) { // Print each error.
88 echo ” – $msg<br />\n”;
89 }
90 echo ‘</p><p>Please try again.</p><p><br /></p>’;
91
92 } // End of if (empty($errors)) IF.
93
94 mysqli_close($dbc); // Close the database connection.
95
96 } // End of the main Submit conditional.
97 ?>
98 <h1>Change Your Password</h1>
99 <form action=”password.php” method=”post”>
100 <p>Email Address: <input type=”text” name=”email” size=”20″ maxlength=”60″ value=”<?php if
(isset($_POST[’email’])) echo $_POST[’email’]; ?>” /> </p>
101 <p>Current Password: <input type=”password” name=”pass” size=”10″ maxlength=”20″ value=
“<?php if (isset($_POST[‘pass’])) echo $_POST[‘pass’]; ?>” /></p>
102 <p>New Password: <input type=”password” name=”pass1″ size=”10″ maxlength=”20″ value=”<?php
if (isset($_POST[‘pass1’])) echo $_POST[‘pass1’]; ?>” /></p>
103 <p>Confirm New Password: <input type=”password” name=”pass2″ size=”10″ maxlength=”20″ value=”<?php if (isset($_POST[‘pass2’])) echo $_POST[‘pass2’]; ?>” /></p>
104 <p><input type=”submit” name=”submit” value=”Change Password” /></p>
105 </form>
106 <?php include (‘includes/footer.html’); ?>
√提示
如果使用命令TRUNCATE tablename从表中删除所有记录,则mysqli_affected_rows()会返回0,即使查询成功执行并且删除了每一行。这只是一种古怪的行为。
如果运行UPDATE查询,但是实质上没有更改任意列的值(例如,用相同的密码代替一个密码),则mysqli_affected_rows()会返回0。
这里使用的mysqli_affected_rows()条件语句也可以应用于register.php脚本,以确认是否添加了一条记录。与检查if($r)相比,这个条件更严格。