Skip to content

Commit 256da31

Browse files
authored
Fix prefetching the same GQL type for batch request (#658)
* Fix prefetching the same GQL type for batch request * Fix phpstan errors * Fix after pull master and resolving conflicts * Fix cs * Fix phpstan * Bring back ResolveInfo|null instead of ?ResolveInfo * fix cs
1 parent 758d02d commit 256da31

File tree

10 files changed

+290
-25
lines changed

10 files changed

+290
-25
lines changed

src/Parameters/PrefetchDataParameter.php

+5-5
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public function resolve(object|null $source, array $args, mixed $context, Resolv
4343
}
4444

4545
$prefetchBuffer = $context->getPrefetchBuffer($this);
46-
$prefetchBuffer->register($source, $args);
46+
$prefetchBuffer->register($source, $args, $info);
4747

4848
// The way this works is simple: GraphQL first iterates over every requested field and calls ->resolve()
4949
// on it. That, in turn, calls this method. GraphQL doesn't need the actual value just yet; it simply
@@ -53,20 +53,20 @@ public function resolve(object|null $source, array $args, mixed $context, Resolv
5353
// needed, GraphQL calls the callback of Deferred below. That's when we call the prefetch method,
5454
// already knowing all the requested fields (source-arguments combinations).
5555
return new Deferred(function () use ($info, $context, $args, $prefetchBuffer) {
56-
if (! $prefetchBuffer->hasResult($args)) {
56+
if (! $prefetchBuffer->hasResult($args, $info)) {
5757
$prefetchResult = $this->computePrefetch($args, $context, $info, $prefetchBuffer);
5858

59-
$prefetchBuffer->storeResult($prefetchResult, $args);
59+
$prefetchBuffer->storeResult($prefetchResult, $args, $info);
6060
}
6161

62-
return $prefetchResult ?? $prefetchBuffer->getResult($args);
62+
return $prefetchResult ?? $prefetchBuffer->getResult($args, $info);
6363
});
6464
}
6565

6666
/** @param array<string, mixed> $args */
6767
private function computePrefetch(array $args, mixed $context, ResolveInfo $info, PrefetchBuffer $prefetchBuffer): mixed
6868
{
69-
$sources = $prefetchBuffer->getObjectsByArguments($args);
69+
$sources = $prefetchBuffer->getObjectsByArguments($args, $info);
7070
$toPassPrefetchArgs = QueryField::paramsToArguments($this->fieldName, $this->parameters, null, $args, $context, $info, $this->resolver);
7171

7272
return ($this->resolver)($sources, ...$toPassPrefetchArgs);

src/PrefetchBuffer.php

+46-20
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace TheCodingMachine\GraphQLite;
66

7+
use GraphQL\Type\Definition\ResolveInfo;
8+
79
use function array_key_exists;
810
use function md5;
911
use function serialize;
@@ -20,14 +22,27 @@ class PrefetchBuffer
2022
private array $results = [];
2123

2224
/** @param array<array-key, mixed> $arguments The input arguments passed from GraphQL to the field. */
23-
public function register(object $object, array $arguments): void
24-
{
25-
$this->objects[$this->computeHash($arguments)][] = $object;
25+
public function register(
26+
object $object,
27+
array $arguments,
28+
ResolveInfo|null $info = null,
29+
): void {
30+
$this->objects[$this->computeHash($arguments, $info)][] = $object;
2631
}
2732

2833
/** @param array<array-key, mixed> $arguments The input arguments passed from GraphQL to the field. */
29-
private function computeHash(array $arguments): string
30-
{
34+
private function computeHash(
35+
array $arguments,
36+
ResolveInfo|null $info,
37+
): string {
38+
if (
39+
$info instanceof ResolveInfo
40+
&& isset($info->operation)
41+
&& $info->operation->loc?->source?->body !== null
42+
) {
43+
return md5(serialize($arguments) . $info->operation->loc->source->body);
44+
}
45+
3146
return md5(serialize($arguments));
3247
}
3348

@@ -36,32 +51,43 @@ private function computeHash(array $arguments): string
3651
*
3752
* @return array<int, object>
3853
*/
39-
public function getObjectsByArguments(array $arguments): array
40-
{
41-
return $this->objects[$this->computeHash($arguments)] ?? [];
54+
public function getObjectsByArguments(
55+
array $arguments,
56+
ResolveInfo|null $info = null,
57+
): array {
58+
return $this->objects[$this->computeHash($arguments, $info)] ?? [];
4259
}
4360

4461
/** @param array<array-key, mixed> $arguments The input arguments passed from GraphQL to the field. */
45-
public function purge(array $arguments): void
46-
{
47-
unset($this->objects[$this->computeHash($arguments)]);
62+
public function purge(
63+
array $arguments,
64+
ResolveInfo|null $info = null,
65+
): void {
66+
unset($this->objects[$this->computeHash($arguments, $info)]);
4867
}
4968

5069
/** @param array<array-key, mixed> $arguments The input arguments passed from GraphQL to the field. */
51-
public function storeResult(mixed $result, array $arguments): void
52-
{
53-
$this->results[$this->computeHash($arguments)] = $result;
70+
public function storeResult(
71+
mixed $result,
72+
array $arguments,
73+
ResolveInfo|null $info = null,
74+
): void {
75+
$this->results[$this->computeHash($arguments, $info)] = $result;
5476
}
5577

5678
/** @param array<array-key, mixed> $arguments The input arguments passed from GraphQL to the field. */
57-
public function hasResult(array $arguments): bool
58-
{
59-
return array_key_exists($this->computeHash($arguments), $this->results);
79+
public function hasResult(
80+
array $arguments,
81+
ResolveInfo|null $info = null,
82+
): bool {
83+
return array_key_exists($this->computeHash($arguments, $info), $this->results);
6084
}
6185

6286
/** @param array<array-key, mixed> $arguments The input arguments passed from GraphQL to the field. */
63-
public function getResult(array $arguments): mixed
64-
{
65-
return $this->results[$this->computeHash($arguments)];
87+
public function getResult(
88+
array $arguments,
89+
ResolveInfo|null $info = null,
90+
): mixed {
91+
return $this->results[$this->computeHash($arguments, $info)];
6692
}
6793
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TheCodingMachine\GraphQLite\Fixtures\Integration\Controllers;
6+
7+
use TheCodingMachine\GraphQLite\Annotations\Query;
8+
use TheCodingMachine\GraphQLite\Fixtures\Integration\Models\Company;
9+
10+
class CompanyController
11+
{
12+
#[Query]
13+
public function getCompany(string $id): Company
14+
{
15+
return new Company('Company');
16+
}
17+
}

tests/Fixtures/Integration/Controllers/ContactController.php

+10
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ public function getContacts(): array
2626
];
2727
}
2828

29+
#[Query]
30+
public function getContact(string $name): ?Contact
31+
{
32+
return match( $name ) {
33+
'Joe' => new Contact('Joe'),
34+
'Bill' => new Contact('Bill'),
35+
default => null,
36+
};
37+
}
38+
2939
#[Mutation]
3040
public function saveContact(Contact $contact): Contact
3141
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TheCodingMachine\GraphQLite\Fixtures\Integration\Models;
6+
7+
use TheCodingMachine\GraphQLite\Annotations\Type;
8+
9+
#[Type]
10+
class Company
11+
{
12+
public function __construct(
13+
public readonly string $name
14+
) {
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TheCodingMachine\GraphQLite\Fixtures\Integration\Types;
6+
7+
use TheCodingMachine\GraphQLite\Annotations\ExtendType;
8+
use TheCodingMachine\GraphQLite\Annotations\Field;
9+
use TheCodingMachine\GraphQLite\Annotations\Prefetch;
10+
use TheCodingMachine\GraphQLite\Fixtures\Integration\Models\Company;
11+
use TheCodingMachine\GraphQLite\Fixtures\Integration\Models\Contact;
12+
13+
#[ExtendType(class:Company::class)]
14+
class CompanyType
15+
{
16+
17+
#[Field]
18+
public function getName(Company $company): string
19+
{
20+
return $company->name;
21+
}
22+
23+
#[Field]
24+
public function getContact(
25+
Company $company,
26+
#[Prefetch('prefetchContacts')]
27+
array $contacts
28+
): ?Contact {
29+
return $contacts[$company->name] ?? null;
30+
}
31+
32+
public static function prefetchContacts(array $companies): array
33+
{
34+
$contacts = [];
35+
36+
foreach ($companies as $company) {
37+
$contacts[$company->name] = new Contact('Kate');
38+
}
39+
40+
return $contacts;
41+
}
42+
}

tests/Fixtures/Integration/Types/ContactType.php

+53
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
namespace TheCodingMachine\GraphQLite\Fixtures\Integration\Types;
55

6+
use TheCodingMachine\GraphQLite\Fixtures\Integration\Models\Post;
67
use TheCodingMachine\GraphQLite\Annotations\Prefetch;
78
use function array_search;
89
use function strtoupper;
@@ -53,4 +54,56 @@ public static function prefetchContacts(iterable $contacts, string $prefix)
5354
'prefix' => $prefix
5455
];
5556
}
57+
58+
/**
59+
*
60+
* @return Post[]|null
61+
*/
62+
#[Field]
63+
public function getPosts(
64+
Contact $contact,
65+
#[Prefetch('prefetchPosts')]
66+
$posts
67+
): ?array {
68+
return $posts[$contact->getName()] ?? null;
69+
}
70+
71+
public static function prefetchPosts(iterable $contacts): array
72+
{
73+
$posts = [];
74+
foreach ($contacts as $contact) {
75+
$contactPost = array_filter(
76+
self::getContactPosts(),
77+
fn(Post $post) => $post->author?->getName() === $contact->getName()
78+
);
79+
80+
if (!$contactPost) {
81+
continue;
82+
}
83+
84+
$posts[$contact->getName()] = $contactPost;
85+
}
86+
87+
return $posts;
88+
}
89+
90+
private static function getContactPosts(): array
91+
{
92+
return [
93+
self::generatePost('First Joe post', '1', new Contact('Joe')),
94+
self::generatePost('First Bill post', '2', new Contact('Bill')),
95+
self::generatePost('First Kate post', '3', new Contact('Kate')),
96+
];
97+
}
98+
99+
private static function generatePost(
100+
string $title,
101+
string $id,
102+
Contact $author,
103+
): Post {
104+
$post = new Post($title);
105+
$post->id = $id;
106+
$post->author = $author;
107+
return $post;
108+
}
56109
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TheCodingMachine\GraphQLite\Fixtures\Integration\Types;
6+
7+
use TheCodingMachine\GraphQLite\Annotations\ExtendType;
8+
use TheCodingMachine\GraphQLite\Annotations\Field;
9+
use TheCodingMachine\GraphQLite\Fixtures\Integration\Models\Post;
10+
11+
#[ExtendType(class:Post::class)]
12+
class PostType
13+
{
14+
#[Field]
15+
public function getId(Post $post): int
16+
{
17+
return (int) $post->id;
18+
}
19+
20+
#[Field]
21+
public function getTitle(Post $post): string
22+
{
23+
return $post->title;
24+
}
25+
}

0 commit comments

Comments
 (0)