PHP MySQL Tutorial
撰写时间:2023-05-01
修订时间:2023-06-25
本节使用mysqli
接口来连接MySQL数据库。它以面向对象的方式来与MySQL交互。
MAMP
MAMP使用脚本文件来启动及关闭MySQL,位于/Applications/MAMP/bin路径下面。
启动MySQL的文件名为startMysql.sh,内容如下:
#!/bin/sh
/Applications/MAMP/Library/bin/mysqld_safe --port=3306 --socket=/Applications/MAMP/tmp/mysql/mysql.sock --pid-file=/Applications/MAMP/tmp/mysql/mysql.pid --log-error=/Applications/MAMP/logs/mysql_error.log &
关闭MySQL的文件名为stopMysql.sh,内容如下:
#!/bin/sh
/Applications/MAMP/Library/bin/mysqladmin -u <USERNAME> -p<USER_PASSWORD> --socket=/Applications/MAMP/tmp/mysql/mysql.sock shutdown
MAMP使用MySQL的配置文件为/Applications/MAMP/tmp/mysql/my.cnf。
MySQL的安装路径
可使用whereis mysql
来查看MySQL的安装路径。在笔者的Linux系统中,它安装在/etc/mysql路径下。
MySQL的的启动与关闭
启动:
sudo mysqld
关闭:
mysqladmin -uroot -p shutdown
MySQL中文字符问题
查看MySQL中文字符设置
在终端输入:
mysql -uroot -p
mysql是一个运行在终端窗口中的交互程序,可用于连接MySQL服务器,查询并查看结果。
进入mysql终端程序后,运行:
SHOW VARIABLES LIKE '%char%';
将列出所有与字符相关的变量。如:
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | latin1 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
从上面可看出,数据库及服务器的字符均为latin1
。
查看可用的字符集及其对应的collation:
SHOW CHRACTER SET;
显示:
+----------+---------------------------------+---------------------+--------+
| Charset | Description | Default collation | Maxlen |
+----------+---------------------------------+---------------------+--------+
| big5 | Big5 Traditional Chinese | big5_chinese_ci | 2 |
| dec8 | DEC West European | dec8_swedish_ci | 1 |
| cp850 | DOS West European | cp850_general_ci | 1 |
| hp8 | HP West European | hp8_english_ci | 1 |
| koi8r | KOI8-R Relcom Russian | koi8r_general_ci | 1 |
| latin1 | cp1252 West European | latin1_swedish_ci | 1 |
| latin2 | ISO 8859-2 Central European | latin2_general_ci | 1 |
| swe7 | 7bit Swedish | swe7_swedish_ci | 1 |
| ascii | US ASCII | ascii_general_ci | 1 |
| ujis | EUC-JP Japanese | ujis_japanese_ci | 3 |
| sjis | Shift-JIS Japanese | sjis_japanese_ci | 2 |
| hebrew | ISO 8859-8 Hebrew | hebrew_general_ci | 1 |
| tis620 | TIS620 Thai | tis620_thai_ci | 1 |
| euckr | EUC-KR Korean | euckr_korean_ci | 2 |
| koi8u | KOI8-U Ukrainian | koi8u_general_ci | 1 |
| gb2312 | GB2312 Simplified Chinese | gb2312_chinese_ci | 2 |
| greek | ISO 8859-7 Greek | greek_general_ci | 1 |
| cp1250 | Windows Central European | cp1250_general_ci | 1 |
| gbk | GBK Simplified Chinese | gbk_chinese_ci | 2 |
| latin5 | ISO 8859-9 Turkish | latin5_turkish_ci | 1 |
| armscii8 | ARMSCII-8 Armenian | armscii8_general_ci | 1 |
| utf8 | UTF-8 Unicode | utf8_general_ci | 3 |
| ucs2 | UCS-2 Unicode | ucs2_general_ci | 2 |
| cp866 | DOS Russian | cp866_general_ci | 1 |
| keybcs2 | DOS Kamenicky Czech-Slovak | keybcs2_general_ci | 1 |
| macce | Mac Central European | macce_general_ci | 1 |
| macroman | Mac West European | macroman_general_ci | 1 |
| cp852 | DOS Central European | cp852_general_ci | 1 |
| latin7 | ISO 8859-13 Baltic | latin7_general_ci | 1 |
| utf8mb4 | UTF-8 Unicode | utf8mb4_general_ci | 4 |
| cp1251 | Windows Cyrillic | cp1251_general_ci | 1 |
| utf16 | UTF-16 Unicode | utf16_general_ci | 4 |
| utf16le | UTF-16LE Unicode | utf16le_general_ci | 4 |
| cp1256 | Windows Arabic | cp1256_general_ci | 1 |
| cp1257 | Windows Baltic | cp1257_general_ci | 1 |
| utf32 | UTF-32 Unicode | utf32_general_ci | 4 |
| binary | Binary pseudo charset | binary | 1 |
| geostd8 | GEOSTD8 Georgian | geostd8_general_ci | 1 |
| cp932 | SJIS for Windows Japanese | cp932_japanese_ci | 2 |
| eucjpms | UJIS for Windows Japanese | eucjpms_japanese_ci | 3 |
| gb18030 | China National Standard GB18030 | gb18030_chinese_ci | 4 |
+----------+---------------------------------+---------------------+--------+
可以用SHOW COLLATION WHERE Charset LIKE '%utf8%';
来看各个collation的细节。
设置MySQL中文字符
MySQL按下列顺序读取配置文件:
/etc/my.cnf /etc/mysql/my.cnf ~/.my.cnf
默认情况下,没有第1及第3个文件,但存在第2个文件。
第2件文件,/etc/mysql/my.cnf是/etc/alternatives/my.cnf文件的快捷方式,而后者又是/etc/mysql/mysql.cnf的快捷方式。因此,/etc/mysql/my.cnf实际上指向了其所在目录下的/etc/mysql/mysql.cnf文件。
/etc/mysql/mysql.cnf的内容为:
!includedir /etc/mysql/conf.d/
!includedir /etc/mysql/mysql.conf.d/
它导入了两个路径。第一个是/etc/mysql/conf.d,在其路径下,有一个名为mysql.cnf的文件,其内容为:
[mysql]
显然,这是用以设置MySQL客户端的配置,默认情况下,没有额外的配置。
第二个是/etc/mysql/mysql.conf.d,在其路径下,有一个名为mysqld.cnf的文件,其主要内容为:
[mysqld_safe]
socket = /var/run/mysqld/mysqld.sock
nice = 0
[mysqld]
#
# * Basic Settings
#
user = mysql
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
port = 3306
basedir = /usr
datadir = /var/lib/mysql
tmpdir = /tmp
lc-messages-dir = /usr/share/mysql
skip-external-locking
#
# Instead of skip-networking the default is now to listen only on
# localhost which is more compatible and is not less secure.
bind-address = 127.0.0.1
这是MySQL服务器的配置。
要查看MySQL服务器加载了哪些配置,使用以下命令:
mysqld --print-default
显示:
mysqld would have been started with the following arguments:
--user=mysql --pid-file=/var/run/mysqld/mysqld.pid --socket=/var/run/mysqld/mysqld.sock --port=3306 --basedir=/usr --datadir=/var/lib/mysql --tmpdir=/tmp --lc-messages-dir=/usr/share/mysql --skip-external-locking --character-set-server=utf8 --bind-address=127.0.0.1 --key_buffer_size=16M --max_allowed_packet=16M --thread_stack=192K --thread_cache_size=8 --myisam-recover-options=BACKUP --query_cache_limit=1M --query_cache_size=16M --log_error=/var/log/mysql/error.log --expire_logs_days=10 --max_binlog_size=100M --user=mysql --pid-file=/var/run/mysqld/mysqld.pid --socket=/var/run/mysqld/mysqld.sock --port=3306 --basedir=/usr --datadir=/var/lib/mysql --tmpdir=/tmp --lc-messages-dir=/usr/share/mysql --skip-external-locking --bind-address=127.0.0.1 --key_buffer_size=16M --max_allowed_packet=16M --thread_stack=192K --thread_cache_size=8 --myisam-recover-options=BACKUP --query_cache_limit=1M --query_cache_size=16M --log_error=/var/log/mysql/error.log --expire_logs_days=10 --max_binlog_size=100M
更改服务器配置
修改/etc/mysql/mysql.conf.d/mysqld.cnf文件的内容如下:
[mysqld]
...
character-set-server=utf8mb4
collation-server=utf8mb4_general_ci
...
如果查看系统变量,将可看到:
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8mb4 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8mb4 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
可以看到,它改变了character_set_database
及character_set_server
的值。
character_set_system
是数据库系统存储系统元数据的字符集,该属性是只读的,值为utf8
。
更改客户端配置
修改/etc/mysql/conf.d/mysql.cnf文件的内容如下:
[mysql]
default-character-set=utf8mb4
如果查看系统变量,将可看到:
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | utf8mb4 |
| character_set_connection | utf8mb4 |
| character_set_database | utf8mb4 |
| character_set_filesystem | binary |
| character_set_results | utf8mb4 |
| character_set_server | utf8mb4 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
可以看到,它改变了character_set_client
, character_set_connection
及character_set_results
的值。
这与在终端的mysql程序中运行:
mysql> SET NAMES 'utf8mb4';
的效果是一样的。
为特定的数据库创建专属用户
安装好MySQL后,默认用户为root。这是一个超级用户,它可以操作MySQL所有数据库。因此,一是应当及时修改root的密码,二是不可轻易将此密码泄露给别人。在一个普通的PHP应用中,我们需要为应用创建单独的数据库,并为此数据库创建专属的用户并配置其权限,这样可避免使用默认的root用户来操作所有数据库。步骤如下:
- 以root身份连接MySQL中的
mysql
数据库。
- 创建PHP应用专属用户并设置其密码。
- 创建PHP应用的数据库并赋于新用户对该数据库的权限
- 以新用户的身份连接新的数据库
下面以NetBeans为例来实施上述步骤。
以root身份连接MySQL中的mysql
数据库

