Trait应统一提取校验GET参数,返回结构化数据而非布尔值:getPaginationParams()返回合法page/limit,默认兜底;getSearchParams()自动trim并支持嵌套q字段;getSortParams()解析sort且内置白名单防注入。

为什么直接在控制器里写 request()->query() 不够用
当多个控制器都需要从 GET 参数中提取、校验、转换同一组字段(比如 page、limit、sort、q),硬编码会导致重复逻辑、类型不一致、漏校验。Trait 的作用不是“复用代码”,而是把「参数契约」显式抽出来——谁用谁承担这个契约,而不是靠文档或注释约定。
注意:不要把验证规则塞进 Trait 里返回布尔值;应该返回处理后的结构化数据,让调用方决定怎么用(比如传给 Eloquent query builder 或 DTO)。
GetParamsTrait 应该返回什么,而不是怎么取
重点不是封装 request()->input(),而是统一参数语义和默认行为。例如:page 必须是整数且 ≥1,limit 要限制范围,sort 要支持 field:asc 解析。这些逻辑一旦散落在各处,改一处就漏三处。
-
getPaginationParams()返回['page' => 1, 'limit' => 15],自动过滤非法值 -
getSearchParams()提取q并 trim,同时支持q[title]、q[content]这种嵌套写法 -
getSortParams()解析sort=created_at:desc,name:asc成二维数组,带白名单校验
所有方法都应使用 request() 辅助函数(而非注入 Request 对象),避免测试时难 mock;但内部用 request()->query() 而非 request()->input(),明确只读 GET 参数。
容易踩的坑:类型转换和空值处理
Laravel 的 request()->integer('page') 在值为 null 或空字符串时返回 0,这会破坏分页逻辑(第 0 页非法)。必须手动判断:
public function getPaginationParams(): array
{
$page = request()->query('page');
$limit = request()->query('limit');
return [
'page' => is_numeric($page) && (int)$page >= 1 ? (int)$page : 1,
'limit' => in_array($limit, ['10', '15', '30', '50'], true) ? (int)$limit : 15,
];
}
另一个坑是 sort 字段未做白名单校验,攻击者传 sort=id;drop table users-- 没用,但传 sort=updated_at,foo.bar 可能导致 SQL 注入(如果直接拼进 order by)。Trait 里必须内置字段白名单,比如只允许 ['id', 'name', 'created_at']。
怎么在控制器里安全地用这个 Trait
别在 Trait 里调用 abort() 或抛异常——那是控制器的责任。Trait 只负责“尽力解析”,出错就回退默认值。
- 在控制器中
use GetParamsTrait;即可,无需$this->前缀调用方法 - 若需组合多个参数集,直接调用
$params = array_merge($this->getPaginationParams(), $this->getSortParams()); - 不要覆盖父类同名方法;Laravel 不禁止,但会掩盖意图——比如你写了
getSortParams(),结果 Model 里也有同名方法,调用时实际执行的是 Model 的,不是 Trait 的
最常被忽略的一点:GET 参数可能来自 URL 编码后的字符串,比如 q=hello%20world,request()->query('q') 已自动 decode,不用再 urldecode() —— 多解一次会损坏中文等字符。


















