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 (6)

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
Collapse
 
tegos profile image
Ivan Mykhavko

Thank you for your feedback and for sharing your code examples!
I see your point about readability it might not be immediately clear that the value is being decremented rather than incremented.
I'll consider making that aspect more explicit in future examples.

I just want demonstrate that incrementing can be done this way.
I dont have anything against DB::raw, you're free to use it wherever you prefer or even not use eloquent at all. 😊

I appreciate your suggestion about using a trait or collection macros for handling DB::raw operations.
However, I personally don't use traits or macros, as I don’t consider them the best architectural choices.

Collapse
 
xwero profile image
david duymelinck

I added the collection example because Laravel developers love a fluent API.
I wouldn't use it either if I had the choice.

What is stopping you from using traits?

Thread Thread
 
tegos profile image
Ivan Mykhavko

Simply, I don't like traits. First, the term itself doesn't exist in oop, and using traits can prevent certain oop principles from being fully applied.
Second, their implementation is working like a copy-paste mechanism.
When I see a codebase where everything revolves around traits, it usually indicates poor design.
Here are some thoughts on traits:
barryosull.com/blog/why-i-don-t-li...

Thread Thread
 
xwero profile image
david duymelinck

I never thought much about it because the same mechanism exists in other languages too. Even in Java you can create methods with a body in interfaces. Then I think the PHP implementation is a bit cleaner.

If multiple languages have this mechanism to overcome the single class inheritance, that means that there is enough friction in codebases not to consider the composition alternative.

I share your concerns, but I'm not going out of my way to avoid them.

Thread Thread
 
tegos profile image
Ivan Mykhavko

I like your point of view. But in Java, this was added recently, in 2014, and I'm not sure if default methods are the same as PHP traits. It's interesting to chat with you. :)