字段筛选关键在SQL层用select()精准裁剪,而非依赖PHP层accessor;必须显式select主键以保障分页,动态字段需白名单校验防注入,toSql()验证生成语句最可靠。

字段筛选不是“加个参数就能省流量”,关键在 select() 用对位置、选对字段、避开隐式加载陷阱。
什么时候必须用 select() 而不是靠模型 $casts 或 accessor?
数据库查询层的字段裁剪,和 PHP 层的数据加工是两件事。accessor 只在模型实例化后才执行,select() 是在 SQL 层就砍掉不需传输的列,直接减少网络 IO 和内存占用。
- 大文本字段(如
description、content)在列表页根本不需要,却随get()全量拉取 → 必须select('id', 'title', 'status') - 关联预加载(
with('user'))默认会查users.*,但列表只要头像和昵称 → 改成with(['user' => fn($q) => $q->select('id', 'name', 'avatar')]) - 用
find()或first()时,如果只改模型的$fillable或$hidden,数据库仍返回全部字段 → 无效,必须显式select()
select() 和 addSelect() 的区别与误用场景
select() 会覆盖默认的 *,而 addSelect() 是追加字段(常配合 groupBy 或聚合计算)。
- 写
select('id')->select('name')→ 后者覆盖前者,最终只查name,不是两个字段 - 想查主表字段 + 关联表某个计数,得用
select('posts.*')->addSelect(DB::raw('COUNT(comments.id) as comment_count')) - 在
withCount()后再手动addSelect()计算字段,注意别重复命名导致覆盖
分页时 select() 漏字段导致报错或数据异常
Laravel 分页依赖主键做游标或偏移,若 select() 没包含主键(如 id),paginate() 会抛出 Column not found 或返回空结果。
- 分页前务必确认
select()包含主键:select('id', 'title', 'updated_at') - 用复合主键模型?Laravel 原生分页不支持,得手动
simplePaginate()或弃用分页 - 关联排序 +
select()混用时,如join('users')->select('posts.*')->orderBy('users.name'),没问题;但若写成select('posts.title')就丢掉了posts.id,分页崩
API 返回字段动态控制:别在控制器里拼 select() 数组
用户请求 ?fields=id,title,slug 时,不能直接 select(explode(',', $request->fields)) —— 缺少白名单校验等于开放 SQL 字段注入入口。
- 定义允许字段白名单:
$allowed = ['id', 'title', 'slug', 'status', 'created_at'] - 过滤请求字段:
$fields = array_intersect($request->input('fields', []), $allowed) - 兜底至少含主键:
$fields = array_unique(array_merge(['id'], $fields)) - 禁止传入函数或表达式字段(如
DB::raw('NOW()')),这类必须硬编码在服务层
最易被忽略的是:select() 后调用 toSql() 看生成语句,比看文档更能暴露字段缺失、别名冲突、JOIN 字段覆盖等问题。


















