Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Updating Laravel Eloquent mutator values on save in the correct order

Suppose I have five fields in a table: price, cost, handling, shipping, profit

The fields shipping and profit have mutators because they are based off calculations.

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'];
}

My problem here is two-fold, and I'm trying to figure out the most elegant solution for them:

  1. The shipping attribute always has to have it's mutator called first during a save so the profit attribute will be correct.

  2. Each time there is a change on the price, cost or handling attributes, I need the profit and/or shipping attributes to update as well (again, in proper order).

How can I effectively address these two issues with my mutators?

NOTE: The is a simplified version of the calculations I have on this particular model. In reality, there are these two examples out of 20 others that have mutators, so I need whatever I do to be scalable against many interdependent calculations.

like image 922
eComEvo Avatar asked Nov 24 '25 02:11

eComEvo


1 Answers

To solve this and keep everything as self maintaining as possible, I had to create a somewhat complex solution on this model. Below is the simplified version based on the original post for anyone interested, but it can be easily expanded.

For each of the mutators, I created accessors to make sure calculations are always done for each new request for the data:

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

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

The accessors use the getAttribute method instead of direct attribute access to ensure that any other mutators or relations behind the scenes get called automatically.

The mutators are then used to ensure the data is cached in the database for any future queries against them (this, as opposed to just appending the data):

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

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

To make sure all dependent fields are updated when there is a change, I added this to the model:

protected static $mutatorDependants = [
    'handling'        => [
        'shipping'
    ],
    'price'              => [
        'profit'
    ],
    'cost'              => [
        'profit'
    ],
    'shipping'              => [
        'profit'
    ]
];

protected $mutatorCalled = []; // Added to prevent unnecessary mutator calls.

If any of the above fields are changed, then their dependents are automatically recalculated on the saving event:

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 = [];
    });
}

The way it is structured, every call to setAttribute in thesaving method above should cascade down triggering every other related mutator.

Logic may need a little tweaking if I notice anything updating out of order, but so far it does the job and is scalable if I need to add more fields with mutators.

like image 60
eComEvo Avatar answered Nov 26 '25 15:11

eComEvo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!