今天分享一个刚挖的坑,我们的产品有一个交互是用户可以复制自己的日程,我们当时实现的时候仅仅花了不到半小时就上线了,完事还夸了 Eloquent 真的是面面俱到,连复制功能都做好了,代码如下:
$item = Item::find($request->input('copy_from'))
->replicate()
->fill(['copy_from' => $request->input('copy_from')])
->save();
就这样完成了一条记录的复制,是不是非常简单?
后来因为我们为了加快查询,为 json 字段中某些关键字段加了虚拟字段:
Schema::table('items', function (Blueprint $table) {
$table->unsignedBigInteger('v_meeting_id')
->index()
->nullable()
->virtualAs('CASE WHEN `properties`->>"$.meeting_id" = "null" THEN 0 ELSE `properties`->>"$.meeting_id" END');
});
关于虚拟字段的内容可以参考:http://mysql.taobao.org/monthly/2017/12/09/
,在 Laravel migration 中的用法如上,不过我加了一些条件处理。
就在今天线上报错了,这个复制功能报错:
General error: 3105 The value specified for generated column 'v_meeting_id' in table 'items' is not allowed.
我检查 SQL 才发现 $item->replicate()
是直接对 Model 的 $attributes
字段复制,也就是不会经过 $fillable
字段过滤,导致最终生成的 insert 语句中存在虚拟字段赋值,导致了上面的报错。
于是复制逻辑不得不修改为:
$item = Item::create(
array_merge(
Item::find($request->input('copy_from'))->toArray(),
['copy_from' => $request->input('copy_from')]
)
);
回头再来看看 replicate
的源码:
public function replicate(array $except = null)
{
$defaults = [
$this->getKeyName(),
$this->getCreatedAtColumn(),
$this->getUpdatedAtColumn(),
];
$attributes = Arr::except(
$this->getAttributes(), $except ? array_unique(array_merge($except, $defaults)) : $defaults
);
return tap(new static, function ($instance) use ($attributes) {
$instance->setRawAttributes($attributes);
$instance->setRelations($this->relations);
$instance->fireModelEvent('replicating', false);
});
}
可以看到直接将当前实例的 $attributes
排除掉 $except
后写入新的实例,然后复制关系,并没有走 fill
方法,所以 $fillable
就没用上。
所以这是掉到了自己给自己挖的坑里。大家周末愉快!