《零基础学PHP:从入门到实战》教程-模块七:MySQL 数据库基础-5

第 5 章:PHP 连接 MySQL:使用 mysqli 执行数据库操作

章节介绍

学习目标

  1. 理解 PHP 与 MySQL 交互的必要性,掌握 mysqli 扩展的两种基本使用方式。
  2. 能够独立编写 PHP 脚本,建立并管理与 MySQL 数据库的连接。
  3. 熟练使用 PHP 执行SELECT查询,并能将结果集处理为可用的 PHP 数据(如关联数组)。
  4. 掌握使用 PHP 执行INSERTUPDATEDELETE等数据操作语句的方法。
  5. 深刻理解 SQL 注入攻击的原理,并强制掌握使用预处理语句(Prepared Statements)进行安全防范的标准实践。

在本教程中的作用

本章是理论走向实战的枢纽安全基石 。前几章我们学会了在 MySQL 环境中直接操作数据,而本章将赋予 PHP 程序"生命力",使其能够动态地存取后端数据库中的数据,从而构建出真正的动态网站。此外,本章重点灌输的安全编程思想,是每一位合格开发者必须刻入 DNA 的准则,直接关系到应用的安全性。

与前面章节的衔接

本章将直接应用第 2、3 章所学的 SQL 知识。您将不再于 MySQL 命令行中手动输入 SQL,而是改为在 PHP 脚本中编写这些 SQL 语句,并通过 PHP 发送给 MySQL 服务器执行,最后在 PHP 中处理返回的结果。第 4 章搭建的环境是本章所有代码的运行基础。

本章主要内容概览

我们将从建立数据库连接开始,逐步学习执行查询与操作语句,最终聚焦于防范 SQL 注入的预处理语句。本章将以一个完整的"用户管理"实战项目贯穿始终,巩固 CRUD 操作的安全实现流程。

核心概念讲解

1. 为什么需要 PHP 连接数据库?

静态网站的內容是固定的 HTML 文件。而动态网站(如新闻站、电商平台)的内容(如文章、商品信息)存储在数据库中。PHP 的角色是"中间人"或"大脑",它接收用户请求(如访问某个文章页面),根据请求向数据库查询对应的数据,然后将数据和 HTML 模板结合,生成最终的、包含动态内容的网页发送给用户。没有数据库连接的 PHP,只能处理静态逻辑;没有 PHP 的数据库,只是一潭无法与 Web 交互的"死水"。

2. mysqli 扩展简介

PHP 提供了多种与 MySQL 交互的扩展,其中mysqli(MySQL Improved)是当前推荐的方式之一,它支持 MySQL 4.1 及以上版本,提供了面向对象和面向过程两种编程接口。

  • 面向对象风格:代码更清晰、易于维护,是现代 PHP 开发的主流方式,本章将以此为重点。
  • 面向过程风格 :类似于旧的mysql扩展,函数式调用,在某些简单场景或遗留代码中可见。

3. 数据库连接与连接参数

建立连接是第一步,需要四个关键参数:

  • 主机名(Host) :MySQL 服务器地址。本地开发通常是 localhost127.0.0.1
  • 用户名(Username) :拥有数据库访问权限的用户,如 root
  • 密码(Password):对应用户的密码。
  • 数据库名(Database) :要操作的具体数据库名称。
    最佳实践 :永远不要在代码中硬编码这些敏感信息!应将其存储在配置文件(如 config.php)中,并通过 include 引入,或使用环境变量管理。

4. 执行 SQL 语句的流程

一个典型的数据库操作遵循以下流程:

  1. 建立连接 :创建mysqli对象。
  2. 准备 SQL:编写 SQL 语句字符串。对于用户输入的数据,必须使用预处理语句。
  3. 执行 SQL
  • SELECT查询:使用 query() 或预处理语句的 execute() 方法,返回一个结果集对象。
  • INSERT/UPDATE/DELETE:使用相同方法执行,返回布尔值表示成功与否,可通过方法获取受影响行数。
  1. 处理结果
  • 对于查询:遍历结果集,提取数据。
  • 对于操作:检查是否成功,进行相应业务处理(如提示用户)。
  1. 释放资源与关闭连接:显式释放结果集内存,并最终关闭数据库连接(或依赖脚本结束自动关闭)。

