diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index 14d17f9925db..c579db7b9241 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -61,6 +61,13 @@ trait HasAttributes */ protected $original = []; + /** + * The model attribute's previous state. + * + * @var array + */ + protected $previous = []; + /** * The changed model attributes. * @@ -1959,6 +1966,32 @@ public function getRawOriginal($key = null, $default = null) return Arr::get($this->original, $key, $default); } + /** + * Get the model's previous attribute values. + * + * @param string|null $key + * @param mixed $default + * @return mixed|array + */ + public function getPrevious($key = null, $default = null) + { + return (new static)->setRawAttributes( + $this->previous, $sync = true + )->getOriginalWithoutRewindingModel($key, $default); + } + + /** + * Get the model's raw previous attribute values. + * + * @param string|null $key + * @param mixed $default + * @return mixed|array + */ + public function getRawPrevious($key = null, $default = null) + { + return Arr::get($this->previous, $key, $default); + } + /** * Get a subset of the model's attributes. * @@ -2018,6 +2051,18 @@ public function syncOriginalAttributes($attributes) return $this; } + /** + * Sync the previous attributes with the original. + * + * @return $this + */ + public function syncPrevious() + { + $this->previous = $this->original; + + return $this; + } + /** * Sync the changed attributes. * diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index 7504f04fb8a0..6c19057c6496 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -989,6 +989,8 @@ protected function incrementOrDecrement($column, $amount, $extra, $method) $amount = (clone $this)->setAttribute($column, $amount)->getAttributeFromArray($column); } + $this->syncPrevious(); + return tap($this->setKeysForSaveQuery($this->newQueryWithoutScopes())->{$method}($column, $amount, $extra), function () use ($column) { $this->syncChanges(); @@ -1236,6 +1238,8 @@ protected function performUpdate(Builder $query) $dirty = $this->getDirtyForUpdate(); if (count($dirty) > 0) { + $this->syncPrevious(); + $this->setKeysForSaveQuery($query)->update($dirty); $this->syncChanges(); diff --git a/tests/Integration/Database/EloquentDeleteTest.php b/tests/Integration/Database/EloquentDeleteTest.php index bd0880767a52..81420cb3de36 100644 --- a/tests/Integration/Database/EloquentDeleteTest.php +++ b/tests/Integration/Database/EloquentDeleteTest.php @@ -33,6 +33,15 @@ protected function afterRefreshingDatabase() }); } + public function testBasicDelete() + { + $post = Post::create(); + + Post::where('id', $post->id)->delete(); + + $this->assertCount(0, Post::all()); + } + public function testDeleteWithLimit() { if ($this->driver === 'sqlsrv') { diff --git a/tests/Integration/Database/EloquentModelRefreshTest.php b/tests/Integration/Database/EloquentModelRefreshTest.php index 4bac91dbfe32..304c04f10883 100644 --- a/tests/Integration/Database/EloquentModelRefreshTest.php +++ b/tests/Integration/Database/EloquentModelRefreshTest.php @@ -54,6 +54,18 @@ public function testItSyncsOriginalOnRefresh() $this->assertSame('patrick', $post->getOriginal('title')); } + public function testItDoesNotSyncPreviousOnRefresh() + { + $post = Post::create(['title' => 'pat']); + + Post::find($post->id)->update(['title' => 'patrick']); + + $post->refresh(); + + $this->assertEmpty($post->getDirty()); + $this->assertEmpty($post->getPrevious()); + } + public function testAsPivot() { Schema::create('post_posts', function (Blueprint $table) { diff --git a/tests/Integration/Database/EloquentModelTest.php b/tests/Integration/Database/EloquentModelTest.php index d4ad6c207437..70dd28dcb0e6 100644 --- a/tests/Integration/Database/EloquentModelTest.php +++ b/tests/Integration/Database/EloquentModelTest.php @@ -84,12 +84,14 @@ public function testDiscardChanges() $this->assertFalse($user->wasChanged()); $this->assertSame($originalName, $user->getOriginal('name')); $this->assertSame($overrideName, $user->getAttribute('name')); + $this->assertEmpty($user->getPrevious()); $user->discardChanges(); $this->assertEmpty($user->getDirty()); $this->assertSame($originalName, $user->getOriginal('name')); $this->assertSame($originalName, $user->getAttribute('name')); + $this->assertEmpty($user->getPrevious()); $user->save(); $this->assertFalse($user->wasChanged()); diff --git a/tests/Integration/Database/EloquentUpdateTest.php b/tests/Integration/Database/EloquentUpdateTest.php index 68fdc26993a2..dbf1bccc78bb 100644 --- a/tests/Integration/Database/EloquentUpdateTest.php +++ b/tests/Integration/Database/EloquentUpdateTest.php @@ -41,9 +41,9 @@ public function testBasicUpdate() 'title' => 'Ms.', ]); - TestUpdateModel1::where('title', 'Ms.')->delete(); + TestUpdateModel1::where('title', 'Ms.')->update(['title' => 'Dr.']); - $this->assertCount(0, TestUpdateModel1::all()); + $this->assertSame('Dr.', TestUpdateModel1::first()->title); } public function testUpdateWithLimitsAndOrders() @@ -136,6 +136,47 @@ public function testIncrementOrDecrementIgnoresGlobalScopes() $deletedModel->decrement('counter'); $this->assertEquals(0, $deletedModel->fresh()->counter); } + + public function testUpdateSyncsPrevious() + { + $model = TestUpdateModel1::create([ + 'name' => Str::random(), + 'title' => 'Ms.', + ]); + + $model->update(['title' => 'Dr.']); + + $this->assertSame('Dr.', $model->title); + $this->assertSame('Dr.', $model->getOriginal('title')); + $this->assertSame('Ms.', $model->getPrevious('title')); + } + + public function testSaveSyncsPrevious() + { + $model = TestUpdateModel1::create([ + 'name' => Str::random(), + 'title' => 'Ms.', + ]); + + $model->title = 'Dr.'; + $model->save(); + + $this->assertSame('Dr.', $model->title); + $this->assertSame('Dr.', $model->getOriginal('title')); + $this->assertSame('Ms.', $model->getPrevious('title')); + } + + public function testIncrementSyncsPrevious() + { + $model = TestUpdateModel3::create([ + 'counter' => 0, + ]); + + $model->increment('counter'); + + $this->assertEquals(1, $model->counter); + $this->assertEquals(0, $model->getPrevious('counter')); + } } class TestUpdateModel1 extends Model