diff --git a/src/PrefetchBuffer.php b/src/PrefetchBuffer.php index 5105d02249..d93d3d93c5 100644 --- a/src/PrefetchBuffer.php +++ b/src/PrefetchBuffer.php @@ -22,7 +22,10 @@ class PrefetchBuffer /** @param array $arguments The input arguments passed from GraphQL to the field. */ public function register(object $object, array $arguments): void { - $this->objects[$this->computeHash($arguments)][] = $object; + $hash = $this->computeHash($arguments); + + $this->objects[$hash][] = $object; + unset($this->results[$hash]); } /** @param array $arguments The input arguments passed from GraphQL to the field. */ @@ -44,7 +47,8 @@ public function getObjectsByArguments(array $arguments): array /** @param array $arguments The input arguments passed from GraphQL to the field. */ public function purge(array $arguments): void { - unset($this->objects[$this->computeHash($arguments)]); + $hash = $this->computeHash($arguments); + unset($this->objects[$hash], $this->results[$hash]); } /** @param array $arguments The input arguments passed from GraphQL to the field. */ diff --git a/tests/Fixtures/Integration/Controllers/BlogController.php b/tests/Fixtures/Integration/Controllers/BlogController.php new file mode 100644 index 0000000000..c3ed027d09 --- /dev/null +++ b/tests/Fixtures/Integration/Controllers/BlogController.php @@ -0,0 +1,19 @@ +id; + } + + /** + * @param Post[][] $prefetchedPosts + * @return Post[] + */ + #[Field(prefetchMethod: 'prefetchPosts')] + public function getPosts(array $prefetchedPosts): array + { + return $prefetchedPosts[$this->id] ?? []; + } + + /** + * @param Blog[][] $prefetchedSubBlogs + * @return Blog[] + */ + #[Field(prefetchMethod: 'prefetchSubBlogs')] + public function getSubBlogs(array $prefetchedSubBlogs): array + { + return $prefetchedSubBlogs[$this->id] ?? []; + } + + /** + * @param self[] $blogs + * @return Post[][] + */ + public function prefetchPosts(array $blogs): array + { + $posts = []; + foreach ($blogs as $blog) { + $blogId = $blog->getId(); + $posts[$blogId][] = new Post("post-$blogId.1"); + $posts[$blogId][] = new Post("post-$blogId.2"); + } + return $posts; + } + + /** + * @param self[] $blogs + * @return Blog[][] + */ + public function prefetchSubBlogs(array $blogs): array + { + $subBlogs = []; + foreach ($blogs as $blog) { + $blogId = $blog->getId(); + $subBlogId = $blogId * 10; + $subBlogs[$blogId][] = new Blog($subBlogId); + } + return $subBlogs; + } +} diff --git a/tests/Fixtures/Integration/Models/Comment.php b/tests/Fixtures/Integration/Models/Comment.php new file mode 100644 index 0000000000..7d853c281a --- /dev/null +++ b/tests/Fixtures/Integration/Models/Comment.php @@ -0,0 +1,21 @@ +text; + } +} diff --git a/tests/Fixtures/Integration/Models/Post.php b/tests/Fixtures/Integration/Models/Post.php index 8b611202d2..90c0e1e4f2 100644 --- a/tests/Fixtures/Integration/Models/Post.php +++ b/tests/Fixtures/Integration/Models/Post.php @@ -97,6 +97,29 @@ public function setSummary(?string $summary): void $this->summary = $summary; } + /** + * @param Comment[][] $prefetchedComments + * @return Comment[] + */ + #[Field(prefetchMethod: 'prefetchComments')] + public function getComments(array $prefetchedComments): array + { + return $prefetchedComments[$this->title] ?? []; + } + + /** + * @param self[] $posts + * @return Comment[][] + */ + public function prefetchComments(array $posts): array + { + $comments = []; + foreach ($posts as $post) { + $comments[$post->title][] = new Comment("comment for $post->title"); + } + return $comments; + } + /** * @param string $inaccessible */ diff --git a/tests/Integration/EndToEndTest.php b/tests/Integration/EndToEndTest.php index c5756cc58b..b2b7a29e7f 100644 --- a/tests/Integration/EndToEndTest.php +++ b/tests/Integration/EndToEndTest.php @@ -2500,4 +2500,119 @@ public function isAllowed(string $right, $subject = null): bool $data = $this->getSuccessResult($result); $this->assertSame(["graph", "ql"], $data['updateTrickyProduct']['list']); } + + public function testPrefetchingOfSameTypeInDifferentNestingLevels(): void + { + /** @var Schema $schema */ + $schema = $this->mainContainer->get(Schema::class); + + $schema->assertValid(); + + $queryString = ' + query { + blogs { + id + subBlogs { + id + posts { + title + comments { + text + } + } + } + posts { + title + comments { + text + } + } + } + } + '; + + $result = GraphQL::executeQuery( + $schema, + $queryString, + null, + new Context() + ); + + $this->assertSame([ + 'blogs' => [ + [ + 'id' => '1', + 'subBlogs' => [ + [ + 'id' => '10', + 'posts' => [ + [ + 'title' => 'post-10.1', + 'comments' => [ + ['text' => 'comment for post-10.1'], + ], + ], + [ + 'title' => 'post-10.2', + 'comments' => [ + ['text' => 'comment for post-10.2'], + ], + ] + ] + ] + ], + 'posts' => [ + [ + 'title' => 'post-1.1', + 'comments' => [ + ['text' => 'comment for post-1.1'], + ], + ], + [ + 'title' => 'post-1.2', + 'comments' => [ + ['text' => 'comment for post-1.2'], + ], + ] + ] + ], + [ + 'id' => '2', + 'subBlogs' => [ + [ + 'id' => '20', + 'posts' => [ + [ + 'title' => 'post-20.1', + 'comments' => [ + ['text' => 'comment for post-20.1'], + ], + ], + [ + 'title' => 'post-20.2', + 'comments' => [ + ['text' => 'comment for post-20.2'], + ], + ] + ] + ] + ], + 'posts' => [ + [ + 'title' => 'post-2.1', + 'comments' => [ + ['text' => 'comment for post-2.1'], + ], + ], + [ + 'title' => 'post-2.2', + 'comments' => [ + ['text' => 'comment for post-2.2'], + ], + ] + ] + ], + ] + ], $this->getSuccessResult($result)); + } }