5. SQL 注入与预处理语句(核心安全概念)

  • SQL 注入原理 :攻击者通过在 Web 表单、URL 参数等输入点插入恶意的 SQL 代码,欺骗服务器执行非预期的 SQL 命令。例如,登录时输入用户名 admin' --,可能绕过密码验证。
  • 预处理语句(Prepared Statements):这是防御 SQL 注入的终极武器。其原理是将 SQL 语句的结构(模板)与数据分离发送。
  1. 准备阶段 :发送一个 SQL 模板,其中变量用占位符 ? 表示。例如:SELECT * FROM users WHERE username = ? AND password = ?
  2. 绑定阶段 :将用户输入的数据"绑定"到对应的占位符上。mysqli会确保这些数据被严格地当作数据处理,而非 SQL 代码的一部分。
  3. 执行阶段 :执行已绑定数据的语句。
    这个过程从根本上杜绝了用户输入"污染"SQL 语句结构的可能性。

代码示例

示例 1:建立数据库连接(面向对象风格)

php 复制代码
<?php
// config.php - 数据库配置文件,应置于Web目录外或通过.htaccess保护
define('DB_HOST', 'localhost');
define('DB_USER', 'your_username'); // 替换为你的数据库用户名
define('DB_PASS', 'your_password'); // 替换为你的数据库密码
define('DB_NAME', 'php_tutorial');  // 替换为你在第1章创建的数据库名
// connect.php - 建立数据库连接并处理错误
header('Content-Type: text/html; charset=utf-8'); // 确保中文正确显示
// 通过mysqli构造函数建立连接
$mysqli = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);

// 检查连接是否成功 (面向对象风格使用 connect_error 属性)
if ($mysqli->connect_error) {
    // 在实际生产环境中,应记录错误日志,并向用户展示友好信息
// die() 会终止脚本执行,仅用于开发调试
die('数据库连接失败: (' . $mysqli->connect_errno . ') ' . $mysqli->connect_error);
} else {
    echo "数据库连接成功!<br>";
    // 设置客户端字符集为UTF-8,避免中文乱码
if (!$mysqli->set_charset("utf8mb4")) {
        printf("加载字符集utf8mb4失败: %s<br>", $mysqli->error);
    } else {
        echo "当前字符集: " . $mysqli->character_set_name() . "<br>";
    }
}

// 后续的数据库操作代码将放在这里...

// 脚本结束时连接会自动关闭,但显式关闭是好习惯
// $mysqli->close();
?>

预期输出:

复制代码
数据库连接成功!
当前字符集: utf8mb4

注意 :请将 DB_USERDB_PASSDB_NAME 替换为您自己环境的实际值。

示例 2:执行 SELECT 查询并处理结果集

假设我们有一个在第 2 章创建的 users 表。

php 复制代码
<?php
// 包含数据库连接文件
require_once 'connect.php'; // 假设connect.php已包含config.php并创建了$mysqli对象
// 要执行的SQL查询语句
$sql = "SELECT id, username, email, created_at FROM users ORDER BY created_at DESC";

// 执行查询
$result = $mysqli->query($sql);

// 检查查询是否成功执行
if ($result === false) {
    die('查询失败: ' . $mysqli->error); // 输出错误信息
}

// 检查是否有数据返回
if ($result->num_rows > 0) {
    echo "<h2>用户列表</h2>";
    echo "<table border='1' cellpadding='10'>";
    echo "<tr><th>ID</th><th>用户名</th><th>邮箱</th><th>注册时间</th></tr>";

    // 使用 fetch_assoc() 逐行获取关联数组
while ($row = $result->fetch_assoc()) {
        echo "<tr>";
        echo "<td>" . htmlspecialchars($row['id']) . "</td>"; // 使用htmlspecialchars防止XSS
        echo "<td>" . htmlspecialchars($row['username']) . "</td>";
        echo "<td>" . htmlspecialchars($row['email']) . "</td>";
        echo "<td>" . htmlspecialchars($row['created_at']) . "</td>";
        echo "</tr>";
    }
    echo "</table>";
    echo "共找到 " . $result->num_rows . " 条记录。";
} else {
    echo "没有找到任何用户。";
}

// 释放结果集占用的内存
$result->free();

// 关闭连接 (connect.php中可能已关闭,这里演示)
// $mysqli->close();
?>

示例 3:执行 INSERT 操作(不安全的方式 - 用于对比演示风险)

php 复制代码
<?php
// 不安全示例:直接拼接用户输入到SQL中
require_once 'connect.php';