在Databases
节点下创建一个用以连接mysql
数据库的节点。
创建PHP应用专属用户并设置其密码。
对上述节点按右键,点击Execute Command...
菜单项。

输入以下命令:
CREATE USER 'tutuser'@'localhost' IDENTIFIED BY 'tut0898'
结果是在mysql
数据库中将创建一个tutuser
用户,其密码为tut0898
。
创建PHP应用的数据库并赋于新用户对该数据库的权限

在弹出的窗口中输入要创建的数据库名称,并将该数据库的所有权限都赋与tutuser@localhost
用户。

单击OK
按钮。
将在Databases
节点下创建一个新的连接mysql_tutorial_db
数据库的连接。但该连接的用户是root
。因此,我们可以先将该连接节点删除,然后再以tutuser
身份来新建一个连接。
以新用户的身份连接新的数据库
对Dabases
节点按右键,选New Connection...
菜单项。

在弹出的New Connection Wizard
窗口中,Driver
列表框选择MySQL (Connector/J driver)
。单击Next
按钮。

在此步,输入相应的数据库名称,以及用户姓名及密码。可以勾选Remember password
,让NetBeans记住密码。点击Test Connection
按钮,以测试该配置是否正确。若无问题,则在左下角出现Connection Succeeded.
的提示。单击Next
按钮。

