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');
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'),
]);
Or little better:
PriceImport::query()->whereKey($priceImport->id)->increment('imported_rows', [
'unknown_rows' => DB::raw('unknown_rows - 1'),
]);
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();
}
}
Resulting Query
UPDATE `price_imports`
SET `imported_rows` = `imported_rows` + -1,
`unknown_rows` = `unknown_rows` + 1
WHERE `price_imports`.`id` = 5;
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)
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.You could even create collection macros that fills a mass assignment array.
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.
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?
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...
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.
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. :)