// 模拟从表单接收的数据
$_POST['username'] = 'new_user';
$_POST['email'] = 'user@example.com';
// 假设密码已使用password_hash()加密,这里简单演示
$_POST['password_hash'] = password_hash('mypassword', PASSWORD_DEFAULT);

// !!! 危险操作:直接拼接变量 !!!
$username = $_POST['username'];
$email = $_POST['email'];
$password_hash = $_POST['password_hash'];

$sql = "INSERT INTO users (username, email, password_hash) VALUES ('$username', '$email', '$password_hash')";

if ($mysqli->query($sql) === true) {
    echo "新用户插入成功,ID为: " . $mysqli->insert_id; // 获取自增ID
} else {
    echo "插入失败: " . $mysqli->error;
}
?>

示例 4:使用预处理语句执行 INSERT(安全的方式)

php 复制代码
<?php
// 安全示例:使用预处理语句
require_once 'connect.php';

// 模拟从表单接收的数据
$username = 'safe_user';
$email = 'safe@example.com';
$password_hash = password_hash('secure123', PASSWORD_DEFAULT);

// 1. 准备SQL语句模板,使用 ? 作为占位符
$sql = "INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)";

// 2. 准备预处理语句
$stmt = $mysqli->prepare($sql);
if ($stmt === false) {
    die('准备语句失败: ' . $mysqli->error);
}

// 3. 绑定参数('sss'表示三个参数都是字符串类型)
// 类型说明: i-整数, d-浮点数, s-字符串, b-二进制数据
$stmt->bind_param('sss', $username, $email, $password_hash);

// 4. 执行语句
if ($stmt->execute()) {
    echo "用户安全插入成功,ID为: " . $stmt->insert_id;
} else {
    echo "执行失败: " . $stmt->error;
}

// 5. 关闭预处理语句
$stmt->close();
?>

示例 5:使用预处理语句执行 SELECT 与 UPDATE(综合)

php 复制代码
<?php
// 综合示例:查询用户并更新其邮箱
require_once 'connect.php';

// --- 第一部分:使用预处理语句查询特定用户 ---
$search_username = 'safe_user';
$sql_select = "SELECT id, email FROM users WHERE username = ?";
$stmt_select = $mysqli->prepare($sql_select);
$stmt_select->bind_param('s', $search_username);
$stmt_select->execute();
// 将结果绑定到变量
$stmt_select->bind_result($user_id, $old_email);
$stmt_select->fetch(); // 获取一行数据
echo "找到用户: ID=$user_id, 旧邮箱=$old_email <br>";
$stmt_select->close();

// --- 第二部分:使用预处理语句更新该用户的邮箱 ---
if ($user_id) {
    $new_email = 'updated@example.com';
    $sql_update = "UPDATE users SET email = ? WHERE id = ?";
    $stmt_update = $mysqli->prepare($sql_update);
    $stmt_update->bind_param('si', $new_email, $user_id); // 注意类型:字符串和整数
if ($stmt_update->execute()) {
        echo "邮箱更新成功,影响行数: " . $stmt_update->affected_rows;
    } else {
        echo "更新失败: " . $stmt_update->error;
    }
    $stmt_update->close();
}

$mysqli->close();
?>

实战项目:简易用户管理后台(CRUD)

本项目将创建一个具有基本增删改查功能的用户管理页面,强制使用预处理语句确保安全。

项目需求分析

  1. 功能需求
  • 列表展示 (Read):在一个页面以表格形式展示所有用户信息(ID, 用户名, 邮箱, 注册时间)。
  • 新增用户 (Create):通过表单提交,将新用户安全地插入数据库。
  • 编辑用户 (Update):点击列表中的"编辑"链接,跳转到表单页预填用户信息,修改后提交更新。
  • 删除用户 (Delete):点击列表中的"删除"链接,弹出确认框后删除对应用户。
  1. 非功能需求
  • 所有数据库操作必须使用预处理语句防范 SQL 注入。
  • 输出到 HTML 页面的动态数据必须使用 htmlspecialchars() 过滤,防范 XSS。
  • 代码结构清晰,包含基本的错误处理。

技术方案与文件结构

复制代码
/user_admin/
├── config.php          # 数据库配置(敏感信息)
├── connect.php         # 数据库连接与通用函数
├── index.php           # 用户列表展示页(主页面)
├── create.php          # 新增用户页面
├── edit.php            # 编辑用户页面
└── delete.php          # 删除用户处理脚本

