From f2d376fd3a4efdd785fb8f30a54a273a409bfab2 Mon Sep 17 00:00:00 2001 From: wilianx7 Date: Tue, 6 Feb 2024 17:00:29 -0300 Subject: [PATCH 1/5] feat (Auditable): customized relationship for Attach, Detach and Sync --- src/Auditable.php | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/Auditable.php b/src/Auditable.php index e86ab748..a5cbfebc 100644 --- a/src/Auditable.php +++ b/src/Auditable.php @@ -2,6 +2,7 @@ namespace OwenIt\Auditing; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Arr; @@ -365,11 +366,11 @@ public function transformAudit(array $data): array * */ protected function resolveUser() - { + { if (! empty($this->preloadedResolverData['user'] ?? null)) { return $this->preloadedResolverData['user']; } - + $userResolver = Config::get('audit.user.resolver'); if (is_null($userResolver) && Config::has('audit.resolver') && !Config::has('audit.user.resolver')) { @@ -508,11 +509,11 @@ public function getAuditEvent() public function getAuditEvents(): array { return $this->auditEvents ?? Config::get('audit.events', [ - 'created', - 'updated', - 'deleted', - 'restored', - ]); + 'created', + 'updated', + 'deleted', + 'restored', + ]); } /** @@ -668,18 +669,20 @@ public function transitionTo(Contracts\Audit $audit, bool $old = false): Contrac * @param array $attributes * @param bool $touch * @param array $columns + * @param BelongsToMany|null $relationship * @return void * @throws AuditingException */ - public function auditAttach(string $relationName, $id, array $attributes = [], $touch = true, $columns = ['*']) + public function auditAttach(string $relationName, $id, array $attributes = [], $touch = true, $columns = ['*'], $relationship = null) { if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'attach')) { throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method attach'); } - $old = $this->{$relationName}()->get($columns); - $this->{$relationName}()->attach($id, $attributes, $touch); - $new = $this->{$relationName}()->get($columns); + $relationCall = $relationship ?? $this->{$relationName}(); + $old = $relationCall->get($columns); + $relationCall->attach($id, $attributes, $touch); + $new = $relationCall->get($columns); $this->dispatchRelationAuditEvent($relationName, 'attach', $old, $new); } @@ -688,18 +691,20 @@ public function auditAttach(string $relationName, $id, array $attributes = [], $ * @param mixed $ids * @param bool $touch * @param array $columns + * @param BelongsToMany|null $relationship * @return int * @throws AuditingException */ - public function auditDetach(string $relationName, $ids = null, $touch = true, $columns = ['*']) + public function auditDetach(string $relationName, $ids = null, $touch = true, $columns = ['*'], $relationship = null) { if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'detach')) { throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method detach'); } - $old = $this->{$relationName}()->get($columns); - $results = $this->{$relationName}()->detach($ids, $touch); - $new = $this->{$relationName}()->get($columns); + $relationCall = $relationship ?? $this->{$relationName}(); + $old = $relationCall->get($columns); + $results = $relationCall->detach($ids, $touch); + $new = $relationCall->get($columns); $this->dispatchRelationAuditEvent($relationName, 'detach', $old, $new); return empty($results) ? 0 : $results; @@ -710,21 +715,23 @@ public function auditDetach(string $relationName, $ids = null, $touch = true, $c * @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids * @param bool $detaching * @param array $columns + * @param BelongsToMany|null $relationship * @return array * @throws AuditingException */ - public function auditSync($relationName, $ids, $detaching = true, $columns = ['*']) + public function auditSync($relationName, $ids, $detaching = true, $columns = ['*'], $relationship = null) { if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'sync')) { throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method sync'); } - $old = $this->{$relationName}()->get($columns); - $changes = $this->{$relationName}()->sync($ids, $detaching); + $relationCall = $relationship ?? $this->{$relationName}(); + $old = $relationCall->get($columns); + $changes = $relationCall->sync($ids, $detaching); if (collect($changes)->flatten()->isEmpty()) { $old = $new = collect([]); } else { - $new = $this->{$relationName}()->get($columns); + $new = $relationCall->get($columns); } $this->dispatchRelationAuditEvent($relationName, 'sync', $old, $new); From f8d1b84c40370dc76ce1688bc33333ab3e1ab8ad Mon Sep 17 00:00:00 2001 From: wilianx7 Date: Wed, 14 Feb 2024 18:26:30 -0300 Subject: [PATCH 2/5] feat (Auditable): closure implementation for Attach, Detach and Sync --- src/Auditable.php | 85 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 62 insertions(+), 23 deletions(-) diff --git a/src/Auditable.php b/src/Auditable.php index a5cbfebc..3c38779a 100644 --- a/src/Auditable.php +++ b/src/Auditable.php @@ -664,94 +664,107 @@ public function transitionTo(Contracts\Audit $audit, bool $old = false): Contrac */ /** - * @param string $relationName + * @param string|\Closure $relation * @param mixed $id * @param array $attributes * @param bool $touch * @param array $columns - * @param BelongsToMany|null $relationship * @return void * @throws AuditingException */ - public function auditAttach(string $relationName, $id, array $attributes = [], $touch = true, $columns = ['*'], $relationship = null) + public function auditAttach($relation, $id, array $attributes = [], $touch = true, $columns = ['*']) { - if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'attach')) { - throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method attach'); + if (is_string($relation)) { + $this->validateRelationshipMethodExistence($relation, 'attach'); + } else { + $this->validateBelongsToManyRelationshipClosure($relation); } - $relationCall = $relationship ?? $this->{$relationName}(); + $relationCall = is_string($relation) ? $this->{$relation}() : $relation(); $old = $relationCall->get($columns); $relationCall->attach($id, $attributes, $touch); $new = $relationCall->get($columns); + + $relationName = is_string($relation) ? $relation : $relationCall->getRelationName(); + $this->dispatchRelationAuditEvent($relationName, 'attach', $old, $new); } /** - * @param string $relationName + * @param string|\Closure $relation * @param mixed $ids * @param bool $touch * @param array $columns - * @param BelongsToMany|null $relationship * @return int * @throws AuditingException */ - public function auditDetach(string $relationName, $ids = null, $touch = true, $columns = ['*'], $relationship = null) + public function auditDetach($relation, $ids = null, $touch = true, $columns = ['*']) { - if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'detach')) { - throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method detach'); + if (is_string($relation)) { + $this->validateRelationshipMethodExistence($relation, 'detach'); + } else { + $this->validateBelongsToManyRelationshipClosure($relation); } - $relationCall = $relationship ?? $this->{$relationName}(); + $relationCall = is_string($relation) ? $this->{$relation}() : $relation(); $old = $relationCall->get($columns); $results = $relationCall->detach($ids, $touch); $new = $relationCall->get($columns); + + $relationName = is_string($relation) ? $relation : $relationCall->getRelationName(); + $this->dispatchRelationAuditEvent($relationName, 'detach', $old, $new); return empty($results) ? 0 : $results; } /** - * @param $relationName + * @param string|\Closure $relation * @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids * @param bool $detaching * @param array $columns - * @param BelongsToMany|null $relationship * @return array * @throws AuditingException */ - public function auditSync($relationName, $ids, $detaching = true, $columns = ['*'], $relationship = null) + public function auditSync($relation, $ids, $detaching = true, $columns = ['*']) { - if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'sync')) { - throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method sync'); + if (is_string($relation)) { + $this->validateRelationshipMethodExistence($relation, 'sync'); + } else { + $this->validateBelongsToManyRelationshipClosure($relation); } - $relationCall = $relationship ?? $this->{$relationName}(); + $relationCall = is_string($relation) ? $this->{$relation}() : $relation(); $old = $relationCall->get($columns); $changes = $relationCall->sync($ids, $detaching); + if (collect($changes)->flatten()->isEmpty()) { $old = $new = collect([]); } else { $new = $relationCall->get($columns); } + + $relationName = is_string($relation) ? $relation : $relationCall->getRelationName(); + $this->dispatchRelationAuditEvent($relationName, 'sync', $old, $new); return $changes; } /** - * @param string $relationName + * @param string|\Closure $relation * @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids * @param array $columns * @return array * @throws AuditingException */ - public function auditSyncWithoutDetaching(string $relationName, $ids, $columns = ['*']) + public function auditSyncWithoutDetaching($relation, $ids, $columns = ['*']) { - if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'syncWithoutDetaching')) { - throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method syncWithoutDetaching'); + if (is_string($relation)) { + $this->validateRelationshipMethodExistence($relation, 'syncWithoutDetaching'); } - return $this->auditSync($relationName, $ids, false, $columns); + return $this->auditSync($relation, $ids, false, $columns); } /** @@ -778,4 +791,30 @@ private function dispatchRelationAuditEvent($relationName, $event, $old, $new) Event::dispatch(AuditCustom::class, [$this]); $this->isCustomEvent = false; } + + private function validateRelationshipMethodExistence(string $relationName, string $methodName): void + { + if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), $methodName)) { + throw new AuditingException("Relationship $relationName was not found or does not support method $methodName"); + } + } + + private function validateBelongsToManyRelationshipClosure(\Closure $closure): void + { + $hasParams = !!(new \ReflectionFunction($closure))->getNumberOfParameters(); + + if ($hasParams) { + throw new AuditingException("Invalid Closure for BelongsToMany Relationship"); + } + + try { + $closure(); + } catch (\Exception $exception) { + throw new AuditingException("Call to undefined or invalid method for BelongsToMany Relationship"); + } + + if (!($closure() instanceof BelongsToMany)) { + throw new AuditingException("Invalid Closure for BelongsToMany Relationship"); + } + } } From 4e42f097aef9fcaddeef0235c0cc289c8e2a4e19 Mon Sep 17 00:00:00 2001 From: wilianx7 Date: Wed, 14 Feb 2024 18:27:21 -0300 Subject: [PATCH 3/5] test (AuditingTest): closure validations for Attach, Detach and Sync --- tests/Functional/AuditingTest.php | 148 ++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/tests/Functional/AuditingTest.php b/tests/Functional/AuditingTest.php index 3d776964..dc59f202 100644 --- a/tests/Functional/AuditingTest.php +++ b/tests/Functional/AuditingTest.php @@ -601,6 +601,58 @@ public function itWillAuditAttach() $this->assertSame($secondCategory->name, $lastArticleAudit['new'][0]['name']); } + /** + * @test + * @return void + */ + public function itWillAuditAttachByClosure() + { + $firstCategory = factory(Category::class)->create(); + $secondCategory = factory(Category::class)->create(); + $article = factory(Article::class)->create(); + + $closure = function () use ($article) {return $article->categories(); }; + + $article->auditAttach($closure, $firstCategory); + $article->auditAttach($closure, $secondCategory); + $lastArticleAudit = $article->audits->last()->getModified()['categories']; + + $this->assertSame($firstCategory->name, $article->categories->first()->name); + $this->assertSame(0, count($lastArticleAudit['old'])); + $this->assertSame(1, count($lastArticleAudit['new'])); + $this->assertSame($secondCategory->name, $lastArticleAudit['new'][0]['name']); + } + + /** + * @test + * @return void + */ + public function itWillNotAuditAttachByInvalidClosure() + { + $firstCategory = factory(Category::class)->create(); + $article = factory(Article::class)->create(); + + $this->expectExceptionMessage("Invalid Closure for BelongsToMany Relationship"); + + $invalidClosure = function () use ($article) { return $article->title; }; + + $article->auditAttach($invalidClosure, $firstCategory); + } + + /** + * @test + * @return void + */ + public function itWillNotAuditAttachByInvalidRelationName() + { + $firstCategory = factory(Category::class)->create(); + $article = factory(Article::class)->create(); + + $this->expectExceptionMessage("Relationship invalidRelation was not found or does not support method attach"); + + $article->auditAttach('invalidRelation', $firstCategory); + } + /** * @test * @return void @@ -627,6 +679,53 @@ public function itWillAuditSync() $this->assertGreaterThan($no_of_audits_before, $no_of_audits_after); } + /** + * @test + * @return void + */ + public function itWillAuditSyncByClosure() + { + $firstCategory = factory(Category::class)->create(); + $secondCategory = factory(Category::class)->create(); + $article = factory(Article::class)->create(); + + $article->categories()->attach($firstCategory); + + $no_of_audits_before = Audit::where('auditable_type', Article::class)->count(); + $categoryBefore = $article->categories()->first()->getKey(); + + $closure = function () use ($article) { return $article->categories(); }; + + $article->auditSync($closure, [$secondCategory->getKey()]); + + $no_of_audits_after = Audit::where('auditable_type', Article::class)->count(); + $categoryAfter = $article->categories()->first()->getKey(); + + $this->assertSame($firstCategory->getKey(), $categoryBefore); + $this->assertSame($secondCategory->getKey(), $categoryAfter); + $this->assertNotSame($categoryBefore, $categoryAfter); + $this->assertGreaterThan($no_of_audits_before, $no_of_audits_after); + } + + /** + * @test + * @return void + */ + public function itWillNotAuditSyncByInvalidClosure() + { + $firstCategory = factory(Category::class)->create(); + $secondCategory = factory(Category::class)->create(); + $article = factory(Article::class)->create(); + + $article->categories()->attach($firstCategory); + + $this->expectExceptionMessage("Invalid Closure for BelongsToMany Relationship"); + + $invalidClosure = function ($param) use ($article) { return $article->categories(); }; + + $article->auditSync($invalidClosure, [$secondCategory->getKey()]); + } + /** * @test * @return void @@ -654,6 +753,55 @@ public function itWillAuditDetach() $this->assertGreaterThan($no_of_audits_before, $no_of_audits_after); } + /** + * @test + * @return void + */ + public function itWillAuditDetachByClosure() + { + $firstCategory = factory(Category::class)->create(); + $secondCategory = factory(Category::class)->create(); + $article = factory(Article::class)->create(); + + $article->categories()->attach($firstCategory); + $article->categories()->attach($secondCategory); + + $no_of_audits_before = Audit::where('auditable_type', Article::class)->count(); + $categoryBefore = $article->categories()->first()->getKey(); + + $closure = function () use ($article) { return $article->categories(); }; + + $article->auditDetach($closure, [$firstCategory->getKey()]); + + $no_of_audits_after = Audit::where('auditable_type', Article::class)->count(); + $categoryAfter = $article->categories()->first()->getKey(); + + $this->assertSame($firstCategory->getKey(), $categoryBefore); + $this->assertSame($secondCategory->getKey(), $categoryAfter); + $this->assertNotSame($categoryBefore, $categoryAfter); + $this->assertGreaterThan($no_of_audits_before, $no_of_audits_after); + } + + /** + * @test + * @return void + */ + public function itWillNotAuditDetachByInvalidClosure() + { + $firstCategory = factory(Category::class)->create(); + $secondCategory = factory(Category::class)->create(); + $article = factory(Article::class)->create(); + + $article->categories()->attach($firstCategory); + $article->categories()->attach($secondCategory); + + $this->expectExceptionMessage("Call to undefined or invalid method for BelongsToMany Relationship"); + + $invalidClosure = function () use ($article) { return $article->invalidRelation(); }; + + $article->auditDetach($invalidClosure, [$firstCategory->getKey()]); + } + /** * @test * @return void From 7128bb071092c4675d30887f30159b0cd82a35e4 Mon Sep 17 00:00:00 2001 From: wilianx7 Date: Sat, 17 Feb 2024 11:57:53 -0300 Subject: [PATCH 4/5] feat (Auditable): closure implementation for Attach, Detach and Sync --- src/Auditable.php | 83 ++++++++++++++++++++--------------------------- 1 file changed, 36 insertions(+), 47 deletions(-) diff --git a/src/Auditable.php b/src/Auditable.php index 3c38779a..6e052d79 100644 --- a/src/Auditable.php +++ b/src/Auditable.php @@ -664,77 +664,79 @@ public function transitionTo(Contracts\Audit $audit, bool $old = false): Contrac */ /** - * @param string|\Closure $relation + * @param string $relationName * @param mixed $id * @param array $attributes * @param bool $touch * @param array $columns + * @param \Closure|null $callback * @return void * @throws AuditingException */ - public function auditAttach($relation, $id, array $attributes = [], $touch = true, $columns = ['*']) + public function auditAttach(string $relationName, $id, array $attributes = [], $touch = true, $columns = ['*'], $callback = null) { - if (is_string($relation)) { - $this->validateRelationshipMethodExistence($relation, 'attach'); - } else { - $this->validateBelongsToManyRelationshipClosure($relation); + $this->validateRelationshipMethodExistence($relationName, 'attach'); + + $relationCall = $this->{$relationName}(); + + if ($callback instanceof \Closure) { + $this->applyClosureToRelationship($relationCall, $callback); } - $relationCall = is_string($relation) ? $this->{$relation}() : $relation(); $old = $relationCall->get($columns); $relationCall->attach($id, $attributes, $touch); $new = $relationCall->get($columns); - $relationName = is_string($relation) ? $relation : $relationCall->getRelationName(); - $this->dispatchRelationAuditEvent($relationName, 'attach', $old, $new); } /** - * @param string|\Closure $relation + * @param string $relationName * @param mixed $ids * @param bool $touch * @param array $columns + * @param \Closure|null $callback * @return int * @throws AuditingException */ - public function auditDetach($relation, $ids = null, $touch = true, $columns = ['*']) + public function auditDetach(string $relationName, $ids = null, $touch = true, $columns = ['*'], $callback = null) { - if (is_string($relation)) { - $this->validateRelationshipMethodExistence($relation, 'detach'); - } else { - $this->validateBelongsToManyRelationshipClosure($relation); + $this->validateRelationshipMethodExistence($relationName, 'detach'); + + $relationCall = $this->{$relationName}(); + + if ($callback instanceof \Closure) { + $this->applyClosureToRelationship($relationCall, $callback); } - $relationCall = is_string($relation) ? $this->{$relation}() : $relation(); $old = $relationCall->get($columns); $results = $relationCall->detach($ids, $touch); $new = $relationCall->get($columns); - $relationName = is_string($relation) ? $relation : $relationCall->getRelationName(); - $this->dispatchRelationAuditEvent($relationName, 'detach', $old, $new); return empty($results) ? 0 : $results; } /** - * @param string|\Closure $relation + * @param string $relationName * @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids * @param bool $detaching * @param array $columns + * @param \Closure|null $callback * @return array * @throws AuditingException */ - public function auditSync($relation, $ids, $detaching = true, $columns = ['*']) + public function auditSync(string $relationName, $ids, $detaching = true, $columns = ['*'], $callback = null) { - if (is_string($relation)) { - $this->validateRelationshipMethodExistence($relation, 'sync'); - } else { - $this->validateBelongsToManyRelationshipClosure($relation); + $this->validateRelationshipMethodExistence($relationName, 'sync'); + + $relationCall = $this->{$relationName}(); + + if ($callback instanceof \Closure) { + $this->applyClosureToRelationship($relationCall, $callback); } - $relationCall = is_string($relation) ? $this->{$relation}() : $relation(); $old = $relationCall->get($columns); $changes = $relationCall->sync($ids, $detaching); @@ -744,27 +746,24 @@ public function auditSync($relation, $ids, $detaching = true, $columns = ['*']) $new = $relationCall->get($columns); } - $relationName = is_string($relation) ? $relation : $relationCall->getRelationName(); - $this->dispatchRelationAuditEvent($relationName, 'sync', $old, $new); return $changes; } /** - * @param string|\Closure $relation + * @param string $relationName * @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids * @param array $columns + * @param \Closure|null $callback * @return array * @throws AuditingException */ - public function auditSyncWithoutDetaching($relation, $ids, $columns = ['*']) + public function auditSyncWithoutDetaching(string $relationName, $ids, $columns = ['*'], $callback = null) { - if (is_string($relation)) { - $this->validateRelationshipMethodExistence($relation, 'syncWithoutDetaching'); - } + $this->validateRelationshipMethodExistence($relationName, 'syncWithoutDetaching'); - return $this->auditSync($relation, $ids, false, $columns); + return $this->auditSync($relationName, $ids, false, $columns, $callback); } /** @@ -799,22 +798,12 @@ private function validateRelationshipMethodExistence(string $relationName, strin } } - private function validateBelongsToManyRelationshipClosure(\Closure $closure): void + private function applyClosureToRelationship(BelongsToMany $relation, \Closure $closure): void { - $hasParams = !!(new \ReflectionFunction($closure))->getNumberOfParameters(); - - if ($hasParams) { - throw new AuditingException("Invalid Closure for BelongsToMany Relationship"); - } - try { - $closure(); - } catch (\Exception $exception) { - throw new AuditingException("Call to undefined or invalid method for BelongsToMany Relationship"); - } - - if (!($closure() instanceof BelongsToMany)) { - throw new AuditingException("Invalid Closure for BelongsToMany Relationship"); + $closure($relation); + } catch (\Throwable $exception) { + throw new AuditingException("Invalid Closure for {$relation->getRelationName()} Relationship"); } } } From 102250b54d8fcef3fc3dd8a9ffbc2668d863bfb9 Mon Sep 17 00:00:00 2001 From: wilianx7 Date: Sat, 17 Feb 2024 11:59:19 -0300 Subject: [PATCH 5/5] test (AuditingTest): closure validations for Detach and Sync --- tests/Functional/AuditingTest.php | 115 +++++++++--------- ...00_000003_create_categories_test_table.php | 1 + 2 files changed, 59 insertions(+), 57 deletions(-) diff --git a/tests/Functional/AuditingTest.php b/tests/Functional/AuditingTest.php index dc59f202..16624c06 100644 --- a/tests/Functional/AuditingTest.php +++ b/tests/Functional/AuditingTest.php @@ -3,6 +3,7 @@ namespace OwenIt\Auditing\Tests\Functional; use Carbon\Carbon; +use Illuminate\Database\QueryException; use Illuminate\Foundation\Testing\Assert; use Illuminate\Foundation\Testing\WithFaker; use Illuminate\Support\Facades\App; @@ -601,44 +602,6 @@ public function itWillAuditAttach() $this->assertSame($secondCategory->name, $lastArticleAudit['new'][0]['name']); } - /** - * @test - * @return void - */ - public function itWillAuditAttachByClosure() - { - $firstCategory = factory(Category::class)->create(); - $secondCategory = factory(Category::class)->create(); - $article = factory(Article::class)->create(); - - $closure = function () use ($article) {return $article->categories(); }; - - $article->auditAttach($closure, $firstCategory); - $article->auditAttach($closure, $secondCategory); - $lastArticleAudit = $article->audits->last()->getModified()['categories']; - - $this->assertSame($firstCategory->name, $article->categories->first()->name); - $this->assertSame(0, count($lastArticleAudit['old'])); - $this->assertSame(1, count($lastArticleAudit['new'])); - $this->assertSame($secondCategory->name, $lastArticleAudit['new'][0]['name']); - } - - /** - * @test - * @return void - */ - public function itWillNotAuditAttachByInvalidClosure() - { - $firstCategory = factory(Category::class)->create(); - $article = factory(Article::class)->create(); - - $this->expectExceptionMessage("Invalid Closure for BelongsToMany Relationship"); - - $invalidClosure = function () use ($article) { return $article->title; }; - - $article->auditAttach($invalidClosure, $firstCategory); - } - /** * @test * @return void @@ -687,16 +650,22 @@ public function itWillAuditSyncByClosure() { $firstCategory = factory(Category::class)->create(); $secondCategory = factory(Category::class)->create(); + $thirdCategory = factory(Category::class)->create(); $article = factory(Article::class)->create(); - $article->categories()->attach($firstCategory); + $article->categories()->attach([$firstCategory->getKey() => [ 'pivot_type' => 'PIVOT_1' ]]); + $article->categories()->attach([$secondCategory->getKey() => [ 'pivot_type' => 'PIVOT_2' ]]); $no_of_audits_before = Audit::where('auditable_type', Article::class)->count(); $categoryBefore = $article->categories()->first()->getKey(); - $closure = function () use ($article) { return $article->categories(); }; - - $article->auditSync($closure, [$secondCategory->getKey()]); + $article->auditSync( + 'categories', + [$thirdCategory->getKey() => [ 'pivot_type' => 'PIVOT_1' ]], + true, + ['*'], + function ($categories) { return $categories->wherePivot('pivot_type', 'PIVOT_1'); } + ); $no_of_audits_after = Audit::where('auditable_type', Article::class)->count(); $categoryAfter = $article->categories()->first()->getKey(); @@ -705,6 +674,21 @@ public function itWillAuditSyncByClosure() $this->assertSame($secondCategory->getKey(), $categoryAfter); $this->assertNotSame($categoryBefore, $categoryAfter); $this->assertGreaterThan($no_of_audits_before, $no_of_audits_after); + + $this->assertSame( + "{$secondCategory->getKey()},{$thirdCategory->getKey()}", + $article->categories()->pluck('id')->join(',') + ); + + $this->assertSame( + $secondCategory->getKey(), + $article->categories()->wherePivot('pivot_type', 'PIVOT_2')->first()->getKey() + ); + + $this->assertSame( + $thirdCategory->getKey(), + $article->categories()->wherePivot('pivot_type', 'PIVOT_1')->first()->getKey() + ); } /** @@ -719,11 +703,15 @@ public function itWillNotAuditSyncByInvalidClosure() $article->categories()->attach($firstCategory); - $this->expectExceptionMessage("Invalid Closure for BelongsToMany Relationship"); - - $invalidClosure = function ($param) use ($article) { return $article->categories(); }; + $this->expectException(QueryException::class); - $article->auditSync($invalidClosure, [$secondCategory->getKey()]); + $article->auditSync( + 'categories', + [$secondCategory->getKey()], + true, + ['*'], + function ($categories) { return $categories->wherePivot('invalid_pivot_column', 'PIVOT_1'); } + ); } /** @@ -761,17 +749,23 @@ public function itWillAuditDetachByClosure() { $firstCategory = factory(Category::class)->create(); $secondCategory = factory(Category::class)->create(); + $thirdCategory = factory(Category::class)->create(); $article = factory(Article::class)->create(); - $article->categories()->attach($firstCategory); - $article->categories()->attach($secondCategory); + $article->categories()->attach([$firstCategory->getKey() => [ 'pivot_type' => 'PIVOT_1' ]]); + $article->categories()->attach([$secondCategory->getKey() => [ 'pivot_type' => 'PIVOT_2' ]]); + $article->categories()->attach([$thirdCategory->getKey() => [ 'pivot_type' => 'PIVOT_2' ]]); $no_of_audits_before = Audit::where('auditable_type', Article::class)->count(); $categoryBefore = $article->categories()->first()->getKey(); - $closure = function () use ($article) { return $article->categories(); }; - - $article->auditDetach($closure, [$firstCategory->getKey()]); + $article->auditDetach( + 'categories', + [$firstCategory->getKey(), $secondCategory->getKey(), $thirdCategory->getKey()], + true, + ['*'], + function ($categories) { return $categories->wherePivot('pivot_type', 'PIVOT_1'); } + ); $no_of_audits_after = Audit::where('auditable_type', Article::class)->count(); $categoryAfter = $article->categories()->first()->getKey(); @@ -780,6 +774,11 @@ public function itWillAuditDetachByClosure() $this->assertSame($secondCategory->getKey(), $categoryAfter); $this->assertNotSame($categoryBefore, $categoryAfter); $this->assertGreaterThan($no_of_audits_before, $no_of_audits_after); + + $this->assertSame( + "{$secondCategory->getKey()},{$thirdCategory->getKey()}", + $article->categories()->pluck('id')->join(',') + ); } /** @@ -789,17 +788,19 @@ public function itWillAuditDetachByClosure() public function itWillNotAuditDetachByInvalidClosure() { $firstCategory = factory(Category::class)->create(); - $secondCategory = factory(Category::class)->create(); $article = factory(Article::class)->create(); $article->categories()->attach($firstCategory); - $article->categories()->attach($secondCategory); - - $this->expectExceptionMessage("Call to undefined or invalid method for BelongsToMany Relationship"); - $invalidClosure = function () use ($article) { return $article->invalidRelation(); }; + $this->expectExceptionMessage('Invalid Closure for categories Relationship'); - $article->auditDetach($invalidClosure, [$firstCategory->getKey()]); + $article->auditDetach( + 'categories', + [$firstCategory->getKey()], + true, + ['*'], + function ($categories) { return $categories->invalid(); } + ); } /** diff --git a/tests/database/migrations/0000_00_00_000003_create_categories_test_table.php b/tests/database/migrations/0000_00_00_000003_create_categories_test_table.php index 21ba202c..6fd0258f 100644 --- a/tests/database/migrations/0000_00_00_000003_create_categories_test_table.php +++ b/tests/database/migrations/0000_00_00_000003_create_categories_test_table.php @@ -21,6 +21,7 @@ public function up() Schema::create('model_has_categories', function (Blueprint $table) { $table->string('model_type'); + $table->string('pivot_type')->nullable(); $table->unsignedBigInteger('category_id'); $table->unsignedBigInteger('model_id'); $table->timestamps();