在保存时按正确顺序更新Laravel Eloquent mutator的值


Updating Laravel Eloquent mutator values on save in the correct order

假设表中有五个字段:价格、成本、处理、运输、利润

字段shippingprofit有突变,因为它们是基于计算的。

public function setShippingAttribute($value) {
    $this->attributes['shipping'] = $this->attributes['handling'] + $value;
}
public function setProfitAttribute($value) {
    $this->attributes['profit'] = $this->attributes['price'] - $this->attributes['shipping'] - $this->attributes['cost'];
}

我的问题是双重的,我正试图找出最优雅的解决方案:

  1. shipping属性总是有它的mutator在保存期间首先调用,所以profit属性将是正确的。

  2. 每次在price, costhandling属性上发生变化时,我需要profit和/或shipping属性也要更新(再次,按适当的顺序)。

我如何有效地解决我的突变子的这两个问题?

注:这是我对这个特定模型的计算的简化版本。实际上,在其他20个示例中只有这两个示例具有突变,因此我需要根据许多相互依赖的计算进行可伸缩。

为了解决这个问题并尽可能地保持一切都是自我维护的,我必须在这个模型上创建一个有点复杂的解决方案。下面是基于原始帖子的简化版本,但可以很容易地扩展。

对于每个mutator,我创建了访问器,以确保每次对数据的新请求都进行计算:

public function getShippingAttribute($value) {
    return $this->getAttribute('handling') + $value;
}
public function getProfitAttribute($value) {
    return $this->getAttribute('price') - $this->getAttribute('shipping') - $this->getAttribute('cost');
}

访问器使用getAttribute方法而不是直接的属性访问,以确保自动调用幕后的任何其他突变或关系。

然后使用mutator来确保数据缓存在数据库中,以便将来对它们进行任何查询(这与仅仅追加数据相反):

public function setShippingAttribute($value) {
    $this->attributes['shipping'] = $this->getShippingAttribute($value);
}
public function setProfitAttribute($value) {
    $this->attributes['profit'] = $this->getProfitAttribute($value);
}

为了确保在发生更改时更新所有依赖字段,我将以下内容添加到模型中:

protected static $mutatorDependants = [
    'handling'        => [
        'shipping'
    ],
    'price'              => [
        'profit'
    ],
    'cost'              => [
        'profit'
    ],
    'shipping'              => [
        'profit'
    ]
];
protected $mutatorCalled = []; // Added to prevent unnecessary mutator calls.

如果上述任何字段被更改,则它们的依赖项将在saving事件上自动重新计算:

public static function boot()
{
    parent::boot();
    static::saving(function ($model) {
        /** @var 'Item $model */
        foreach ($model->getDirty() as $key => $value) {
            // Ensure mutators that depend on changed fields are also updated.
            if (isset(self::$mutatorDependants[$key])) {
                foreach (self::$mutatorDependants[$key] as $field) {
                    if (!in_array($field, $model->mutatorCalled)) {
                        $model->setAttribute($field, $model->attributes[$field]);
                        $model->mutatorCalled[] = $field;
                    }
                }
            }
        }
        $model->mutatorCalled = [];
    });
}

它的结构方式是,在上面的saving方法中,每个对setAttribute的调用都应该向下级联触发所有其他相关的mutator。

逻辑可能需要一点调整,如果我注意到任何更新无序,但到目前为止,它做的工作,是可伸缩的,如果我需要添加更多的字段与mutators

也许你可以扩展Laravel的Model类并覆盖(和使用)fill方法。

如果你看一下代码,你会看到fill调用fillableFromArray。我可能会重写这些方法,以按照我需要更新的顺序返回字段。

所以我猜你最后会得到$model->fill(array(fields...))->save()

希望这对你有帮助。(供您参考,我使用Laravel 4.1)

所有属于模型的东西,应该只负责数据存储。计算应该被封装到它们自己的领域对象中。这样你就不会把存储逻辑和计算混合在一起,这样它们就可以相互独立——这对于维护和单元测试来说是非常好的。

也就是说,处理这种情况的更好的方法是创建一个自定义facade,并在那里封装所有的计算。

按照这种方式,你不需要破解Laravel的核心来实现它并创建紧密耦合。

然而,这种方法有一个小缺点——你不能利用自动表单模型绑定,所以你需要手动提供值。