分步骤实现

步骤 1:创建配置文件与通用连接文件

config.php

php 复制代码
<?php
// 数据库配置常量
define('DB_HOST', 'localhost');
define('DB_USER', 'your_username');
define('DB_PASS', 'your_password');
define('DB_NAME', 'php_tutorial');
?>

connect.php

php 复制代码
<?php
// 包含配置
require_once 'config.php';

// 启用错误报告(开发环境)
error_reporting(E_ALL);
ini_set('display_errors', 1);

// 建立数据库连接
$mysqli = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if ($mysqli->connect_error) {
    die('连接数据库失败,请检查配置。错误代码:' . $mysqli->connect_errno);
}
// 设置字符集
$mysqli->set_charset("utf8mb4");

/**
 * 安全获取GET/POST参数,并过滤XSS
 * @param string $key 参数键名
* @param mixed $default 默认值
* @return mixed 过滤后的值
*/
function getInput($key, $default = '') {
    $value = $default;
    if (isset($_POST[$key])) {
        $value = $_POST[$key];
    } elseif (isset($_GET[$key])) {
        $value = $_GET[$key];
    }
    // 去除两端空格,防止无意义的空白字符
$value = trim($value);
    // 使用htmlspecialchars转换特殊字符,防止XSS(用于输出时)
// 注意:这里返回的是转义后的字符串,适用于直接echo。如果用于数据库查询,预处理语句会处理。
// 更佳实践是在输出时进行htmlspecialchars,这里返回原始值用于绑定。
return $value;
}
?>
步骤 2:实现用户列表页 (index.php)
php 复制代码
<?php
require_once 'connect.php';

$page_title = "用户管理后台";
include_once 'header.php'; // 假设有一个简单的HTML头部文件
?>

<h1>用户列表</h1>
<p><a href="create.php" class="btn">添加新用户</a></p>

<?php
// 查询所有用户
$sql = "SELECT id, username, email, created_at FROM users ORDER BY id DESC";
$result = $mysqli->query($sql);

if ($result && $result->num_rows > 0) {
?>
<table border="1" cellspacing="0" cellpadding="8" width="100%">
    <tr>
        <th>ID</th>
        <th>用户名</th>
        <th>邮箱</th>
        <th>注册时间</th>
        <th>操作</th>
    </tr>
    <?php while ($user = $result->fetch_assoc()): ?>
    <tr>
        <td><?php echo htmlspecialchars($user['id']); ?></td>
        <td><?php echo htmlspecialchars($user['username']); ?></td>
        <td><?php echo htmlspecialchars($user['email']); ?></td>
        <td><?php echo htmlspecialchars($user['created_at']); ?></td>
        <td>
            <a href="edit.php?id=<?php echo urlencode($user['id']); ?>">编辑</a> |
            <a href="delete.php?id=<?php echo urlencode($user['id']); ?>"
               onclick="return confirm('确定要删除用户 "<?php echo addslashes(htmlspecialchars($user['username'])); ?>" 吗?此操作不可恢复!');">删除</a>
        </td>
    </tr>
    <?php endwhile; ?>
</table>
<?php
    $result->free();
} else {
    echo "<p>暂无用户数据。</p>";
}
?>

<?php
// 关闭连接
$mysqli->close();
include_once 'footer.php'; // 假设有一个简单的HTML底部文件
?>
步骤 3:实现新增用户页 (create.php)
php 复制代码
<?php
require_once 'connect.php';

$page_title = "添加新用户";
include_once 'header.php';

// 处理表单提交
$message = '';
$message_type = ''; // success, error

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = getInput('username');
    $email = getInput('email');
    $password = getInput('password');

    // 简单的后端验证
