应优先使用 withQuery() 替代 appends(),因其自动过滤分页参数、兼容嵌套结构、明确走 query string 路径且调用时机早;若用 appends(),须手动排除 page 等参数、确保链式调用顺序、避免伪静态路由冲突,并确认分页对象已实例化为支持该方法的驱动类。

appends() 传参前没过滤 page 参数,导致分页链接里出现 page=1&page=2
这是最常见也最容易被忽略的问题:appends() 会原样把数组塞进 query string,如果传入的数组里还带着 page,而分页组件自己又在生成链接时加了一次 page=2,最终 URL 就变成 ?search=abc&page=1&page=2。浏览器和服务器通常只取第一个或最后一个,结果就是跳转错页。
必须手动排除 page(以及你自定义的分页参数名,比如 p):
-
array_diff_key($_GET, ['page'])是基础写法,但仅适用于扁平参数 - 含嵌套结构(如
filter[status]=1&filter[cat]=2)时,$_GET在 PHP 中已自动展开为多维数组,直接array_diff_key会丢掉子键——得先用flatten_params()打平 - TP6+ 更推荐用
withQuery(input('param.'))替代appends(),它自动处理嵌套、过滤分页参数、兼容点号和方括号语法
在 paginate() 之后才调用 appends(),模板里 render() 已经跑完了
appends() 不是“设置默认行为”,而是链式修改当前分页对象的 URL 构建逻辑。如果你在控制器里写了:
$list = $query->paginate(10);
$this->assign('list', $list);
$list->appends(['q' => 'test']);
那 appends() 实际上是在 assign 之后执行的,模板中 {$list->render()} 拿到的是未追加参数的旧对象。
立即学习“PHP免费学习笔记(深入)”;
正确顺序必须是:
- 先调用
paginate() - 紧接着链式调用
appends()或withQuery() - 再
assign()给模板 - 别拆成两行赋值再调方法,容易漏掉链式返回值
用了伪静态路由,appends() 却往 path segment 里塞参数
ThinkPHP 的 appends() 默认按 query string 拼接,但如果你启用了 url_html_suffix 或自定义了带变量的路由(比如 /search/:q/p/:page),框架可能尝试把追加参数塞进路径段,而不是 ? 后面——这时参数就“消失”了,因为没匹配上路由规则。
验证方式:打开分页链接,看地址栏是 /list?page=2&q=abc 还是 /list/abc/2。后者说明路由机制接管了参数解析,appends() 失效。
- 优先改用
withQuery(),它明确走 query string 路径,不参与 path segment 匹配 - 若必须用
appends(),确保路由配置里没有把搜索参数声明为路径变量(比如避免search/:q这种写法) - 检查
config/app.php中url_route_must和url_html_suffix是否干扰了 query 解析逻辑
TP6.3+ 里直接调 appends() 报 “Call to undefined method”
不是所有分页对象都支持 appends()。TP6 默认返回的是 think\Paginator 实例,但它本身没有 appends() 方法——这个方法实际挂在 think\paginator\driver\Bootstrap 等具体驱动类上,只有调用过 render() 或显式指定了模板类型后,对象才被“升级”为带该方法的实例。
所以直接对 $list 调 appends() 会报错,尤其在没设 template 配置或用了自定义分页类时。
- 安全做法是统一用
withQuery(),它是模型查询构建器的方法,调用时机早、兼容性好 - 如果坚持用
appends(),确保分页对象已实例化为驱动类(比如通过$list->render()触发一次,再调用;但不推荐这种副作用写法) - 检查是否误用了
simple()分页——它返回的是简化版 Paginator,压根不支持appends()
withQuery() 直接作用于查询构造器,天然规避了对象生命周期和路由解析阶段的错位;而 appends() 是个“事后补救”,容易卡在中间环节失效。



















