Eloquent 之 Fill 方法源码解析

早上起来看到有关于 Laravel Mass-Assignment (批量赋值) 的讨论,于是动手写了一篇关于 fill 方法的源码解析~

第一次写,如果哪里写得有问题的话请大家多多包涵~,欢迎在评论区给我指正哦,如果觉得还不错的话,就给个赞鼓励我一下吧~ : )

正文分割线


上一次分析了 Laravel 中的模型事件与观察者模式,这次来解析一下 Eloquent 中的 fill

Laravel 的童鞋应该都知道,fill 方法是一个给 Eloquent 实例赋值属性的方法,让我们点开 fill 方法先看一看它的源码:

这里笔者所使用的版本为 Laravel 5.5最新版,为了方便阅读,删除掉了注释

public function fill(array $attributes)
{
    $totallyGuarded = $this->totallyGuarded();

    foreach ($this->fillableFromArray($attributes) as $key => $value) {
        $key = $this->removeTableFromKey($key);

        if ($this->isFillable($key)) {
            $this->setAttribute($key, $value);
        } elseif ($totallyGuarded) {
            throw new MassAssignmentException($key);
        }
    }

    return $this;
}

首先可以看到,Laravel 会先去调用一个自身的 totallyGuarded 方法,让我们点开这个方法:

public function totallyGuarded()
{
   return count($this->getFillable()) == 0 && $this->getGuarded() == ['*'];
}

可以看到这个方法的作用就是去获取自身的 fillableguarded,然后判断他们是否都为 不可批量赋值 状态,最后返回一个布尔值

// 返回一个 True or False 的布尔值
// 如果未设置 fillable 与 guarded,则会返回 True (注意,在这种情况下,此 `Model` 是不允许批量赋值任何属性的哦)
// 反之则返回 False
$totallyGuarded = $this->totallyGuarded();

Ok,让我们回到刚才的 fill 方法继续往下看

接下来是一个 foreach 循环,但是在循环之前,Laravel 对传入的赋值属性执行了 fillableFromArray 这个方法,再点进去看一下,

protected function fillableFromArray(array $attributes)
{
    if (count($this->getFillable()) > 0 && ! static::$unguarded) {
        return array_intersect_key($attributes, array_flip($this->getFillable()));
    }

    return $attributes;
}

此方法会检测你是否在 fillable 数组中定义了值,如果定义了值,则会返回 fillableattributes 相交的值,如果没有,则返回 attributes 自身

然后回到 fill ,在调用 fillableFromArray 对参数进行处理之后,现在返回的值只剩我们允许批量赋值的属性了 (如果你定义了)

循环第一行,先使用 removeTableFromKey 对参数的 Key 进行处理,删除键中的表名,此方法就不做过多讲解,只是一个字符串拆分取值的函数

$key = $this->removeTableFromKey($key);

接着往下看,Laravel对将要进行填充的每个属性都调用了 isFillable 方法来确保此属性是可以被填充的,让我们看一看它的源码:

public function isFillable($key)
{
   if (static::$unguarded) {
       return true;
   }

   if (in_array($key, $this->getFillable())) {
       return true;
   }

   if ($this->isGuarded($key)) {
       return false;
   }

   return empty($this->getFillable()) &&
       ! Str::startsWith($key, '_');
}

可以看到,在此方法中 Laravel 先判断了此 Model 是否禁用了守卫 (guarded),如果此 Model 并未启用守卫,那么直接返回 True

if (static::$unguarded) {
    return true;
}

如果启用了守卫,那么会判断一下此属性是否存在于 fillable 数组中,如果存在,则返回 True,

if (in_array($key, $this->getFillable())) {
    return true;
}

如果此属性不存在于 fillable 数组中,那么 Laravel 会再次判断此属性是否存在于 guarded 数组中,如果存在于此数组中,那么此属性就不是一个可以被批量赋值的属性,那么就会直接返回 False

if ($this->isGuarded($key)) {
    return false;
}

如果以上都不符合,那么 Laravel 在最后会判断一下自身的 fillable 数组是为空并且此属性是以 _ 开头,然后返回一个布尔值

return empty($this->getFillable()) && ! Str::startsWith($key, '_');

然后回到 fill 方法接着看,如果此属性通过了 isFillable 方法的过滤,那么将此属性赋值给自身 (因为时间有限,setAttribute 这个方法就不细讲啦~),

$this->setAttribute($key, $value);

如果没有通过 isFillable 方法的过滤,那么 Laravel 会判断一下自身 Model 是否处于不限制任何属性批量赋值的状态,如果不是,那么 Laravel 会直接抛出一个 Exception

// 判断此属性是否通过了检测
if ($this->isFillable($key)) {
    // 将此属性赋值给自身
    $this->setAttribute($key, $value);

// 如果没有通过检测,那么判断一下自身 `Model` 是否为全部不可批量赋值状态,如果是,那么会抛出一个 `Exception`
} elseif ($totallyGuarded) {
    throw new MassAssignmentException($key);
}

在对所有的属性进行检测并且赋值后, Laravel 会将自身返回

return $this;

解析完毕,以上就是 fill 方法的源码啦~,最后来一个小结

在你调用 fill 方法的时候,Laravel 首先就会去检测当前 Model 的状态,

当你设置了 fillable 数组,没有设置 guarded 数组时,那么此 Model 会处于 仅可批量赋值指定属性 的状态
当你没有设置 fillable 数组,却设置了 guarded 数组时,那么此 Model 会处于 可批量赋值任何属性 的状态
至于你同时设置了 fillableguarded 数组的情况就不去讨论了,因为这样做本身就是被 Laravel 所禁止的

然后调用 fillableFromArray 去获取 attributesfillable 数组的交集,如果你没有定义 fillable 或者禁用掉了守卫,那么此方法会直接返回 attributes

然后 Laravel 会对返回的数组做一个循环,在这个循环中 Laravel 会对每一个属性调用 isFillable 方法检测这个属性是否可以被填充,如果没有通过此方法的检测(不存在于fillable 数组中并且没有设置 guarded 数组或存在于 guarded 数组中),那么 Laravel会检测当前 Model 是否处于 仅可批量赋值指定属性 状态,如果是,那么会直接抛出一个 Exception

然后 Laravel 会返回完成赋值操作后的 $this

以上就是 Eloquentfill 方法的源码解析啦~,Laravel 的源码读下来还是很清晰易懂的~,不得不再次佩服 Laravel 的设计,不愧为 巨匠级框架

本帖已被设为精华帖!
本帖由 Summer 于 6年前 加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!