由于我们尚未在mysql_tutorial_db
数据库中创建表,因此这一步可不管它。单击Next
按钮。

此步设置连接名称,可按下图修改。单击Finish
按钮。

现在,mysql_tutorial_db
的连接已经成功创建并显示在Databases
节点之下。

使用此视图,我们可以很方便地创建表了。
MySQL密码字段的加密与解密
下面使用AES算法来加密字符串abc
。AES算法需指定密钥,这里作为例子,密钥随便取值为mykey
。
SET @cript_str = AES_ENCRYPT('abc', 'mykey');
SELECT @cript_str;
加密后,将结果存储至变量@cript_str中,然后再显示其值。输出结果为:
<BLOB 16 bytes>
结果已经被加密为二进制。下面显示解密后的字符串。
SELECT AES_DECRYPT(@cript_str, 'mykey');
结果为:
<BLOB 3 bytes>
说明abc
这3个字符占用了3个字节的空间。对于中文的UTF-8编码,如海上生明月
这5个汉字,则会占去15个字节。对于二进制数据,可用CAST AS语句转换为字符:
SELECT CAST(AES_DECRYPT(@cript_str, 'mykey') AS CHAR);
显示:
abc
在数据库中创建表
在此应用中,需要达到什么目的?
- 用户登录
- 班主任录入各科成绩
- 学生观看自己的成绩,并有机会给班主任评论
- 学生管理自己的帐号
用户角色表:role
先创建用户角色表,只有两个角色:admin
及student
。
DROP TABLE IF EXISTS role;
CREATE TABLE role (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
role_name CHAR(10)
);
INSERT INTO role (role_name) VALUES ('admin');
INSERT INTO role (role_name) VALUES ('student');
用户表:user
DROP TABLE IF EXISTS role;
CREATE TABLE user (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL,
password BLOB NOT NULL,
role_id INT NOT NULL,
FOREIGN KEY (role_id) REFERENCES role (id)
);
INSERT INTO user (name, password, role_id) VALUES ('黄老师', AES_ENCRYPT('12345', 'mykey'), 1);
注意,这里对密码12345
进行了加密,而较为适合存储加密的字段类型为BLOB
,如果选取的字段类型不对,则导致经解密后的字段文本无法正常显示。
这种情况下,查看解密后的password
字段的代码为:
SELECT password
FROM user
WHERE id = 1
INTO @decript_password
;
SELECT CAST(AES_DECRYPT(@decript_password, 'mykey') AS CHAR);
在实际的应用代码中,可以这样编写:
SELECT id, name, CAST(AES_DECRYPT(password, "mykey") AS CHAR) AS password FROM user WHERE name = '黄老师';
将所有初始化表的SQL语句放在一个文件中
在初始化数据库数据时,由于各表可能存在外键约束关系,因此删除含有外键的表时会失败。此时可将所有SQL语句都放一个文件中,先解除外键约束,删除各表,再恢复外键约束,再创建表并插入数据。
set @@FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS role;
DROP TABLE IF EXISTS user;
DROP TABLE IF EXISTS score;
set @@FOREIGN_KEY_CHECKS = 1;
-- table role
CREATE TABLE role (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
role_name CHAR(10)
);
INSERT INTO role (role_name) VALUES ('admin');
INSERT INTO role (role_name) VALUES ('student');
-- table user
CREATE TABLE user (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL,
password BLOB NOT NULL,
role_id INT NOT NULL,
FOREIGN KEY (role_id) REFERENCES role (id)
);
INSERT INTO user (name, password, role_id) VALUES ('黄老师', AES_ENCRYPT('12345', 'mykey'), 1);
INSERT INTO user (name, password, role_id) VALUES ('张三', AES_ENCRYPT('abc', 'mykey'), 2);
INSERT INTO user (name, password, role_id) VALUES ('李四', AES_ENCRYPT('abc', 'mykey'), 2);
INSERT INTO user (name, password, role_id) VALUES ('王五', AES_ENCRYPT('abc', 'mykey'), 2);
-- SELECT password
-- FROM user
-- WHERE id = 2
-- INTO @decript_password
-- ;
--
-- SELECT CAST(AES_DECRYPT(@decript_password, 'mykey') AS CHAR);
-- table score
CREATE TABLE score (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
semester INT NOT NULL,
chinese INT NOT NULL,
maths INT NOT NULL,
english INT NOT NULL,
student_id INT NOT NULL,
FOREIGN KEY (student_id) REFERENCES user (id)
);
INSERT INTO score (semester, chinese, maths, english, student_id) VALUES (1, 92, 78, 85, 2);
INSERT INTO score (semester, chinese, maths, english, student_id) VALUES (1, 63, 81, 75, 3);
INSERT INTO score (semester, chinese, maths, english, student_id) VALUES (1, 83, 65, 92, 4);
连接MySQL数据库
新建连接对象
创建对象时自动连接
$db_host = 'localhost';
$db_user = 'root';
$db_password = 'root';
$db_db = 'wishlist';
$db_port = '3306';
$mysqli = new mysqli($db_host, $db_user, $db_password, $db_db, $db_port);
上面的代码新建了一个mysqli
对象,用以连接数据库。
当$db_host
的值为localhost
时,PHP是通过UNIX socket来进行连接的。通过查看mysqli
的host_info
属性以验证:
echo 'Host information: '.$mysqli->host_info."
";
显示:
Host information: Localhost via UNIX socket
而当$db_host
的值为127.0.0.1
时,PHP将通过TCP/IP来进行连接:
Host information: 127.0.0.1 via TCP/IP
上面的代码,当新建了mysqli
对象后,它会立即使用构造函数的参数自动连接数据库。
手工连接
有时我们需要进行特定的设置后再连接,则可进行手工连接。
$mysqli = mysqli_init();
if (!$mysqli) {
die('mysqli_init failed');
}
if(!$mysqli->options(MYSQLI_INIT_COMMAND, 'SET AUTOCOMMIT = 0')) {
die('Setting MYSQLI_INIT_COMMAND failed');
}
if (!$mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, 5)) {
die('Setting MYSQLI_OPT_CONNECT_TIMEOUT failed');
}
$mysqli->real_connect($db_host, $db_user, $db_password, $db_db, $db_port);
为何有mysqli_real_connect()
函数名?这是因为已经存在mysqli_connect()
的函数,而它是构造器的别名。
检查是否成功连接
下面的代码检查连接数据库是否成功:
if ($mysqli->connect_error) {
echo 'Errno: {$mysqli->connect_errno}
';
echo 'Error: {$mysqli->connect_error}
';
exit;
}
检索数据
$result = $mysqli->query("SELECT * FROM wishers ORDER BY id ASC");
for ($row_no = 0; $row_no < $result->num_rows; $row_no++) {
$result->data_seek($row_no);
$row = $result->fetch_assoc();
echo "id: {$row['id']}, name: {$row['name']}
";
}
$result->free();
检索单行数据:
$result = $mysqli->query("SELECT id, label FROM test WHERE id = 1");
$row = $result->fetch_assoc();
关闭连接
$mysqli->close();
默认情况下,由脚本所发起的连接,要么由用户显式地关闭,或在脚本结束后由系统自动释放。而持续化的连接(persistent connection)则不会自动关闭,它们将被放进连接池使用。
因此,上面的语句也可以省略。
登录表单的设计
在设计表单时,我们需要考虑5个问题。一是在哪里进行表单校验的问题。可直接跳转到另外的页面,但这给表单校验带来了不便。较好的方案是直接在原表单中进行检验后再跳转。二是表单提交方式是GET
还是POST
的问题。三是如何显示表单错误信息的问题。四是如何判断表单是初次显示还是用户提交了表单数据。五是在回显页面时,如何保存用户已经填入的表单数据的问题。
<form method='POST'>
<label for='name'>姓名:</label><input type="text" value='<?php echo $name ?>' name='name' required />
<span class='error' id='name-error-msg'><?php echo $name_error_msg ?></span>
<label for='password'>密码:</label><input type="password" value='<?php echo $password ?>' name='password' required />
<span class='error' id='password-error-msg'><?php echo $password_error_msg ?></span>
<input type="submit" />
</form>
首先,此表单提交后,由该页面自己负责校验,并且表单提交的方式是POST
,用户录入的数据不会在浏览器地址栏中显示。
其次,两个文本框,其value属性值均由PHP在服务器端来提供。页面初始化显示时,由PHP在服务器端设置为空值或特定的初始化值。而如果是用户提交表单后有校验问题需要回显页面,则由PHP将这些变量值设置为用户已经录入的值,这样便实现了保存用户已经录入数据的效果。
第三,表单检验错误信息,也照样由PHP提供。则由于span标签是行内元素,当为空值时,在界面中什么都不显示,而如果有错误信息,就会显示出来。一般情况下,这些显示表单校验错误的信息应放在相应的文本框下面。
上面的表单,仅从要实现的功能的方面予以考虑,不考虑页面布局效果。满足了功能方面的需求后,界面代码确定下来了,再用CSS来给其化妆。
form {
max-width: 400px;
margin: 0 auto;
padding: 1em;
background-color: rgb(57, 83, 56);
border-radius: 0.5em;
display: grid;
border: 1px solid gray;
grid-template-areas: "name-label name-input"
". name-error"
"pass-label pass-input"
". pass-error"
". submit ";
grid-template-columns: auto 1fr;
row-gap: 1em;
}
form label {
text-align: right;
color: rgb(100, 219, 214);
width: max-content;
}
form input[type='text'],
form input[type='password'] {
background-color: #333C3E;
}
label[for="name"] { grid-area: name-label; }
input[type='text'] { grid-area: name-input; }
#name-error-msg { grid-area: name-error; text-align: left; }
label[for="password"] { grid-area: pass-label; }
input[type='password'] { grid-area: pass-input; }
#password-error-msg { grid-area: pass-error; text-align: left; }
form input[type='submit'] {
grid-area: submit;
justify-self: right;
padding: 0.2em 0;
width: 100px;
}
.error {
color: rgb(255, 168, 214);
}
最后是服务器端的PHP代码,就放在页面代码的最上面即可。
<?php
$name = '黄老师';
$password = '12345';
$name_error_msg = '';
$password_error_msg = '';
// if submitted, validate
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$error = false;
$name = htmlspecialchars(trim($_POST['name']));
if (empty($name)) {
$name_error_msg = '名字不能为空';
$error = true;
}
$password = htmlspecialchars(trim($_POST['password']));
if (empty($password)) {
$password_error_msg = '密码不能为空';
$error = true;
}
// check database data
$db_host = 'localhost';
$db_user = 'tutuser';
$db_password = 'tut0898';
$db_db = 'mysql_tutorial_db';
$db_port = '3306';
$mysqli = new mysqli($db_host, $db_user, $db_password, $db_db, $db_port);
if ($mysqli->connect_error) {
printf('Errno: %s
', $mysqli->connect_errno);
printf('Error: %s
', $mysqli->connect_error);
exit;
}
$query = 'SELECT id, name, CAST(AES_DECRYPT(password, "mykey") AS CHAR) AS password FROM user WHERE name = "' .$name .'"';
$result = $mysqli->query($query);
if ($result->num_rows == 0) {
$name_error_msg = '用户不存在';
$error = true;
} else {
while($row = $result->fetch_assoc()) {
if ($row['password'] !== $password) {
$password_error_msg = '密码不正确';
$error = true;
} else {
session_start();
$_SESSION['user'] = $name;
$_SESSION['user_id'] = $row['id'];
}
}
}
$result->free();
$mysqli->close();
if ($error == false) {
header('Location: list-score.php');
exit;
}
}
?>
首先,为页面中要在表单中显示值的4个变量$name, $password, $name_error_msg, $password_error_msg设置了初始值。这样,页面在首次显示时,也可利用这些变量的初始值。
其次,使用$_SERVER['REQUEST_METHOD'] == 'POST'
的代码来判断页面是首次显示还是用户已经提交了表单数据。如果是页面首次显示,则不执行条件语句下的代码块,直接显示原始的页面内容。
而如果是用户提交表单,则依序校验各个字段是否正确。从逻辑结构上,我们先设置了一个变量$error的值为false
,即没有错误。在校验过程中,若有任何错误,则将此变量的值设置为true
。而在最后,放了下面的把关语句:
if ($error == false) {
header('Location: list-score.php');
exit;
}
意为,如果上面的校验没有任何错误,则跳转至list-score.php文件,否则,运行并显示下面的HTML代码以显示表单。
而在表单各字段的校验过程中,一但发现错误,一是设置相应的错误信息,二是将变量$error的值设置为true
。
对于用户姓名及密码字段,先统一检查它们的值是否为空值。然而再根据它们的值读取数据库中的信息进行进一步的校验。
最后,如果姓名及密码都正确了,则将用户名及用户Id保存进HTML的session中:
session_start();
$_SESSION['user'] = $name;
$_SESSION['user_id'] = $row['id'];
session_start()
的意思是,从现在开始,允许在session中以Map名值对的方式保存或读取相应的变量。我们将当前登录用户的姓名及其id都存入session中。这样,后续同一session的网页均可访问这些变量以判断当前登录用户是谁。
至此,如果前面均未出现错误,我们已将用户登录信息保存进session中,并立即跳转至list-score.php文件。
列出成绩单的页面设计
列出成绩单的页面相对比较简单。
同上节一样,在运行网页前,我们需要在服务器端作些准备工作。
<?php
session_start();
if (!array_key_exists("user", $_SESSION)) {
header('Location: login.php');
exit;
}
$db_host = 'localhost';
$db_user = 'tutuser';
$db_password = 'tut0898';
$db_db = 'mysql_tutorial_db';
$db_port = '3306';
$mysqli = new mysqli($db_host, $db_user, $db_password, $db_db, $db_port);
if ($mysqli->connect_error) {
printf('<p>Errno: %s</p>', $mysqli->connect_errno);
printf('<p>Error: %s</p>', $mysqli->connect_error);
exit();
}
?>
先运行session_start()
以访问存在session中的变量。array_key_exists函数检查数组中是否存在键名或索引值。如果session中没有user
此键名,则说明并非通过登录窗口界面而访问本页,例如直接在地址栏中输入网址后访问的本页,故此需重新导航至login.php。
然后准备数据库连接,以在页面中可以访问数据库。
<h2>学生成绩单</h2>
<p>欢迎您,<?php echo $_SESSION['user'] ?>,以下是所有学生的成绩单。</p>
<table>
<tr><th>学生姓名</th><th>语文</th><th>数学</th><th>英语</th></tr>
<?php
$query = '
SELECT user.name AS 学生姓名, score.chinese, score.maths, score.english
FROM score, user
WHERE score.student_id = user.id and user.role_id = 2;
';
$result = $mysqli->query($query);
while($row = $result->fetch_assoc()) {
printf("<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>", $row['学生姓名'], $row['chinese'], $row['maths'], $row['english']);
}
$result->free();
$mysqli->close();
?>
</table>
<form action="logoff.php">
<input type="submit" value="退出登录" />
</form>
先从session中读取user的值并显示出来。然后,通过PHP从数据库读取数据并填充表格。最后,设立一个退出登录的表单,交由logoff.php来处理。
退出登录页面的设计
logoff.php的内容如下:
<?php
session_start();
$_SESSION = array();
session_destroy();
header('Location: login.php');
exit;
?>
先调用session_start函数,然后,代码$_SESSION = array()
取消session中的键名设置,session_destroy函数将session中的数据全部清空。最后,根据业务逻辑的需要,将页面重新导航至login.php。
数据库安全问题
隐藏用以连接的敏感信息
上面logon.php及list-score.php均将用以连接的敏感信息放在了普通的网页文件中,而这些文件位于服务器的根目录下面,意味着别人可以直接访问得到这些敏感数据,从而有较大的风险。
好的做法是,将数据库敏感信息不要放在服务器允许外界访问的根目录下。例如,如果服务器允许外界访问的根目录名称为WWWROOT,我们就可以在WWWROOT文件夹旁边新建一个名为include的文件夹,且在里面新建一个名为db.inc.php的文件,内容如下:
<?php
$db_host = 'localhost';
$db_user = 'tutuser';
$db_password = 'tut0898';
$db_db = 'mysql_tutorial_db';
$db_port = '3306';
?>
然后,将logon.php及list-score.php连接数据库的代码:
$db_host = 'localhost';
$db_user = 'tutuser';
$db_password = 'tut0898';
$db_db = 'mysql_tutorial_db';
$db_port = '3306';
$mysqli = new mysqli($db_host, $db_user, $db_password, $db_db, $db_port);
改为:
include $_SERVER['DOCUMENT_ROOT'].'/../include/db.inc.php';
$mysqli = new mysqli($db_host, $db_user, $db_password, $db_db, $db_port);
其次,在数据库连接错误时,不要轻易将诸如数据库表名、表结构等敏感信息暴露出来。
防止SQL注入
第一步是接收用户数据时,一概经过htmlspecialchars函数的转换后才使用。该函数将HTML中的5个特殊字符进行转换:&
转换为&
,双引号 "
转换为"
,单引号 '
转换为'
,<
转换为<
,>
转换为>
。htmlentities函数除了转换上述5个特殊字符后,对于任意字符,如果有对应的HTML entity,则自动转换为相应的HTML entity。
有些时候,如果是从数据库取出的数据,也有可能带有这些特殊字符,在需要的时候,也可以用上述两个函数进行转换。