路由决定了请求能进哪扇门,本节解决进门之后的头件事:把客户端带来的输入读出来、读对、读干净。Request 对象贯穿生命周期(回忆 2.2 的时序图),是你在控制器里最常打交道的对象。读完本节,你应当建立一条输入纪律:类型过滤、白名单取值、默认值兜底,让脏数据在第一步就被拦下。
初学者读输入的写法往往是这样:$_GET['id']、$_POST['title'] 直接取。这条路有三个隐患:类型不受控(客户端传来的一切都是字符串甚至数组)、未定义就报错、以及最危险的——取了不该取的字段。最后一条是真实的攻击面:模型批量赋值(第 4 章)会把请求里所有字段原样入库,如果攻击者在表单之外多塞一个 is_admin=1,而你恰好全量接收了 POST 数据,权限字段就被凭空改掉了。请求对象的 API 设计就是为了让你「按需取、带类型、带默认」,把这三件事变成顺手动作。
use think\Request; class Order extends BaseController { public function create(Request $request) { // 单值读取:第二个参数是默认值,第三个参数是过滤函数 $page = $request->get('page', 1, 'intval'); $kw = $request->get('kw', '', 'trim'); // 带类型的整体读取 $ids = $request->post('ids/a', []); // /a 强制数组 $price = $request->post('price/f', 0.0); // /f 浮点 $bool = $request->post('agree/b', false); // /b 布尔 // 白名单只取需要的字段——批量赋值场景的救命纪律 $data = $request->only(['book_id', 'num', 'remark']); // 明确不要的字段剔除(与 only 互补) $form = $request->post(); unset($form['is_admin']); // HEADER 与路由参数 $token = $request->header('X-Token', ''); $bookId = $request->param('id'); // 路由参数、GET、POST 合流读取 return json(compact('page', 'kw', 'data')); } }
几个写法要点:get/post/param 的分界是来源——param 是合流视图,适合「怎么传都行」的场景,权限敏感字段则永远从明确来源取;斜杠类型后缀(/a、/f、/b)一行完成类型转换,比散落的 intval 干净得多;only 白名单是批量赋值前的标准前置动作,与模型层的字段许可(第 4 章)构成双保险。
现代前端常直接提交 JSON。框架会自动解析内容类型为 JSON 的请求体,POST 读取照常工作;原始内容也有出口。上传文件则走专用的 file 读取:
// JSON 请求体:Content-Type: application/json // {"book_id": 42, "num": 1} $bookId = $request->post('book_id'); // 自动解析,无需 json_decode $raw = $request->getContent(); // 原始报文体,特殊场景自己解析 // 上传文件:返回 UploadedFile 对象(安全处理见第 7 章) $file = $request->file('cover'); if ($file) { $path = \think\facade\Filesystem::putFile('cover', $file); }
| 场景 | 反模式 | 规范写法 |
|---|---|---|
| 取页码 | $page = $_GET['page'] |
$request->get('page', 1, 'intval') |
| 批量入库 | $request->post() 全量传给模型 | $request->only([...白名单]) |
|
| 取整数 ID | $request->param('id') 用前再转型 |
路由 pattern 加正则 + 方法签名 int |
| 判断请求方式 | $_SERVER['REQUEST_METHOD'] | $request->isPost() / isGet() |
|
| 判断 AJAX | 读 X-Requested-With 头 |
$request->isAjax() |
这份清单值得直接搬进团队的代码评审模板。核心原则一句话:输入在进入业务逻辑之前,必须完成类型与范围的收敛——至于「值合不合法」(比如数量上限、库存余量),那是第 6 章验证器与第 4 章业务逻辑的事。
⚠️ 常见坑:不要相信任何来自客户端的「隐藏字段」。表单里藏的 user_id、price 在客户端眼里都是明文可改的——价格、归属这类敏感值要么从服务端会话取,要么入库前用服务端数据重新核对。
💡 关键直觉:把请求对象当成「不可信的外部世界」的唯一入口,所有读取都过它并留类型痕迹——日后排查数据异常,顺着 request 调用点翻,总能找到入口。
背景:3.1 练习里的下单接口目前裸取 POST。操作:改造成 $request->only(['book_id','num','remark']) 白名单读取,book_id 与 num 加类型后缀,remark 过 trim;路由层给参数加 pattern;再故意用命令行模拟一次「塞了 is_admin 字段」的请求,对比改造前后的入库差异。结果示例:改造后多出来的字段被 only 静默丢弃,入库字段与设计完全一致。解读:这一步防的不是当前用户,是未来的你忘了哪一处没加白名单——纪律前置比事后审计便宜得多。变式:把同一套滤网套到搜索接口上,体会「合流读取 param 用在哪最合适、哪里绝不能用」。
$_GET 与 $_POST。/a、/f、/b 等一行完成类型收敛,配默认值参数兜底。only 取法是批量赋值场景的标准前置,未声明即不存在。输入读干净了,结果怎么体面地送回去?下一节看响应对象与四种输出形态。