UUID作主键须禁用自增、设$keyType='string'且$incrementing=false,迁移中用uuid()而非id(),模型创建时用Str::uuid()生成ID,确保路由绑定与API返回均为字符串类型。

UUID 作为主键必须禁用自增
Laravel 默认给 id 字段加了 autoIncrement(),但 UUID 是字符串,数据库不支持对字符串字段启用自增。不关掉它,迁移会报错:SQLSTATE[HY000]: General error: 1075 Incorrect table definition。
实操上要在迁移里显式关闭自增,并指定类型:
Schema::create('users', function (Blueprint $table) {
$table->uuid('id')->primary(); // 不写 ->autoIncrement()
$table->string('name');
});
-
uuid()方法生成的是CHAR(36)(MySQL)或UUID类型(PostgreSQL),不是整数 - 如果用
id()方法,Laravel 9+ 默认就是uuid(),但老版本仍是bigIncrements(),不能混用 - SQLite 对 UUID 支持弱,主键得用
string('id', 36)->primary()手动模拟,且无法用原生 UUID 函数生成
模型里要声明 $keyType 和 $incrementing
否则 Eloquent 仍按整数主键逻辑处理:尝试用 + 计算 ID、默认排序用 id ASC、批量插入时可能跳过 id 字段。
必须在模型中明确告诉框架这是字符串主键:
class User extends Model
{
protected $keyType = 'string';
public $incrementing = false;
}
-
$keyType = 'string'影响序列化、路由绑定、JSON 输出时的类型(避免被转成数字或科学计数法) -
$incrementing = false关键:它让save()不跳过id字段,也让create()不试图生成自增 ID - 漏设
$incrementing = false是最常见翻车点——新建记录后$user->id是null,但数据库里其实有值
生成 UUID 的时机和位置选哪个
不能依赖数据库生成(如 MySQL UUID() 函数),因为 Eloquent 在保存前就需要知道 id 值(例如关联关系、事件监听、日志记录)。
推荐在模型创建时就生成,而不是在迁移或 DB 层:
- 用
Uuid::uuid4()->toString()(laravel/framework ≥ 9.2,内置) - 或
Str::uuid()->toString()(更简洁,底层一样) - 在模型的
booting钩子里赋值,确保每次 new 都有 ID:
protected static function booting()
{
static::creating(fn ($model) => $model->id ??= (string) Str::uuid());
}
- 别在构造函数里做——Eloquent 从数据库 hydrate 实例时不走
__construct - 别用
creating事件回调而不用booting+ 匿名函数——后者能保证顺序,避免被其他 trait 覆盖 - PostgreSQL 用户注意:
uuid-ossp扩展不是默认启用的,Str::uuid()更可靠
路由模型绑定和 API 返回要注意类型
Route 模型绑定默认用 find($value),对字符串 ID 没问题;但如果你写了 where('id', $id) 这类硬编码查询,PHP 可能把 UUID 字符串当整数比较(尤其带连字符开头是 0 的时候),导致查不到。
API 返回时也容易出问题:
- 前端 JS 会把长数字字符串(如
"12345678901234567890")自动转成科学计数法,UUID 必须始终以字符串形式传输 - Laravel 默认 JSON 序列化会保持字符串类型,但如果你手动 cast 成
int或用了casts = ['id' => 'integer'],就完蛋了 - 测试时用
assertJsonFragment(['id' => 'xxx'])比assertJsonMissing更稳妥,避免类型隐式转换干扰
真正麻烦的是跨服务场景:一个用 UUID 的 Laravel 服务调另一个用 Snowflake 的 Go 服务,ID 类型不一致、长度不同、排序逻辑冲突——这时候光配对 Laravel 没用,得统一 ID 生成契约。


















