laravel模型默认值有三层机制:$attributes仅影响php层新建实例的初始值;数据库迁移中usecurrent()等确保sql插入时生效;creating事件用于运行时动态赋值。三者时机不同,不可混用。

模型构造时设置默认值用 $attributes
在 Laravel 模型里,数据库字段没传值时想自动填默认值,最直接的方式是在模型类里定义 $attributes 数组。它只影响新建模型实例(new Model 或 Model::make()),不触发数据库写入,也不干预 create() 时的批量赋值逻辑。
常见错误是以为设了 $attributes 就能覆盖表结构里的 DEFAULT,其实两者完全独立:数据库默认值由 SQL 插入时生效,而 $attributes 是 PHP 层的初始值。
-
$attributes中的键必须和数据库字段名完全一致(包括大小写、下划线) - 如果字段是 JSON 类型或需要序列化,别直接塞数组/对象——Laravel 不会自动序列化,得配合
$casts - 时间字段如
created_at别硬设字符串,优先用Carbon::now()或留空让 Laravel 自动处理
class User extends Model
{
protected $attributes = [
'status' => 'active',
'score' => 0,
'meta' => '[]', // 注意:JSON 字段要存字符串格式
];
}
数据库迁移里设默认值必须用 useCurrent() 或 useCurrentOnUpdate()
如果希望插入记录时数据库自己填默认值(比如 created_at、uuid),不能只靠 PHP 层,得在迁移文件里明确声明。Laravel 的 Schema 构建器对不同数据库行为不一致,尤其 MySQL 和 PostgreSQL 对 DEFAULT 的解释差别很大。
典型坑是写 ->default('uuid()') —— 这会被当字面字符串插入,不是函数调用;或者用 ->default(DB::raw('uuid()')),但 SQLite 不支持,导致迁移失败。
- 时间戳统一用
->useCurrent()(MySQL 5.6.5+ / PostgreSQL)或->useCurrentOnUpdate() - UUID 字段推荐用
->uuid()->default(DB::raw('gen_random_uuid()'))(PostgreSQL)或->string()->default(DB::raw('UUID()'))(MySQL),并确认驱动支持 - 普通字符串/数字默认值,直接
->default('draft')即可,安全跨库
creating 模型事件比 $attributes 更灵活但更重
当默认值依赖运行时上下文(比如当前用户 ID、请求来源、随机 token),$attributes 就不够用了。这时该用模型的 creating 事件,在保存前动态赋值。
注意它只在 save() 或 create() 时触发,make() 不会执行;而且如果模型被批量插入(insert()),事件根本不会触发——这是很多人踩坑的地方。
- 避免在
creating里做耗时操作(如 HTTP 请求、DB 查询),会影响性能 - 如果字段受
$fillable限制,确保默认值字段也在白名单里,否则会被过滤掉 - 多个事件监听器叠加时,顺序不可控,别假设执行先后
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
if (empty($model->uuid)) {
$model->uuid = Str::uuid()->toString();
}
if (! $model->sort_order) {
$model->sort_order = self::max('sort_order') + 1;
}
});
}
填充器(Accessors & Mutators)不是设默认值的工具
有人把 getFooAttribute 或 setFooAttribute 当成默认值入口,这是误解。Accessors 只影响读取时的值转换,Mutators 只影响赋值时的预处理,它们不解决“字段为空时该填什么”的问题。
比如写了个 setPriceAttribute 把空值转成 0,看起来像设了默认值,但实际只是拦截了赋值动作;如果字段压根没出现在插入数据里,这个方法根本不会被调用。
- Accessors/Mutators 适合格式转换(如大小写、单位换算),不适合兜底逻辑
- 如果真要用 Mutator 做兜底,得配合
isset($this->attributes['field'])判断,且仅限于显式赋值场景 - 更稳妥的做法还是回到
$attributes或creating事件











