DEV Community

Cover image for Tip for Using incrementEach in Laravel
Ivan Mykhavko
Ivan Mykhavko

Posted on

Tip for Using incrementEach in Laravel

Laravel provides a convenient way to increment or decrement column values in a model using the increment and decrement methods.

Starting from Laravel 9, an additional method, incrementEach (and decrementEach), allows updating multiple columns at once.

The Problem: Incrementing and Decrementing Simultaneously

What if you need to increment and decrement different columns at the same time?

Consider the following scenario with a PriceImport model that has the columns imported_rows and unknown_rows. You might try something like this:

PriceImport::query()->increment('unknown_rows')->decrement('imported_rows');
Enter fullscreen mode Exit fullscreen mode

But, this approach won't work since increment and decrement methods can't be chained.

Common Workaround (But Not Ideal)

Typical way to handle this is by using DB::raw():

PriceImport::query()->whereKey($priceImport->id)->update([
    'imported_rows' => DB::raw('imported_rows + 1'),
    'unknown_rows' => DB::raw('unknown_rows - 1'),
]);
Enter fullscreen mode Exit fullscreen mode

Or little better:

PriceImport::query()->whereKey($priceImport->id)->increment('imported_rows', [
    'unknown_rows' => DB::raw('unknown_rows - 1'),
]);
Enter fullscreen mode Exit fullscreen mode

While this works, it's not the most elegant or laravel-friendly solution.

The Elegant Solution: Using Negative Values with incrementEach

Laravel’s incrementEach method allows passing negative values, which effectively works as a decrement operation.

Here’s how you can achieve both incrementing and decrementing at the same time in a clean way:

Full Example

<?php

namespace App\Actions\PriceImport;

use App\Actions\Actionable;
use App\Models\PriceImport\PriceImport;
use App\Models\PriceImport\PriceImportProduct;
use App\QueryBuilder\Queries\PriceImport\MovePriceImportDuplicateRowQuery;

final class PriceImportHandleDuplicateAction implements Actionable
{
    public function handle(PriceImport $priceImport): void
    {
        $duplicateCount = PriceImportProduct::query()
            ->where('row_occurrences', '>', 1)
            ->where('price_import_id', $priceImport->id)
            ->count();

        PriceImport::query()->whereKey($priceImport->id)->incrementEach([
            'imported_rows' => -$duplicateCount, // Decrementing
            'unknown_rows' => $duplicateCount,   // Incrementing
        ]);

        (new MovePriceImportDuplicateRowQuery($priceImport->id))->query();
    }
}
Enter fullscreen mode Exit fullscreen mode

Resulting Query

UPDATE `price_imports`
SET `imported_rows` = `imported_rows` + -1,
    `unknown_rows` = `unknown_rows` + 1
WHERE `price_imports`.`id` = 5;
Enter fullscreen mode Exit fullscreen mode

Conclusion

With this simple trick, you can increment and decrement multiple columns simultaneously using Laravel’s incrementEach method, making your code cleaner and more readable.

Top comments (1)

Collapse
 
xwero profile image
david duymelinck • Edited

It doesn't make the code more readable, because at first sight the code increments the columns. Even with the comment, the minus symbol was hard to spot.

I don't understand what is wrong with the update method.
If using DB::raw is not your thing you can write a trait.

trait DbRaw {
    protected function increment(string $column, int $amount = 1)
   {
        return DB::Raw("$column + $amount");
   }

   protected function decrement(string $column, int $amount = 1)
   {
        return DB::Raw("$column - $amount");
   }
}

// repository.php
PriceImport::query()->whereKey($priceImport->id)->update([
    'imported_rows' => $this->increment('imported_rows'),
    'unknown_rows' => $this->decrement('unknown_rows'),
]);
Enter fullscreen mode Exit fullscreen mode

You could even create collection macros that fills a mass assignment array.

Collection::macro('increment', function ($column, $amount = 1) {
    return $this->put($column, DB::raw("$column + $amount"));
});

Collection::macro('decrement', function ($column, $amount = 1) {
    return $this->put($column, DB::raw("$column - $amount"));
});

// repository
PriceImport::query()->whereKey($priceImport->id)->update(
  collect()->increment('imported_rows')->decrement('unknown_rows')->all()
);
Enter fullscreen mode Exit fullscreen mode