Skip to content

Commit

Permalink
Fix prefetching the same GQL type for batch request
Browse files Browse the repository at this point in the history
  • Loading branch information
discodingo committed Mar 8, 2024
1 parent 11a2600 commit 2f517a1
Show file tree
Hide file tree
Showing 10 changed files with 295 additions and 26 deletions.
65 changes: 44 additions & 21 deletions src/PrefetchBuffer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace TheCodingMachine\GraphQLite;

use GraphQL\Type\Definition\ResolveInfo;
use function array_key_exists;
use function md5;
use function serialize;
Expand All @@ -20,48 +21,70 @@ class PrefetchBuffer
private array $results = [];

/** @param array<int,mixed> $arguments The input arguments passed from GraphQL to the field. */
public function register(object $object, array $arguments): void
{
$this->objects[$this->computeHash($arguments)][] = $object;
public function register(
object $object,
array $arguments,
?ResolveInfo $info = null
): void {
$this->objects[$this->computeHash($arguments, $info)][] = $object;
}

/** @param array<int,mixed> $arguments The input arguments passed from GraphQL to the field. */
private function computeHash(array $arguments): string
{
return md5(serialize($arguments));
private function computeHash(
array $arguments,
?ResolveInfo $info = null
): string {
if (
null === $info
|| null === ($queryBody = $info->operation->loc?->source->body)
) {
return md5(serialize($arguments));
}
return md5(serialize($arguments) . $queryBody);
}

/**
* @param array<int,mixed> $arguments The input arguments passed from GraphQL to the field.
*
* @return array<int, object>
*/
public function getObjectsByArguments(array $arguments): array
{
return $this->objects[$this->computeHash($arguments)] ?? [];
public function getObjectsByArguments(
array $arguments,
?ResolveInfo $info = null
): array {
return $this->objects[$this->computeHash($arguments, $info)] ?? [];
}

/** @param array<int,mixed> $arguments The input arguments passed from GraphQL to the field. */
public function purge(array $arguments): void
{
unset($this->objects[$this->computeHash($arguments)]);
public function purge(
array $arguments,
?ResolveInfo $info = null
): void {
unset($this->objects[$this->computeHash($arguments, $info)]);
}

/** @param array<int,mixed> $arguments The input arguments passed from GraphQL to the field. */
public function storeResult(mixed $result, array $arguments): void
{
$this->results[$this->computeHash($arguments)] = $result;
public function storeResult(
mixed $result,
array $arguments,
?ResolveInfo $info = null
): void {
$this->results[$this->computeHash($arguments, $info)] = $result;
}

/** @param array<int,mixed> $arguments The input arguments passed from GraphQL to the field. */
public function hasResult(array $arguments): bool
{
return array_key_exists($this->computeHash($arguments), $this->results);
public function hasResult(
array $arguments,
?ResolveInfo $info = null
): bool {
return array_key_exists($this->computeHash($arguments, $info), $this->results);
}

/** @param array<int,mixed> $arguments The input arguments passed from GraphQL to the field. */
public function getResult(array $arguments): mixed
{
return $this->results[$this->computeHash($arguments)];
public function getResult(
array $arguments,
?ResolveInfo $info = null
): mixed {
return $this->results[$this->computeHash($arguments, $info)];
}
}
10 changes: 5 additions & 5 deletions src/QueryField.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,28 +104,28 @@ public function __construct(string $name, OutputType $type, array $arguments, Re

$prefetchBuffer = $context->getPrefetchBuffer($this);

$prefetchBuffer->register($source, $args);
$prefetchBuffer->register($source, $args, $info);

return new Deferred(function () use ($prefetchBuffer, $source, $args, $context, $info, $prefetchArgs, $prefetchMethodName, $arguments, $resolveFn, $originalResolver) {
if (! $prefetchBuffer->hasResult($args)) {
if (! $prefetchBuffer->hasResult($args, $info)) {
if ($originalResolver instanceof SourceResolverInterface) {
$originalResolver->setObject($source);
}

// TODO: originalPrefetchResolver and prefetchResolver needed!!!
$prefetchCallable = [$originalResolver->getObject(), $prefetchMethodName];

$sources = $prefetchBuffer->getObjectsByArguments($args);
$sources = $prefetchBuffer->getObjectsByArguments($args, $info);

assert(is_callable($prefetchCallable));
$toPassPrefetchArgs = $this->paramsToArguments($prefetchArgs, $source, $args, $context, $info, $prefetchCallable);

array_unshift($toPassPrefetchArgs, $sources);
assert(is_callable($prefetchCallable));
$prefetchResult = $prefetchCallable(...$toPassPrefetchArgs);
$prefetchBuffer->storeResult($prefetchResult, $args);
$prefetchBuffer->storeResult($prefetchResult, $args, $info);
} else {
$prefetchResult = $prefetchBuffer->getResult($args);
$prefetchResult = $prefetchBuffer->getResult($args, $info);
}

foreach ($arguments as $argument) {
Expand Down
17 changes: 17 additions & 0 deletions tests/Fixtures/Integration/Controllers/CompanyController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace TheCodingMachine\GraphQLite\Fixtures\Integration\Controllers;

use TheCodingMachine\GraphQLite\Annotations\Query;
use TheCodingMachine\GraphQLite\Fixtures\Integration\Models\Company;

class CompanyController
{
/** @Query() */
public function getCompany(string $id): Company
{
return new Company('Company');
}
}
12 changes: 12 additions & 0 deletions tests/Fixtures/Integration/Controllers/ContactController.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ public function getContacts(): array
];
}

/**
* @Query()
*/
public function getContact(string $name): ?Contact
{
return match( $name ) {
'Joe' => new Contact('Joe'),
'Bill' => new Contact('Bill'),
default => null,
};
}

/**
* @Mutation()
* @param Contact $contact
Expand Down
18 changes: 18 additions & 0 deletions tests/Fixtures/Integration/Models/Company.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace TheCodingMachine\GraphQLite\Fixtures\Integration\Models;

use TheCodingMachine\GraphQLite\Annotations\Type;

/**
* @Type()
*/
class Company
{
public function __construct(
public readonly string $name
) {
}
}
43 changes: 43 additions & 0 deletions tests/Fixtures/Integration/Types/CompanyType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace TheCodingMachine\GraphQLite\Fixtures\Integration\Types;

use TheCodingMachine\GraphQLite\Annotations\ExtendType;
use TheCodingMachine\GraphQLite\Annotations\Field;
use TheCodingMachine\GraphQLite\Fixtures\Integration\Models\Company;
use TheCodingMachine\GraphQLite\Fixtures\Integration\Models\Contact;

/**
* @ExtendType(class=Company::class)
*/
class CompanyType
{
/**
* @Field()
*/
public function getName(Company $company): string
{
return $company->name;
}

/**
* @Field(prefetchMethod="prefetchContacts")
*/
public function getContact(Company $company, array $contacts): ?Contact
{
return $contacts[$company->name] ?? null;
}

public function prefetchContacts(array $companies): array
{
$contacts = [];

foreach ($companies as $company) {
$contacts[$company->name] = new Contact('Kate');
}

return $contacts;
}
}
49 changes: 49 additions & 0 deletions tests/Fixtures/Integration/Types/ContactType.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

namespace TheCodingMachine\GraphQLite\Fixtures\Integration\Types;

use TheCodingMachine\GraphQLite\Fixtures\Integration\Models\Post;
use function array_search;
use function strtoupper;
use TheCodingMachine\GraphQLite\Annotations\ExtendType;
Expand Down Expand Up @@ -52,4 +53,52 @@ public function prefetchContacts(iterable $contacts, string $prefix)
'prefix' => $prefix
];
}

/**
* @Field(prefetchMethod="prefetchPosts")
* @return Post[]|null
*/
public function getPosts($contact, $posts): ?array
{
return $posts[$contact->getName()] ?? null;
}

public function prefetchPosts(iterable $contacts): array
{
$posts = [];
foreach ($contacts as $contact) {
$contactPost = array_filter(
$this->getContactPosts(),
fn(Post $post) => $post->author?->getName() === $contact->getName()
);

if ([] === $contactPost) {
continue;
}

$posts[$contact->getName()] = $contactPost;
}

return $posts;
}

private function getContactPosts(): array
{
return [
$this->generatePost('First Joe post', '1', new Contact('Joe')),
$this->generatePost('First Bill post', '2', new Contact('Bill')),
$this->generatePost('First Kate post', '3', new Contact('Kate')),
];
}

private function generatePost(
string $title,
string $id,
Contact $author,
): Post {
$post = new Post($title);
$post->id = $id;
$post->author = $author;
return $post;
}
}
31 changes: 31 additions & 0 deletions tests/Fixtures/Integration/Types/PostType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace TheCodingMachine\GraphQLite\Fixtures\Integration\Types;

use TheCodingMachine\GraphQLite\Annotations\ExtendType;
use TheCodingMachine\GraphQLite\Annotations\Field;
use TheCodingMachine\GraphQLite\Fixtures\Integration\Models\Post;

/**
* @ExtendType(class=Post::class)
*/
class PostType
{
/**
* @Field()
*/
public function getId(Post $post): int
{
return (int) $post->id;
}

/**
* @Field()
*/
public function getTitle(Post $post): string
{
return $post->title;
}
}
Loading

0 comments on commit 2f517a1

Please sign in to comment.