谨慎使用 Laravel 的 Model 复制(replicate)功能

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值