Skip to content

Commit

Permalink
Fix prefetching the same GQL type for batch request (#658)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
grynchuk authored Mar 11, 2024
1 parent 758d02d commit 256da31
Show file tree
Hide file tree
Showing 10 changed files with 290 additions and 25 deletions.
10 changes: 5 additions & 5 deletions src/Parameters/PrefetchDataParameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public function resolve(object|null $source, array $args, mixed $context, Resolv
}

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

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

$prefetchBuffer->storeResult($prefetchResult, $args);
$prefetchBuffer->storeResult($prefetchResult, $args, $info);
}

return $prefetchResult ?? $prefetchBuffer->getResult($args);
return $prefetchResult ?? $prefetchBuffer->getResult($args, $info);
});
}

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

return ($this->resolver)($sources, ...$toPassPrefetchArgs);
Expand Down
66 changes: 46 additions & 20 deletions src/PrefetchBuffer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace TheCodingMachine\GraphQLite;

use GraphQL\Type\Definition\ResolveInfo;

use function array_key_exists;
use function md5;
use function serialize;
Expand All @@ -20,14 +22,27 @@ class PrefetchBuffer
private array $results = [];

/** @param array<array-key, 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|null $info = null,
): void {
$this->objects[$this->computeHash($arguments, $info)][] = $object;
}

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

return md5(serialize($arguments));
}

Expand All @@ -36,32 +51,43 @@ private function computeHash(array $arguments): string
*
* @return array<int, object>
*/
public function getObjectsByArguments(array $arguments): array
{
return $this->objects[$this->computeHash($arguments)] ?? [];
public function getObjectsByArguments(
array $arguments,
ResolveInfo|null $info = null,
): array {
return $this->objects[$this->computeHash($arguments, $info)] ?? [];
}

/** @param array<array-key, 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|null $info = null,
): void {
unset($this->objects[$this->computeHash($arguments, $info)]);
}

/** @param array<array-key, 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|null $info = null,
): void {
$this->results[$this->computeHash($arguments, $info)] = $result;
}

/** @param array<array-key, 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|null $info = null,
): bool {
return array_key_exists($this->computeHash($arguments, $info), $this->results);
}

/** @param array<array-key, 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|null $info = null,
): mixed {
return $this->results[$this->computeHash($arguments, $info)];
}
}
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');
}
}
10 changes: 10 additions & 0 deletions tests/Fixtures/Integration/Controllers/ContactController.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ 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]
public function saveContact(Contact $contact): Contact
{
Expand Down
16 changes: 16 additions & 0 deletions tests/Fixtures/Integration/Models/Company.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?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
) {
}
}
42 changes: 42 additions & 0 deletions tests/Fixtures/Integration/Types/CompanyType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace TheCodingMachine\GraphQLite\Fixtures\Integration\Types;

use TheCodingMachine\GraphQLite\Annotations\ExtendType;
use TheCodingMachine\GraphQLite\Annotations\Field;
use TheCodingMachine\GraphQLite\Annotations\Prefetch;
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]
public function getContact(
Company $company,
#[Prefetch('prefetchContacts')]
array $contacts
): ?Contact {
return $contacts[$company->name] ?? null;
}

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

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

return $contacts;
}
}
53 changes: 53 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 TheCodingMachine\GraphQLite\Annotations\Prefetch;
use function array_search;
use function strtoupper;
Expand Down Expand Up @@ -53,4 +54,56 @@ public static function prefetchContacts(iterable $contacts, string $prefix)
'prefix' => $prefix
];
}

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

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

if (!$contactPost) {
continue;
}

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

return $posts;
}

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

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

Please sign in to comment.