$errors = [];
    if (empty($username)) $errors[] = "用户名不能为空";
    if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) $errors[] = "邮箱格式无效";
    if (empty($password)) $errors[] = "密码不能为空";

    if (empty($errors)) {
        // 对密码进行哈希处理
$password_hash = password_hash($password, PASSWORD_DEFAULT);

        // 使用预处理语句插入
$sql = "INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)";
        $stmt = $mysqli->prepare($sql);
        if ($stmt) {
            $stmt->bind_param('sss', $username, $email, $password_hash);
            if ($stmt->execute()) {
                $message = "用户添加成功!";
                $message_type = 'success';
                // 清空表单字段(可选)
$username = $email = $password = '';
            } else {
                // 检查是否是唯一约束冲突(如用户名重复)
if ($mysqli->errno == 1062) {
                    $message = "添加失败:用户名或邮箱已存在。";
                } else {
                    $message = "添加失败:" . $stmt->error;
                }
                $message_type = 'error';
            }
            $stmt->close();
        } else {
            $message = "SQL语句准备失败:" . $mysqli->error;
            $message_type = 'error';
        }
    } else {
        $message = implode('<br>', $errors);
        $message_type = 'error';
    }
}
?>

<h1>添加新用户</h1>
<p><a href="index.php">← 返回用户列表</a></p>

<?php if ($message): ?>
<div class="message <?php echo $message_type; ?>">
    <?php echo htmlspecialchars($message); ?>
</div>
<?php endif; ?>

<form method="post" action="">
    <div>
        <label for="username">用户名:</label>
        <input type="text" id="username" name="username" value="<?php echo htmlspecialchars(getInput('username')); ?>" required>
    </div>
    <div>
        <label for="email">邮箱:</label>
        <input type="email" id="email" name="email" value="<?php echo htmlspecialchars(getInput('email')); ?>" required>
    </div>
    <div>
        <label for="password">密码:</label>
        <input type="password" id="password" name="password" required>
    </div>
    <div>
        <button type="submit">添加用户</button>
    </div>
</form>

<?php
$mysqli->close();
include_once 'footer.php';
?>

(编辑页 edit.php 和删除处理页 delete.php 逻辑类似,均使用预处理语句,篇幅所限不在此全文展开,但思路如下:)

  • edit.php :GET 请求时,根据 id 参数用预处理语句查询用户信息并预填表单;POST 请求时,用预处理语句执行 UPDATE。
  • delete.php :接收 GET 参数 id,用预处理语句执行 DELETE,然后跳转回 index.php

项目测试与部署指南

  1. 本地测试
  • 确保 Apache/Nginx、PHP、MySQL 服务已启动。
  • 将项目文件夹放入 Web 服务器根目录(如 htdocs)。
  • 在浏览器访问 http:// localhost/user_admin/index.php
  • 测试添加、编辑、删除、列表功能,并尝试输入包含特殊字符(如'<script>)的数据,观察是否被正确处理。
  1. 安全测试
  • 在用户名输入框尝试输入:test' OR '1'='1,查看是否会引发 SQL 错误或异常结果。在我们的安全代码下,它应被当作普通字符串处理。
  1. 部署建议
  • config.php 移到 Web 目录之外,并通过绝对路径包含。
  • 关闭 PHP 错误显示(display_errors = Off),开启错误日志(log_errors = On)。
  • 为数据库用户分配最小必要权限(非 root 用户)。

项目扩展建议

  1. 增加用户角色(如管理员、普通用户)字段,并在列表和表单中管理。
  2. 为列表页添加分页功能(使用 SQL LIMIT 子句)。
  3. 增加搜索功能(按用户名或邮箱模糊查询)。
  4. 为删除操作添加 CSRF 令牌防护。

最佳实践

1. 数据库操作安全黄金法则

  • 永远使用预处理语句 :这是防御 SQL 注入唯一可靠的方法。不要使用字符串拼接,即使你使用了 mysqli_real_escape_string(),在某些边缘情况下也可能不安全。
  • 最小权限原则 :PHP 连接数据库使用的账号,只赋予其应用所需的最小权限(如SELECTINSERTUPDATEDELETE),不要使用root账号。
  • 错误处理:生产环境禁止向用户显示原始数据库错误信息(可能泄露表结构),应记录到日志文件,并向用户展示通用友好提示。

2. 代码组织与性能

  • 分离配置 :数据库凭据等敏感信息必须放在独立配置文件(如config.php)中,并确保该文件不在 Web 可访问目录内,或通过 .htaccess 文件禁止访问。
  • 重用连接:一个脚本生命周期内,避免重复创建数据库连接。通常一个请求一个连接足矣。
  • 及时释放资源 :对于大的结果集,使用 $result->free()$stmt->close() 及时释放内存。脚本结束时会自动关闭连接,但显式调用 $mysqli->close() 是良好习惯。
  • 使用持久连接需谨慎mysqli 支持持久连接(p:前缀),在特定高并发场景可能有益,但管理不当易导致连接堆积,一般不建议初学者使用。

3. 输入验证与输出转义

  • 输入验证:在绑定到 SQL 之前,根据业务逻辑验证数据(如邮箱格式、必填字段、长度限制)。预处理语句防注入,但不负责业务验证。
  • 输出转义 :任何从数据库取出并要输出到 HTML 页面的数据,都必须使用 htmlspecialchars($string, ENT_QUOTES, 'UTF-8') 进行转义,以防范 XSS 攻击。这是与 SQL 注入并列的常见安全漏洞。

4. 具体安全漏洞案例与防护

案例一:SQL 注入(已详细讲解)
  • 攻击代码$username = "admin' -- "; 拼接到 "SELECT * FROM users WHERE username = '$username' AND password = '$pass'" 中,导致密码验证被注释掉。
  • 防护代码 :必须使用如本章所示的预处理语句(bind_param)。
案例二:XSS 跨站脚本攻击
  • 攻击场景 :用户将昵称设置为 <script>alert('黑客');</script>。如果不加处理直接输出到页面,其他用户查看时就会执行该脚本。
  • 防护代码
php 复制代码
    // 错误:直接输出
echo $user['nickname']; // 危险!
// 正确:输出前转义
echo htmlspecialchars($user['nickname'], ENT_QUOTES, 'UTF-8');
案例三:CSRF 跨站请求伪造
  • 攻击场景 :用户登录了银行网站 A,未退出。访问了恶意网站 B,B 网站中有一个隐藏的图片标签 <img src="http:// bank.com/transfer?to=hacker&amount=1000">,导致用户在不知情下向黑客转账。
  • 防护方案
  1. 使用 CSRF 令牌(Token)。
  2. 关键操作使用 POST 请求而非 GET。
  3. 检查 HTTP Referer 头(不完全可靠)。
php 复制代码
    // 生成并存储Token在Session中
session_start();
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));

    // 在表单中隐藏该Token
    <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">

    // 处理表单时验证Token
    if ($_POST['csrf_token'] !== $_SESSION['csrf_token']) {
        die('非法请求,CSRF验证失败!');
    }

