diff --git a/src/Execution/Arguments/NestedBelongsTo.php b/src/Execution/Arguments/NestedBelongsTo.php index 7a446f084..696342f6e 100644 --- a/src/Execution/Arguments/NestedBelongsTo.php +++ b/src/Execution/Arguments/NestedBelongsTo.php @@ -71,7 +71,7 @@ public static function disconnectOrDelete(BelongsTo $relation, ArgumentSet $args && $args->arguments['delete']->value ) { $relation->dissociate(); - $relation->delete(); + $relation->first()?->delete(); } } } diff --git a/src/Schema/Directives/DeleteDirective.php b/src/Schema/Directives/DeleteDirective.php index e5ed704e4..9aa162e96 100644 --- a/src/Schema/Directives/DeleteDirective.php +++ b/src/Schema/Directives/DeleteDirective.php @@ -90,7 +90,7 @@ public function __invoke($model, $idOrIds): void $relation->getParent()->save(); } - $relation->delete(); + $relation->first()?->delete(); } } else { $related = $relation->make(); // @phpstan-ignore method.notFound (Relation delegates to Builder) diff --git a/tests/Integration/Execution/MutationExecutor/BelongsToTest.php b/tests/Integration/Execution/MutationExecutor/BelongsToTest.php index 8c230aaa1..7d6ee28e8 100644 --- a/tests/Integration/Execution/MutationExecutor/BelongsToTest.php +++ b/tests/Integration/Execution/MutationExecutor/BelongsToTest.php @@ -794,9 +794,53 @@ public function testUpdateAndDeleteBelongsTo(string $action): void ); } + /** @dataProvider existingModelMutations */ + #[DataProvider('existingModelMutations')] + public function testDeleteBelongsToFiresModelEvents(string $action): void + { + $user = factory(User::class)->create(); + $this->assertInstanceOf(User::class, $user); + + $task = factory(Task::class)->make(); + $this->assertInstanceOf(Task::class, $task); + $task->user()->associate($user); + $task->save(); + + $deletingCalled = false; + User::deleting(static function () use (&$deletingCalled): void { + $deletingCalled = true; + }); + + $this->graphQL(/** @lang GraphQL */ <<id} + user: { + delete: true + } + }) { + id + } + } +GRAPHQL + )->assertJson([ + 'data' => [ + "{$action}Task" => [ + 'id' => "{$task->id}", + ], + ], + ]); + + $this->assertTrue( + $deletingCalled, + 'Deleting the related model must trigger model events.', + ); + } + public function testCreateUsingUpsertAndDeleteBelongsTo(): void { - factory(User::class)->create(); + $user = factory(User::class)->create(); + $this->assertInstanceOf(User::class, $user); $this->graphQL(/** @lang GraphQL */ ' mutation { @@ -842,10 +886,10 @@ public function testDoesNotDeleteOrDisconnectOnFalsyValues(string $action): void $user = factory(User::class)->create(); $this->assertInstanceOf(User::class, $user); - $task = $user->tasks()->save( - factory(Task::class)->make(), - ); + $task = factory(Task::class)->make(); $this->assertInstanceOf(Task::class, $task); + $task->user()->associate($user); + $task->save(); $this->graphQL(/** @lang GraphQL */ <<make(); $this->assertInstanceOf(Post::class, $post); - $task->post()->save($post); + $post->task()->associate($task); + $post->save(); $this->schema .= /** @lang GraphQL */ ' type Mutation { @@ -430,6 +431,60 @@ public function testDeleteHasOneThroughNestedArgResolver(): void $this->assertNull(Post::find($post->id)); } + public function testDeleteHasOneThroughNestedArgResolverFiresModelEvents(): void + { + $task = factory(Task::class)->create(); + $this->assertInstanceOf(Task::class, $task); + + $post = factory(Post::class)->make(); + $this->assertInstanceOf(Post::class, $post); + $task->post()->save($post); + + $deletingCalled = false; + Post::deleting(static function () use (&$deletingCalled): void { + $deletingCalled = true; + }); + + $this->schema .= /** @lang GraphQL */ ' + type Mutation { + updateTask( + id: ID! + deletePost: Boolean @delete(relation: "post") + ): Task! @update + } + + type Task { + id: ID! + post: Post + } + + type Post { + id: ID! + } + '; + + $this->graphQL(/** @lang GraphQL */ ' + mutation ($id: ID!) { + updateTask(id: $id, deletePost: true) { + id + } + } + ', [ + 'id' => $task->id, + ])->assertJson([ + 'data' => [ + 'updateTask' => [ + 'id' => "{$task->id}", + ], + ], + ]); + + $this->assertTrue( + $deletingCalled, + 'Deleting the related model must trigger model events.', + ); + } + public function testDeleteBelongsToThroughNestedArgResolver(): void { $user = factory(User::class)->create(); @@ -482,6 +537,61 @@ public function testDeleteBelongsToThroughNestedArgResolver(): void $this->assertNull(User::find($user->id)); } + public function testDeleteBelongsToThroughNestedArgResolverFiresModelEvents(): void + { + $user = factory(User::class)->create(); + $this->assertInstanceOf(User::class, $user); + + $task = factory(Task::class)->make(); + $this->assertInstanceOf(Task::class, $task); + $task->user()->associate($user); + $task->save(); + + $deletingCalled = false; + User::deleting(static function () use (&$deletingCalled): void { + $deletingCalled = true; + }); + + $this->schema .= /** @lang GraphQL */ ' + type Mutation { + updateTask( + id: ID! + deleteUser: Boolean @delete(relation: "user") + ): Task! @update + } + + type Task { + id: ID! + user: User + } + + type User { + id: ID! + } + '; + + $this->graphQL(/** @lang GraphQL */ ' + mutation ($id: ID!) { + updateTask(id: $id, deleteUser: true) { + id + } + } + ', [ + 'id' => $task->id, + ])->assertJson([ + 'data' => [ + 'updateTask' => [ + 'id' => "{$task->id}", + ], + ], + ]); + + $this->assertTrue( + $deletingCalled, + 'Deleting the related model must trigger model events.', + ); + } + public function testDeletingReturnsFalseTriggersException(): void { User::deleting(static fn (): bool => false);