ThinkPHP排序需白名单校验字段和方向,优先用关联数组语法order(['field' => 'asc']),关联表排序须显式join并映射别名,分页时order必须前置且appends透传参数。
ThinkPHP 的排序不是模型自带的“开关”,而是查询时动态附加的行为。模型本身不保存排序逻辑,真正起作用的是你在 调用查询方法前 传给 order() 的参数。用得好,它能灵活响应用户选择;用错,轻则排序失效,重则触发 SQL 注入。
用户传来的 sort=price&dir=desc 看似干净,但攻击者可能发 sort=id%20DESC,%20(SELECT%20password%20FROM%20user)%20--。ThinkPHP 的 order() 默认不做字段合法性检查,字符串拼接等于裸奔。
['id', 'name', 'create_time', 'status', 'price']
in_array() 严格判断用户请求的字段是否在此列表中,不在就 fallback 到默认字段(如 'id')'asc' 或 'desc',其他值一律转为 'asc'
Db::raw() 或字符串拼接方式传参,例如 order($field . ' ' . $dir) 是高危写法ThinkPHP 6.0+ 推荐且安全的写法是数组形式:order(['field' => 'asc'])。它会自动给字段加反引号,避免字段名为保留字(如 order、group)时报错,也防止大小写或下划线引发歧义。
UserModel::order(['price' => 'desc', 'id' => 'asc'])->select()
['price', 'desc'] 这种旧式写法,易被版本兼容性问题坑到parseOrder() 自动识别——它只认模型 $schema 中存在的字段,不处理关联路径或别名想按地址城市排序?withCount() 或 with() 加 order('address.city') 是无效的。TP 不会解析点号路径,也不会自动补 JOIN 条件,SQL 直接报错 Unknown column 'address.city'。
立即学习“PHP免费学习笔记(深入)”;
join() 并设定表别名,例如:->join('address a', 'user.id = a.user_id')
order(['a.city' => 'asc'])
['city' => 'a.city', 'province' => 'a.province'],再根据用户选择取对应值withCount() 统计后参与排序?不行——统计字段不在主 SELECT 中,得改用 join + group + count() 显式构造调用 paginate() 时,如果你先写了 order(),但没把 sort 和 dir 带进分页链接,第二页就会回到默认排序——因为 paginate 生成的翻页 URL 默认不携带这些参数。
order() 必须写在 paginate() 调用之前,且不能被后续链式方法覆盖appends():->appends(['sort' => $field, 'dir' => $dir])
count() 查询可能与主查询不一致,需手动传入 count 参数避免分页错乱sort 和 dir 作为缓存键的一部分,否则不同排序共用同一缓存