默认排序必须通过重写 newQuery() 实现,如 return parent::newQuery()->orderBy('published_at', 'desc');$casts 和 $attributes 无效,全局作用域易冲突,关联查询应单独设置 orderBy。

模型定义时用 $casts 或 $attributes 无法控制查询排序
默认排序不是靠属性类型或初始值决定的,Laravel 模型本身不维护“返回顺序”。哪怕你在 $casts 里写 'sort_order' => 'integer',或者在 $attributes 里预设 'status' => 'active',都对 SELECT 的 ORDER BY 没有任何影响。
真正起作用的是查询构建器(Query Builder)或 Eloquent 的链式调用。模型类里唯一能影响默认行为的地方,是 boot() 中全局作用域,或直接重写 newQuery()。
-
newQuery()是最干净、最可控的方式:它会在每次生成查询前被调用,适合统一加orderBy - 别在
boot()里用static::addGlobalScope()加排序——全局作用域不能保证执行顺序,且容易和后续orderBy冲突,导致意外交互 - 如果只在某个关系里需要固定顺序,优先在关联方法里用
->orderBy('sort', 'asc'),而不是污染整个模型
在 newQuery() 里加 orderBy 是最稳妥的默认排序方案
这个方法会被所有静态查询(User::all()、User::where(...)->get())和实例查询($user->posts)底层调用,属于“查询起点”,天然适配各种使用场景。
示例:让 Post 模型默认按 published_at 降序:
public function newQuery()
{
return parent::newQuery()->orderBy('published_at', 'desc');
}
- 必须调用
parent::newQuery(),否则会丢失 Laravel 内置的查询能力(比如软删除处理) - 多个
orderBy可以链式追加,但注意:后写的会覆盖前面同字段的排序,例如->orderBy('id')->orderBy('created_at')是合法的,但->orderBy('id')->orderBy('id')第二个会生效 - 如果数据库字段名含空格或特殊字符(比如
`sort order`),得用DB::raw()包裹,直接写字符串会报错
orderByRaw 和 orderBy 在默认排序里的实际差异
普通 orderBy('field') 安全、可读、支持迁移兼容;而 orderByRaw() 更灵活,但也更危险——它绕过 Eloquent 的字段校验,拼进 SQL 原样执行。
常见误用场景:想按中文拼音排序、按 JSON 字段内值排序、或用 CASE WHEN 实现权重排序。这些必须用 orderByRaw,但要注意:
- MySQL 8.0+ 才支持
ORDER BY JSON_EXTRACT(...)这类语法,低版本会直接报错 -
orderByRaw("FIELD(status, 'draft', 'reviewing', 'published')")看似简洁,但换数据库(比如 PostgreSQL)就完全不可用 - 如果 raw 表达式里拼接了用户输入(哪怕只是 URL 参数),必须手动过滤或转义,否则就是 SQL 注入入口
分页 + 默认排序下容易漏掉的边界问题
当模型有 newQuery() 自动加 orderBy,再调用 paginate() 时,Laravel 会自动加 LIMIT 和 OFFSET,但不会帮你补 ORDER BY 字段的唯一性约束。
后果很现实:如果多条记录的 published_at 完全相同(比如批量导入时没设微秒),分页翻页可能重复出现或跳过某些行——MySQL 的排序稳定性不保证。
- 解决办法很简单:在默认
orderBy后追加主键,例如->orderBy('published_at', 'desc')->orderBy('id', 'desc') - 不要依赖
created_at当唯一排序依据,它精度只有秒(除非你显式用了useTimestamps()并启用了微秒) - 如果业务上真允许“时间相同即无序”,那应该去掉默认排序,由调用方明确传参,而不是让框架隐式决定
orderBy,但一旦涉及分页、跨库、关联查询或数据去重,很容易变成查不到数据或结果漂移。最保险的做法,是把“默认”理解成“调用方不指定时的兜底”,而不是“永远不变的真理”。


















