模型仅Query后更新, fillable或guarded不生效的解决方案
今天同事问了我一个问题:批量更新为啥
guarded
的设置无效?
先看代码:
// 模型定义
protected $guarded = ['name'];
// guarded不生效操作
Model::query()->update(['name'=>'Robin']);
这种方式本质是直接执行了sql, 模型本身并没有实例化。只有当模型实例化成对象后,对应的事件才生效。同理,如果直接使用query()->update()
, Observer对应的事件也是不生效的。
// guarded生效
$model = Model::query()->find(1);
$model->update(['name'='Robin']);
如何解决?起初靓仔说先把模型都查出来,然后each
一下循环改,但是觉得既然都批量了,还改循环,模型还得全查出来不是还得吃资源吗?我一听,有道理,遂着手改造。
看一眼Model
的源码:
abstract class Model implements Arrayable, ArrayAccess, HasBroadcastChannel, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable
{
/**
* Create a new Eloquent query builder for the model.
*
* @param \Illuminate\Database\Query\Builder $query
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function newEloquentBuilder($query)
{
return new Builder($query);
}
}
我就不赘述我的心路历程了,总之我发现使用query()
后,最终会通过newEloquentBuilder
方法得到一个Illuminate\Database\Eloquent\Builder
,那么只要重写这个方法,返回一个自定义的Builder即可
。
开始改造:
随手挑个模型吧,这里用User
当小白鼠
class User extends Model
{
protected $fillable = ['name'];
public function newEloquentBuilder($query)
{
return new UserBuilder($query, [
'fillable' => $this->fillable
]);
}
}
自定义UserBuilder
<?php
namespace App;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Illuminate\Database\Query\Builder as QueryBuilder;
class UserBuilder extends Builder
{
protected $options;
/**
* Create a new Eloquent query builder instance.
*
* @param \Illuminate\Database\Query\Builder $query
* @return void
*/
public function __construct(QueryBuilder $query, $options)
{
$this->query = $query;
$this->options = $options;
}
/**
* Update records in the database.
*
* @param array $values
* @return int
*/
public function update(array $values)
{
// $user = Auth::user();
// unset($query['title']);
$newValues = Arr::only($values, $this->options['fillable']);
return parent::update($newValues);
}
}
现在query()->update()
也能通过$fillable
限制字段了,问题解决。
本文使用的其实是一种Hack方法,如果大家有更好的解决方案还望不吝指教。
推荐文章: