今天分享一个刚挖的坑,我们的产品有一个交互是用户可以复制自己的日程,我们当时实现的时候仅仅花了不到半小时就上线了,完事还夸了 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
就没用上。