ThinkPHP 8.0 路由参数绑定模型必须显式调用 bind(),不会自动根据类型提示查库注入;不调用则参数为 null;bind 键名须与路由变量完全一致,模型需继承 think\Model 并正确声明主键,失败直接 404 且不可捕获。

路由参数绑定模型必须显式调用 bind()
ThinkPHP 8.0 不会根据控制器方法的类型提示(如 User $user)自动查库并注入模型实例——这和 Laravel 不同。不调 bind(),$user 就是 null,不是空对象,也不是抛异常,而是彻底没走绑定逻辑。
正确写法是在 routes/app.php 中定义路由时链式调用:
Route::get('user/:id', 'User/read')->bind(['id' => \app\model\User::class]);
- 键名
id必须与路由变量:id完全一致,大小写敏感 - 模型类需继承
think\Model,且命名空间要正确引入(如use app\model\User;) - 如果路由是
user/:uid,但模型主键是uid,而模型里没写protected $pk = 'uid';,默认仍按id查,必 404
bind() 失败直接返回 404,无法跳过或自定义
绑定失败(如数据库无对应记录、模型类不存在、主键字段不匹配)时,框架在进入控制器前就抛出 HttpException(404),不会执行任何中间件或控制器逻辑。这个行为不可关闭,也不走 miss 路由。
若需返回 JSON 提示而非页面 404,只能改写绑定逻辑:
立即学习“PHP免费学习笔记(深入)”;
Route::get('user/:id', 'User/read')->bind(function ($value) {
$user = \app\model\User::where('id', $value)->first();
if (!$user) {
abort(404, ['code' => 404, 'msg' => '用户不存在']);
}
return $user;
});
- 闭包接收路由变量值(如
$value是字符串'123'),需手动查库 - 返回模型实例即可,返回
null或非模型对象仍会触发 404 - 注意:此时你失去了 TP 默认的缓存、软删除等模型行为封装,得自己处理
主键非 id 时必须显式指定字段
TP 默认只认 id 字段做主键查询,哪怕路由变量叫 :uid、模型里也有 $uid 属性,只要没声明主键,就查不到。
两种解法,选其一即可:
- 在模型中定义:
protected $pk = 'uid'; - 在
bind()时传数组:['uid' => [\app\model\User::class, 'uid']]
后者更灵活,适合同一模型在不同路由中用不同字段绑定(比如一个用 uid,一个用 sn),但别漏掉第二个字段参数——写成 ['uid' => \app\model\User::class] 等于没写,还是按 id 查。
别和路由变量名、方法参数名混淆
bind() 只负责把路径变量(如 :id)转成模型对象,和控制器方法签名无关。你完全可以写:
public function read($user)
{
// $user 是模型实例,不是原始 ID
}
但前提是路由定义里写了 ->bind(['id' => User::class]),且路径是 user/:id。如果路径是 user/:uid 却 bind id,或者方法参数叫 $u 却没给默认值,照样报错或为 null。
真正容易被忽略的是:绑定发生在调度早期,一旦失败,连日志都打不出来——你看到的只是空白页或 404,得回头检查数据库记录是否存在、主键是否对得上、bind 键名是否拼错。



















