From 8f7d90fe62de372129c44ccfe3fe751169015256 Mon Sep 17 00:00:00 2001 From: stefan Date: Thu, 18 Jul 2024 13:17:16 +0200 Subject: [PATCH 1/5] deleting a nested BelongsTo relation now triggers model events on the deleted model --- src/Execution/Arguments/NestedBelongsTo.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Execution/Arguments/NestedBelongsTo.php b/src/Execution/Arguments/NestedBelongsTo.php index 7a446f0840..696342f6eb 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(); } } } From 1cd1188d03df127b403e9ac1f003b37ec90cb8d6 Mon Sep 17 00:00:00 2001 From: stefan Date: Fri, 19 Jul 2024 15:10:03 +0200 Subject: [PATCH 2/5] deleting a nested BelongsTo relation now triggers model events on the deleted model --- src/Schema/Directives/DeleteDirective.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Schema/Directives/DeleteDirective.php b/src/Schema/Directives/DeleteDirective.php index c3cc90fbd4..e6ec94b7af 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 { // @phpstan-ignore-next-line Relation&Builder mixin not recognized From 7e5fa6c26b4c12138a353c044fe318d96b0e8193 Mon Sep 17 00:00:00 2001 From: stefan Date: Fri, 19 Jul 2024 15:11:48 +0200 Subject: [PATCH 3/5] deleting a nested BelongsTo relation now triggers model events on the deleted model --- src/Schema/Directives/DeleteDirective.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Schema/Directives/DeleteDirective.php b/src/Schema/Directives/DeleteDirective.php index e6ec94b7af..58131c69fb 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->first()->delete(); + $relation->first()?->delete(); } } else { // @phpstan-ignore-next-line Relation&Builder mixin not recognized From e76ddcc73fa12ce96c4498a356e9e23449ec3136 Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 8 Feb 2026 20:52:42 +0100 Subject: [PATCH 4/5] Add regression tests for relation delete model events --- .../MutationExecutor/BelongsToTest.php | 43 +++++++ .../Schema/Directives/DeleteDirectiveTest.php | 109 ++++++++++++++++++ 2 files changed, 152 insertions(+) diff --git a/tests/Integration/Execution/MutationExecutor/BelongsToTest.php b/tests/Integration/Execution/MutationExecutor/BelongsToTest.php index 8c230aaa1e..7f291f1dd2 100644 --- a/tests/Integration/Execution/MutationExecutor/BelongsToTest.php +++ b/tests/Integration/Execution/MutationExecutor/BelongsToTest.php @@ -794,6 +794,49 @@ 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 = $user->tasks()->save( + factory(Task::class)->make(), + ); + $this->assertInstanceOf(Task::class, $task); + + $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(); diff --git a/tests/Integration/Schema/Directives/DeleteDirectiveTest.php b/tests/Integration/Schema/Directives/DeleteDirectiveTest.php index 3fd9315c0c..b2dd12913a 100644 --- a/tests/Integration/Schema/Directives/DeleteDirectiveTest.php +++ b/tests/Integration/Schema/Directives/DeleteDirectiveTest.php @@ -430,6 +430,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 +536,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); From 945fe16409a70d88c01350a4ae30bfa02fb44b37 Mon Sep 17 00:00:00 2001 From: spawnia Date: Mon, 9 Feb 2026 10:56:45 +0100 Subject: [PATCH 5/5] test: align PR-2588 model setup with current test patterns --- .../Execution/MutationExecutor/BelongsToTest.php | 15 ++++++++------- .../Schema/Directives/DeleteDirectiveTest.php | 3 ++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/Integration/Execution/MutationExecutor/BelongsToTest.php b/tests/Integration/Execution/MutationExecutor/BelongsToTest.php index 7f291f1dd2..7d6ee28e85 100644 --- a/tests/Integration/Execution/MutationExecutor/BelongsToTest.php +++ b/tests/Integration/Execution/MutationExecutor/BelongsToTest.php @@ -801,10 +801,10 @@ public function testDeleteBelongsToFiresModelEvents(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(); $deletingCalled = false; User::deleting(static function () use (&$deletingCalled): void { @@ -839,7 +839,8 @@ public function testDeleteBelongsToFiresModelEvents(string $action): void public function testCreateUsingUpsertAndDeleteBelongsTo(): void { - factory(User::class)->create(); + $user = factory(User::class)->create(); + $this->assertInstanceOf(User::class, $user); $this->graphQL(/** @lang GraphQL */ ' mutation { @@ -885,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 {