分页必须同时携带搜索参数和分页参数,sql需两次查询(count+limit),$_get参数须过滤转义,page值须校验有效性。

分页参数必须和搜索条件一起拼进 URL
用户一搜就回到第一页,或者翻页后搜索条件丢了,根本原因是 $_GET 里只保留了 page,漏掉了 keyword、category_id 这类搜索字段。URL 必须同时携带搜索参数和分页参数,比如 /list.php?keyword=php&page=2,而不是只传 page=2。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 用
http_build_query(array_merge($_GET, ['page' => $next_page]))生成下一页链接,自动继承所有当前搜索参数 - 手动拼接时务必过滤掉
page再重加,避免重复:先unset($_GET['page']),再http_build_query($_GET) . '&page=2' - 别在表单里用
method="get"后直接提交就完事——提交后 URL 会丢掉原有page,得用 JS 拦住表单,读取当前page值塞进隐藏域或重写 action
SQL 查询要同时处理 WHERE 和 LIMIT
只写 WHERE keyword LIKE ? 不够,分页依赖 LIMIT 和 OFFSET,但 OFFSET 在有搜索条件时容易错算总数。很多人直接 SELECT * FROM posts WHERE ... LIMIT ?,?,却忘了查总条数做页码导航。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 必须执行两次查询:一次
SELECT COUNT(*)带完整WHERE条件(不含LIMIT),用于算总页数;一次SELECT *带LIMIT和OFFSET -
OFFSET要基于当前页码计算:$offset = ($page - 1) * $per_page,注意$page至少为 1,且需校验是否超总页数 - 别用
SQL_CALC_FOUND_ROWS——MySQL 8.0 已弃用,且在带 WHERE 的复杂查询中不准,老老实实用两次查询更稳
$_GET 参数必须过滤和转义,尤其用于 SQL 和 HTML 输出时
用户在搜索框输 ' OR 1=1 -- ,如果直接拼进 SQL,就成注入;输 <script>alert(1)</script> 又可能触发 XSS。这不是“可能”,是只要没处理就一定会出问题。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 搜索关键词进 SQL 前,必须用 PDO 的
prepare()+bindValue(),例如$stmt->bindValue(':keyword', "%{$_GET['keyword']}%", PDO::PARAM_STR) - 把关键词输出到 HTML(如搜索框回显、结果提示)前,必须过
htmlspecialchars($_GET['keyword'], ENT_QUOTES, 'UTF-8') - 非字符串搜索字段(如
status、user_id)要强制类型转换:(int)$_GET['user_id']或filter_var($_GET['status'], FILTER_SANITIZE_STRING)
页码链接的 page 参数必须校验有效性
有人直接用 $_GET['page'] 算 OFFSET,结果传个 page=-100 或 page=abc,要么查出错数据,要么报 Warning 甚至 SQL 错误。这不是边界测试,是上线必踩的坑。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 用
$page = max(1, (int)$_GET['page'])强制转整型并兜底最小值 - 查完总条数
$total后,立即判断:if ($page > ceil($total / $per_page)) { $page = 1; } - 不要依赖前端传来的
page做任何逻辑分支,它只是个提示,最终分页位置由后端校验后决定
最常被绕开的是搜索参数的 URL 继承逻辑——不是写不出来,是调试时懒得看地址栏有没有带上 keyword,结果上线就被用户反馈“一翻页就清空”。还有人把 $_GET 全部塞进 SQL 绑定,忘了有些键根本不存在,导致 Notice 报错。这些都不是 PHP 问题,是没把请求参数当输入边界来对待。











