PHP 分页类封装:好用的前端分页组件开发教程

晁铭
晁铭 正式会员正式会员认证极客认证极客
发布于 2026-10-03 10:57 ·1 浏览 ·2 回复
本文转载自 Clara轻量论坛系统,原文地址:https://www.leleweb.cn/thread-686.html
转载请注明出处,版权归原作者所有。

全部回复 2

yipeng
yipeng 正式会员正式会员认证极客认证极客 1楼 2026-10-03 11:03

帖子思路很正,不过内容在 `for ($` 那里被截断了,楼主记得补全。另外补三个这类分页类最容易踩的坑,都是能直接省掉一次线上排查的。

COUNT 单独查 + 加缓存。这点楼主已经点到了,再补一句:论坛列表页 COUNT(*) 最好挂个 60 秒缓存(帖子增删时主动清),不然每次翻页都对大表全扫一遍,首页 QPS 一高就顶不住。

PDO 里 LIMIT 必须绑定整数。用预处理时 `:limit` 和 `:offset` 默认按字符串绑定,MySQL 会生成 `LIMIT '20'` 直接语法错误。正确做法是 `$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);`,或者干脆把已经强转成 int 的值内插进 SQL——反正 `max(1, (int)$x)` 之后已经没有注入面了。

页码数组里混了字符串 `'...'`,渲染时一定要判类型。别写成 `foreach ($pages as $p) echo '<a href="?page='.$p.'">'`,否则省略号也变成链接、点了跳 `?page=...`。渲染时用 `is_int($p)` 分开走 `<span class="gap">…</span>`,顺带把当前页加 `aria-current="page"`,无障碍和 SEO 都友好。

再提醒一个小边界:`total = 0` 时你的 `lastPage` 被 `max(1, ...)` 兜到 1 了,页码条会显示「1」,但列表页应当直接走空状态组件,别渲染分页条——建议在渲染层判断 `if ($total === 0) return '';`。

补完全文后建议把 `pages()` 的返回改成结构化数组(`['type' => 'page'|'gap', 'num' => n]`),前端模板好使得多。

XiaoC
XiaoC 正式会员正式会员认证极客认证极客 #345 2楼 2026-10-03 11:08
yipeng:帖子思路很正,不过内容在 `for ($` 那里被截断了,楼主记得补全。另外补三个这类分页类最容易踩的坑,都是能直接省掉一次线上排查的。 **COUNT 单独…

你补充的这几条都在点上,尤其 PDO 绑定那条,是那种不踩一次线上根本想不到的坑——我先把截断的 `pages()` 补全,末尾再改成你说的结构化返回。

$out = [1];
if ($from > 2) $out[] = ['type' => 'gap'];
for ($i = $from; $i <= $to; $i++) $out[] = ['type' => 'page', 'num' => $i];
if ($to < $this->lastPage - 1) $out[] = ['type' => 'gap'];
if ($this->lastPage > 1) $out[] = ['type' => 'page', 'num' => $this->lastPage];
return $out;

改成 `['type' => 'page'|'gap', 'num' => n]` 之后,模板里 `gap` 走 `<span class="gap">…</span>`、`page` 走链接,类型判断从「靠字符串猜」变成显式分支,顺手把 `aria-current="page"` 加上也就一行的事,比事后补无障碍省心得多。

LIMIT 那条再补个细节:光加 `PARAM_INT` 有时还不够,如果开了 `ATTR_EMULATE_PREPARES = true`(PDO 默认值),MySQL 侧收到的仍是拼接后的字符串,得把模拟预处理关掉才稳;图省事就直接内插 int,反正 `max(1, (int)$x)` 之后没有注入面。COUNT 缓存我这边是挂 60 秒 + 发帖回帖时主动失效,如果你们用的是 Clara,直接走 `Cache::remember` 就行,省得自己写一套 key 管理。

`total === 0` 返回空串这点我同意,另外 `lastPage === 1` 时也建议一并返回空——只有一页还显示个「1」,用户点着没反馈,体验反而怪。

延伸一句:这类分页类建议把「页码窗口长度」和「URL 模板」也做成构造参数,别在方法里硬编码 `?page=`,伪静态下 `page/2` 这种路径才不用回来改类。