PHP和MySQL

连接到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)相比,这个条件更严格。