From 4e97b27de0698b53f5b0f09f9a8c583166029e59 Mon Sep 17 00:00:00 2001 From: GoatFreezy Date: Mon, 3 Mar 2025 22:39:36 +0100 Subject: [PATCH 1/4] Feat: Ability to disable auditable --- src/AuditableTrait.php | 3 +++ src/AuditableTraitObserver.php | 8 +++---- tests/Feature/AuditableModelTest.php | 33 ++++++++++++---------------- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/AuditableTrait.php b/src/AuditableTrait.php index 49cf3aa..7df0334 100644 --- a/src/AuditableTrait.php +++ b/src/AuditableTrait.php @@ -12,6 +12,9 @@ */ trait AuditableTrait { + + public $auditable = true; + /** * Boot the audit trait for a model. */ diff --git a/src/AuditableTraitObserver.php b/src/AuditableTraitObserver.php index 08613b8..ff637c8 100644 --- a/src/AuditableTraitObserver.php +++ b/src/AuditableTraitObserver.php @@ -11,7 +11,7 @@ class AuditableTraitObserver */ public function creating(Model $model): void { - if (method_exists($model, 'getCreatedByColumn')) { + if (method_exists($model, 'getCreatedByColumn') && $model->auditable) { $createdBy = $model->getCreatedByColumn(); if (! $model->$createdBy) { @@ -19,7 +19,7 @@ public function creating(Model $model): void } } - if (method_exists($model, 'getUpdatedByColumn')) { + if (method_exists($model, 'getUpdatedByColumn') && $model->auditable) { $updatedBy = $model->getUpdatedByColumn(); if (! $model->$updatedBy) { @@ -41,7 +41,7 @@ protected function getAuthenticatedUserId(): int|string|null */ public function updating(Model $model): void { - if (method_exists($model, 'getUpdatedByColumn')) { + if (method_exists($model, 'getUpdatedByColumn') && $model->auditable) { $updatedBy = $model->getUpdatedByColumn(); if (! $model->isDirty($updatedBy)) { @@ -55,7 +55,7 @@ public function updating(Model $model): void */ public function saved(Model $model): void { - if (method_exists($model, 'getUpdatedByColumn')) { + if (method_exists($model, 'getUpdatedByColumn') && $model->auditable) { $updatedBy = $model->getUpdatedByColumn(); if ($this->getAuthenticatedUserId() && $this->getAuthenticatedUserId() != $model->$updatedBy && $model->isDirty()) { diff --git a/tests/Feature/AuditableModelTest.php b/tests/Feature/AuditableModelTest.php index 89f5c3d..619ce5c 100644 --- a/tests/Feature/AuditableModelTest.php +++ b/tests/Feature/AuditableModelTest.php @@ -48,32 +48,27 @@ expect($post->deleted_by)->toBe($user->id); }); -test('a model wont be updated if edited by another user without it being dirty', function () { - $user = User::create([ +test('a post can be created without audit', function () { + $user = User::forceCreate([ 'name' => 'John Doe', 'email' => 'john@example.com', ]); actingAs($user); - $anotherUser = User::create([ - 'name' => 'Jane Doe', - 'email' => 'jane@example.com', - ]); + $post = new Post(); + $post->title = 'Hello World'; + $post->auditable = false; + $post->save(); - DB::table('posts')->insert([ - 'title' => 'Hello World', - 'created_by' => $anotherUser->id, - 'updated_by' => $anotherUser->id, - ]); - - $model = Post::first(); - - expect($model->created_by)->toBe($anotherUser->id); - expect($model->updated_by)->toBe($anotherUser->id); + expect($post->created_by)->toBe(null); + expect($post->updated_by)->toBe(null); + expect($post->deleted_by)->toBe(null); - $model->save(); + $post->auditable = true; + $post->title = 'Hello World 2'; + $post->save(); - expect($model->created_by)->toBe($anotherUser->id); - expect($model->updated_by)->toBe($anotherUser->id); + expect($post->updated_by)->toBe($user->id); + expect($post->deleted_by)->toBe(null); }); From 11545d31325cdba7de07dc26b932ac5823c5d313 Mon Sep 17 00:00:00 2001 From: GoatFreezy Date: Mon, 3 Mar 2025 23:31:49 +0100 Subject: [PATCH 2/4] Type & Doc $auditable + Use pint to format --- src/AuditableTrait.php | 4 ++-- tests/Feature/AuditableModelTest.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/AuditableTrait.php b/src/AuditableTrait.php index 7df0334..ac3cc0f 100644 --- a/src/AuditableTrait.php +++ b/src/AuditableTrait.php @@ -9,12 +9,12 @@ /** * @property Model $creator * @property Model $updater + * @property bool $auditable */ trait AuditableTrait { + public bool $auditable = true; - public $auditable = true; - /** * Boot the audit trait for a model. */ diff --git a/tests/Feature/AuditableModelTest.php b/tests/Feature/AuditableModelTest.php index 619ce5c..e511b96 100644 --- a/tests/Feature/AuditableModelTest.php +++ b/tests/Feature/AuditableModelTest.php @@ -56,7 +56,7 @@ actingAs($user); - $post = new Post(); + $post = new Post; $post->title = 'Hello World'; $post->auditable = false; $post->save(); From 9a6c92ae9fba011747aeaa8cac9f1c8a7f8d170f Mon Sep 17 00:00:00 2001 From: GoatFreezy Date: Mon, 24 Mar 2025 23:39:56 +0100 Subject: [PATCH 3/4] Implement withoutAudits to match laravel behavior --- src/AuditableTrait.php | 27 +++++++++++++++++++++-- src/AuditableTraitObserver.php | 8 +++---- src/AuditableWithDeletesTraitObserver.php | 4 ++-- tests/Feature/AuditableModelTest.php | 17 +++++++------- 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/AuditableTrait.php b/src/AuditableTrait.php index ac3cc0f..5789347 100644 --- a/src/AuditableTrait.php +++ b/src/AuditableTrait.php @@ -9,11 +9,11 @@ /** * @property Model $creator * @property Model $updater - * @property bool $auditable + * @property bool $auditing */ trait AuditableTrait { - public bool $auditable = true; + protected static bool $auditing = true; /** * Boot the audit trait for a model. @@ -23,6 +23,29 @@ public static function bootAuditableTrait(): void static::observe(new AuditableTraitObserver); } + /** + * Disable auditing. + */ + public static function withoutAudits(callable $callback) + { + $previousState = static::$auditing; + static::$auditing = false; + + try { + return $callback(); + } finally { + static::$auditing = $previousState; + } + } + + /** + * Check is auditing is enabled. + */ + public function isAuditable(): bool + { + return static::$auditing; + } + /** * Get user model who created the record. */ diff --git a/src/AuditableTraitObserver.php b/src/AuditableTraitObserver.php index ff637c8..26b670a 100644 --- a/src/AuditableTraitObserver.php +++ b/src/AuditableTraitObserver.php @@ -11,7 +11,7 @@ class AuditableTraitObserver */ public function creating(Model $model): void { - if (method_exists($model, 'getCreatedByColumn') && $model->auditable) { + if (method_exists($model, 'getCreatedByColumn') && $model->isAuditable()) { $createdBy = $model->getCreatedByColumn(); if (! $model->$createdBy) { @@ -19,7 +19,7 @@ public function creating(Model $model): void } } - if (method_exists($model, 'getUpdatedByColumn') && $model->auditable) { + if (method_exists($model, 'getUpdatedByColumn') && $model->isAuditable()) { $updatedBy = $model->getUpdatedByColumn(); if (! $model->$updatedBy) { @@ -41,7 +41,7 @@ protected function getAuthenticatedUserId(): int|string|null */ public function updating(Model $model): void { - if (method_exists($model, 'getUpdatedByColumn') && $model->auditable) { + if (method_exists($model, 'getUpdatedByColumn') && $model->isAuditable()) { $updatedBy = $model->getUpdatedByColumn(); if (! $model->isDirty($updatedBy)) { @@ -55,7 +55,7 @@ public function updating(Model $model): void */ public function saved(Model $model): void { - if (method_exists($model, 'getUpdatedByColumn') && $model->auditable) { + if (method_exists($model, 'getUpdatedByColumn') && $model->isAuditable()) { $updatedBy = $model->getUpdatedByColumn(); if ($this->getAuthenticatedUserId() && $this->getAuthenticatedUserId() != $model->$updatedBy && $model->isDirty()) { diff --git a/src/AuditableWithDeletesTraitObserver.php b/src/AuditableWithDeletesTraitObserver.php index 7598a9e..a0bb916 100644 --- a/src/AuditableWithDeletesTraitObserver.php +++ b/src/AuditableWithDeletesTraitObserver.php @@ -11,7 +11,7 @@ class AuditableWithDeletesTraitObserver */ public function deleting(Model $model): void { - if (method_exists($model, 'getDeletedByColumn')) { + if (method_exists($model, 'getDeletedByColumn') && $model->isAuditable()) { $deletedBy = $model->getDeletedByColumn(); $model->$deletedBy = $this->getAuthenticatedUserId(); @@ -32,7 +32,7 @@ protected function getAuthenticatedUserId(): int|string|null */ public function restoring(Model $model): void { - if (method_exists($model, 'getDeletedByColumn')) { + if (method_exists($model, 'getDeletedByColumn') && $model->isAuditable()) { $deletedBy = $model->getDeletedByColumn(); $model->$deletedBy = null; diff --git a/tests/Feature/AuditableModelTest.php b/tests/Feature/AuditableModelTest.php index e511b96..25964cb 100644 --- a/tests/Feature/AuditableModelTest.php +++ b/tests/Feature/AuditableModelTest.php @@ -56,16 +56,17 @@ actingAs($user); - $post = new Post; - $post->title = 'Hello World'; - $post->auditable = false; - $post->save(); + Post::withoutAudits(function () { + $post = new Post; + $post->title = 'Hello World'; + $post->save(); - expect($post->created_by)->toBe(null); - expect($post->updated_by)->toBe(null); - expect($post->deleted_by)->toBe(null); + expect($post->created_by)->toBe(null); + expect($post->updated_by)->toBe(null); + expect($post->deleted_by)->toBe(null); + }); - $post->auditable = true; + $post = Post::first(); $post->title = 'Hello World 2'; $post->save(); From 5a2b70b639bc9dfe18e372e0599f21cd288e1301 Mon Sep 17 00:00:00 2001 From: GoatFreezy Date: Mon, 24 Mar 2025 23:45:59 +0100 Subject: [PATCH 4/4] Fix rebase removed test --- tests/Feature/AuditableModelTest.php | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/Feature/AuditableModelTest.php b/tests/Feature/AuditableModelTest.php index 25964cb..acb7c2e 100644 --- a/tests/Feature/AuditableModelTest.php +++ b/tests/Feature/AuditableModelTest.php @@ -48,6 +48,36 @@ expect($post->deleted_by)->toBe($user->id); }); +test('a model wont be updated if edited by another user without it being dirty', function () { + $user = User::create([ + 'name' => 'John Doe', + 'email' => 'john@example.com', + ]); + + actingAs($user); + + $anotherUser = User::create([ + 'name' => 'Jane Doe', + 'email' => 'jane@example.com', + ]); + + DB::table('posts')->insert([ + 'title' => 'Hello World', + 'created_by' => $anotherUser->id, + 'updated_by' => $anotherUser->id, + ]); + + $model = Post::first(); + + expect($model->created_by)->toBe($anotherUser->id); + expect($model->updated_by)->toBe($anotherUser->id); + + $model->save(); + + expect($model->created_by)->toBe($anotherUser->id); + expect($model->updated_by)->toBe($anotherUser->id); +}); + test('a post can be created without audit', function () { $user = User::forceCreate([ 'name' => 'John Doe',