练习题与挑战

基础练习题

  1. 连接与简单查询【难度:★】
    • 题目 :编写一个 PHP 脚本 list_products.php。假设你有一个名为 shop 的数据库,其中包含 products 表(字段:id, name, price, stock)。脚本需要连接数据库,查询所有库存大于 0 的商品,并以 HTML 列表形式显示商品名和价格。
  • 提示 :参照示例 2,SQL 语句为 SELECT name, price FROM products WHERE stock > 0
  • 参考答案要点 :包含连接文件,执行查询,循环 fetch_assoc(),使用 htmlspecialchars 输出。
  1. 预处理语句插入【难度:★★】
    • 题目 :编写一个脚本 add_product.php,包含一个 HTML 表单(商品名、价格、库存)。表单提交后,使用预处理语句将数据安全地插入到 products 表中。
  • 提示 :参照示例 4 和实战项目的 create.php。注意价格可能是浮点数,绑定参数时使用 'sdi'(字符串、浮点数、整数)或 'sdd'
  • 参考答案要点 :表单使用 POST 方法,后端验证输入,使用 preparebind_param,处理成功或失败信息。

进阶练习题

  1. 用户登录验证【难度:★★★】
    • 题目 :基于本章的 users 表(假设已有 password_hash 字段),创建一个登录页面 login.php。用户输入用户名和密码后,使用预处理语句根据用户名查询出密码哈希值,然后用 password_verify() 函数验证密码。成功后,将用户 ID 存入 $_SESSION 并跳转到欢迎页面。
  • 提示SELECT password_hash FROM users WHERE username = ?password_verify($input_password, $hashed_password_from_db) 返回布尔值。
  • 参考答案要点:启动 Session,预处理查询,验证密码,设置 Session 变量,重定向。
  1. 带搜索和分页的列表【难度:★★★☆】
    • 题目 :改进 index.php 的用户列表。1) 增加一个搜索框,可以按用户名模糊搜索(LIKE)。2) 为列表实现简单分页,每页显示 5 条记录。
  • 提示
  • 搜索:WHERE username LIKE ?,绑定参数为 "%{$keyword}%"
  • 分页:SQL 中使用 LIMIT ?, ?,第一个参数是偏移量 ($page-1)*$per_page,第二个参数是 $per_page。绑定参数时注意是 'ii'(两个整数)。需要计算总记录数以生成分页链接。
  • 参考答案要点 :处理 GET 参数 keywordpage,构造动态 WHERE 子句,计算总页数,使用预处理语句执行分页查询。

