diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index 5a4f4e117aa6..4266aeaa7420 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -1182,6 +1182,22 @@ public function increment($column, $amount = 1, array $extra = []) ); } + /** + * Increment the given column's values by the given amounts. + * + * @param array $columns + * @param array $extra + * @return int + * + * @throws \InvalidArgumentException + */ + public function incrementEach($columns, array $extra = []) + { + return $this->toBase()->incrementEach( + $columns, $this->addUpdatedAtColumn($extra) + ); + } + /** * Decrement a column's value by a given amount. * @@ -1197,6 +1213,22 @@ public function decrement($column, $amount = 1, array $extra = []) ); } + /** + * Decrement the given column's values by the given amounts. + * + * @param array $columns + * @param array $extra + * @return int + * + * @throws \InvalidArgumentException + */ + public function decrementEach($columns, array $extra = []) + { + return $this->toBase()->decrementEach( + $columns, $this->addUpdatedAtColumn($extra) + ); + } + /** * Add the "updated at" column to an array of values. * diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index 1176dcd776d6..44a7113cafe2 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -945,6 +945,20 @@ protected function increment($column, $amount = 1, array $extra = []) return $this->incrementOrDecrement($column, $amount, $extra, 'increment'); } + /** + * Increment the given column's values by the given amounts. + * + * @param array $columns + * @param array $extra + * @return int + * + * @throws \InvalidArgumentException + */ + protected function incrementEach(array $columns, array $extra = []) + { + return $this->incrementOrDecrementEach($columns, $extra, 'incrementEach'); + } + /** * Decrement a column's value by a given amount. * @@ -958,6 +972,20 @@ protected function decrement($column, $amount = 1, array $extra = []) return $this->incrementOrDecrement($column, $amount, $extra, 'decrement'); } + /** + * Decrement the given column's values by the given amounts. + * + * @param array $columns + * @param array $extra + * @return int + * + * @throws \InvalidArgumentException + */ + protected function decrementEach(array $columns, array $extra = []) + { + return $this->incrementOrDecrementEach($columns, $extra, 'decrementEach'); + } + /** * Run the increment or decrement method on the model. * @@ -996,6 +1024,47 @@ protected function incrementOrDecrement($column, $amount, $extra, $method) }); } + /** + * Run the incrementEach or decrementEach method on the model. + * + * @param array $columns + * @param array $extra + * @param string $method + * @return int + */ + protected function incrementOrDecrementEach($columns, $extra, $method) + { + if (! $this->exists) { + return $this->newQueryWithoutRelationships()->{$method}($columns, $extra, $method); + } + + foreach ($columns as $column => $amount) { + $this->{$column} = $this->isClassDeviable($column) + ? $this->deviateClassCastableAttribute($method, $column, $amount) + : $this->{$column} + ($method === 'incrementEach' ? $amount : $amount * -1); + } + + $this->forceFill($extra); + + if ($this->fireModelEvent('updating') === false) { + return false; + } + + foreach ($columns as $column => $amount) { + if ($this->isClassDeviable($column)) { + $columns[$column] = (clone $this)->setAttribute($column, $amount)->getAttributeFromArray($column); + } + } + + return tap($this->setKeysForSaveQuery($this->newQueryWithoutScopes())->{$method}($columns, $extra, $method), function () use ($columns) { + $this->syncChanges(); + + $this->fireModelEvent('updated', false); + + $this->syncOriginalAttribute(array_keys($columns)); + }); + } + /** * Update the model in the database. * @@ -2330,7 +2399,7 @@ public function __unset($key) */ public function __call($method, $parameters) { - if (in_array($method, ['increment', 'decrement', 'incrementQuietly', 'decrementQuietly'])) { + if (in_array($method, ['increment', 'decrement', 'incrementQuietly', 'decrementQuietly', 'incrementEach', 'decrementEach'])) { return $this->$method(...$parameters); } diff --git a/tests/Integration/Database/EloquentUpdateTest.php b/tests/Integration/Database/EloquentUpdateTest.php index 68fdc26993a2..7049a41d4017 100644 --- a/tests/Integration/Database/EloquentUpdateTest.php +++ b/tests/Integration/Database/EloquentUpdateTest.php @@ -28,7 +28,8 @@ protected function afterRefreshingDatabase() Schema::create('test_model3', function (Blueprint $table) { $table->increments('id'); - $table->unsignedInteger('counter'); + $table->integer('wallet_1'); + $table->integer('wallet_2'); $table->softDeletes(); $table->timestamps(); }); @@ -105,36 +106,68 @@ public function testSoftDeleteWithJoins() public function testIncrement() { TestUpdateModel3::create([ - 'counter' => 0, + 'wallet_1' => 0, + 'wallet_2' => 0, ]); TestUpdateModel3::create([ - 'counter' => 0, + 'wallet_1' => 0, + 'wallet_2' => 0, ])->delete(); - TestUpdateModel3::increment('counter'); + TestUpdateModel3::increment('wallet_1'); + TestUpdateModel3::incrementEach([ + 'wallet_1' => 10, + 'wallet_2' => -20, + ]); + + $models = TestUpdateModel3::withoutGlobalScopes()->orderBy('id')->get(); + $this->assertEquals(1 + 10, $models[0]->wallet_1); + $this->assertEquals(-20, $models[0]->wallet_2); + $this->assertEquals(0, $models[1]->wallet_1); + $this->assertEquals(0, $models[1]->wallet_2); + + $record = TestUpdateModel3::create([ + 'wallet_1' => 50, + 'wallet_2' => 70, + ]); + $record->incrementEach([ + 'wallet_1' => 20, + 'wallet_2' => -40, + ]); $models = TestUpdateModel3::withoutGlobalScopes()->orderBy('id')->get(); - $this->assertEquals(1, $models[0]->counter); - $this->assertEquals(0, $models[1]->counter); + $this->assertEquals(1 + 10, $models[0]->wallet_1); + $this->assertEquals(-20, $models[0]->wallet_2); + $this->assertEquals(0, $models[1]->wallet_1); + $this->assertEquals(0, $models[1]->wallet_2); + $this->assertEquals(50 + 20, $models[2]->wallet_1); + $this->assertEquals(70 - 40, $models[2]->wallet_2); } public function testIncrementOrDecrementIgnoresGlobalScopes() { /** @var TestUpdateModel3 $deletedModel */ $deletedModel = tap(TestUpdateModel3::create([ - 'counter' => 0, + 'wallet_1' => 0, + 'wallet_2' => 0, ]), fn ($model) => $model->delete()); - $deletedModel->increment('counter'); + $deletedModel->increment('wallet_1'); + $deletedModel->incrementEach(['wallet_1' => 1, 'wallet_2' => 1]); - $this->assertEquals(1, $deletedModel->counter); + $this->assertEquals(1 + 1, $deletedModel->wallet_1); + $this->assertEquals(1, $deletedModel->wallet_2); $deletedModel->fresh(); - $this->assertEquals(1, $deletedModel->counter); + $this->assertEquals(1 + 1, $deletedModel->wallet_1); + $this->assertEquals(1, $deletedModel->wallet_2); + + $deletedModel->decrement('wallet_1'); + $deletedModel->decrementEach(['wallet_1' => 1, 'wallet_2' => 1]); - $deletedModel->decrement('counter'); - $this->assertEquals(0, $deletedModel->fresh()->counter); + $this->assertEquals(0, $deletedModel->fresh()->wallet_1); + $this->assertEquals(0, $deletedModel->fresh()->wallet_2); } } @@ -158,6 +191,6 @@ class TestUpdateModel3 extends Model use SoftDeletes; public $table = 'test_model3'; - protected $fillable = ['counter']; + protected $fillable = ['wallet_1', 'wallet_2']; protected $casts = ['deleted_at' => 'datetime']; }