综合挑战题

  1. 完整博客文章管理模块【难度:★★★★】
    • 题目 :为你下一章将构建的博客系统,提前实现文章管理的后端 PHP 部分。创建 articles 表(id, title, content, author_id, category_id, created_at)。编写四个脚本:
  • article_list.php:显示文章列表(标题、作者、分类、时间)。
  • article_create.php:发布新文章的表单和处理。
  • article_edit.php:编辑已有文章。
  • article_delete.php:删除文章。
  • 要求 :必须使用预处理语句。author_idcategory_id 可以用固定值或从userscategories表关联(关联查询是下一章重点,这里可用简单下拉框选择固定 ID)。思考如何安全地处理富文本内容(content)的存储和 XSS 防护。
  • 解题思路 :这是对本章所有知识点的综合运用。关键在于设计好各个脚本的数据流和安全处理。对于富文本内容,直接使用 htmlspecialchars 会破坏格式,需要考虑使用白名单过滤的 HTML 净化库(如 HTML Purifier),或将内容以原始格式存储,在显示时确保其来自可信来源。

章节总结

重点知识回顾

  1. 连接数据库 :使用 new mysqli() 建立连接,并务必检查 connect_error 和处理字符集。
  2. 执行查询$mysqli->query() 用于简单查询,配合 fetch_assoc() 等函数遍历结果集。
  3. 执行操作INSERTUPDATEDELETE 使用 query() 或预处理语句执行,通过 affected_rowsinsert_id 获取影响。
  4. 安全核心------预处理语句 :这是本章的重中之重 。通过 prepare()bind_param()execute() 三步,彻底免疫 SQL 注入。务必养成习惯,对所有涉及变量的 SQL 操作使用此方式。
  5. 辅助安全------输出转义 :使用 htmlspecialchars() 对输出到 HTML 的动态数据进行转义,防范 XSS。

技能掌握要求

完成本章学习与实践后,您应该能够:

  • 在 PHP 中编写安全的数据库连接代码。
  • 使用预处理语句自信地完成数据的增、删、改、查操作。
  • 解释 SQL 注入的原理,并能演示为何预处理语句可以防御它。
  • 构建一个包含基本 CRUD 功能、具备安全意识的简单数据管理后台。

进一步学习建议

  • 深入 mysqli :探索 multi_query()(多查询)、存储过程、事务处理(begin_transactioncommitrollback)等高级特性。
  • 了解 PDO:PHP 另一个主流数据库抽象层 PDO,支持多种数据库,其预处理语句的用法与 mysqli 略有不同,也是值得学习的技能。
  • 为实战做准备 :下一章,我们将把数据库操作与更复杂的业务逻辑结合,设计多张关联表,并使用JOIN查询,最终构建出一个功能更完整的博客系统。请确保本章的基础已牢固掌握。
相关推荐
翔云1234562 小时前
mysql.gtid_executed 表的初始化和更新机制
数据库·mysql·adb
alphaTao2 小时前
LeetCode 每日一题 2025/12/1-2025/12/7
数据库·算法·leetcode
马克学长2 小时前
SSM特种设备全生命周期管理系统8b729(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·ssm 框架·特种设备管理·全生命周期
小馒头学python2 小时前
openEuler 向量数据库:Milvus 相似度搜索性能测试
数据库·milvus·openeuler
ljh5746491192 小时前
thinkphp8 执行 php think 命令的时候指定日志存储路径
php
正在走向自律3 小时前
Oracle迁移实战:从兼容性挑战到平滑过渡金仓数据库的解决方案
数据库·oracle·国产数据库·金仓数据库·兼容性挑战·迁移成本
QAQalone3 小时前
MySQL实际项目中常用的 DDL 模板
数据库·mysql
翼龙云_cloud3 小时前
腾讯云渠道商:腾讯云轻量服务器和CVM有什么差异?
运维·服务器·云计算·php·腾讯云
Evand J3 小时前
【MATLAB例程】二维指纹对目标的一段轨迹定位,锚点数量可调。输出位置真值、估计值对比,附代码下载链接
开发语言·数据库·matlab