Skip to content

Commit 73c9774

Browse files
committed
files
1 parent 6317931 commit 73c9774

File tree

2 files changed

+175
-0
lines changed

2 files changed

+175
-0
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace GraphQL\Tests\Executor;
4+
5+
use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
6+
use GraphQL\Deferred;
7+
use GraphQL\GraphQL;
8+
use GraphQL\Tests\Executor\TestClasses\Cat;
9+
use GraphQL\Tests\Executor\TestClasses\Dog;
10+
use GraphQL\Tests\PsrValidationCacheAdapter;
11+
use GraphQL\Type\Definition\InterfaceType;
12+
use GraphQL\Type\Definition\ObjectType;
13+
use GraphQL\Type\Definition\Type;
14+
use GraphQL\Type\Schema;
15+
use PHPUnit\Framework\TestCase;
16+
use Symfony\Component\Cache\Adapter\ArrayAdapter;
17+
use Symfony\Component\Cache\Psr16Cache;
18+
19+
final class ValidationWithCacheTest extends TestCase
20+
{
21+
use ArraySubsetAsserts;
22+
23+
/** @see it('isTypeOf used to resolve runtime type for Interface') */
24+
public function testIsValidationCachedWithAdapter(): void
25+
{
26+
$cache = new PsrValidationCacheAdapter(new Psr16Cache(new ArrayAdapter()));
27+
28+
29+
30+
$petType = new InterfaceType([
31+
'name' => 'Pet',
32+
'fields' => [
33+
'name' => ['type' => Type::string()],
34+
],
35+
]);
36+
37+
$DogType = new ObjectType([
38+
'name' => 'Dog',
39+
'interfaces' => [$petType],
40+
'isTypeOf' => static fn ($obj): Deferred => new Deferred(static fn (): bool => $obj instanceof Dog),
41+
'fields' => [
42+
'name' => ['type' => Type::string()],
43+
'woofs' => ['type' => Type::boolean()],
44+
],
45+
]);
46+
47+
$CatType = new ObjectType([
48+
'name' => 'Cat',
49+
'interfaces' => [$petType],
50+
'isTypeOf' => static fn ($obj): Deferred => new Deferred(static fn (): bool => $obj instanceof Cat),
51+
'fields' => [
52+
'name' => ['type' => Type::string()],
53+
'meows' => ['type' => Type::boolean()],
54+
],
55+
]);
56+
57+
$schema = new Schema([
58+
'query' => new ObjectType([
59+
'name' => 'Query',
60+
'fields' => [
61+
'pets' => [
62+
'type' => Type::listOf($petType),
63+
'resolve' => static fn (): array => [
64+
new Dog('Odie', true),
65+
new Cat('Garfield', false),
66+
],
67+
],
68+
],
69+
]),
70+
'types' => [$CatType, $DogType],
71+
]);
72+
73+
$query = '{
74+
pets {
75+
name
76+
... on Dog {
77+
woofs
78+
}
79+
... on Cat {
80+
meows
81+
}
82+
}
83+
}';
84+
85+
GraphQL::executeQuery( $schema, $query, null, null, null, null, null, null, $cache)->toArray();
86+
87+
// TODO: use a spy or something to prove that the validation only happens once
88+
$result = GraphQL::executeQuery( $schema, $query, null, null, null, null, null, null, $cache)->toArray();
89+
90+
$expected = [
91+
'data' => [
92+
'pets' => [
93+
['name' => 'Odie', 'woofs' => true],
94+
['name' => 'Garfield', 'meows' => false],
95+
],
96+
],
97+
];
98+
99+
self::assertEquals($expected, $result);
100+
}
101+
}

tests/PsrValidationCacheAdapter.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace GraphQL\Tests;
4+
5+
use GraphQL\Language\AST\DocumentNode;
6+
use GraphQL\Type\Schema;
7+
use GraphQL\Utils\SchemaPrinter;
8+
use GraphQL\Validator\ValidationCache;
9+
use Psr\SimpleCache\CacheInterface;
10+
use Symfony\Component\String\Exception\InvalidArgumentException;
11+
12+
/**
13+
* @phpstan-type ErrorArray array{
14+
* message: string,
15+
* locations?: array<int, array{line: int, column: int}>
16+
* }
17+
*/
18+
final class PsrValidationCacheAdapter implements ValidationCache
19+
{
20+
private const KEY_PREFIX = 'gql_validation_';
21+
private int $ttl;
22+
private CacheInterface $cache;
23+
24+
public function __construct(
25+
CacheInterface $cache,
26+
int $ttlSeconds = 300
27+
) {
28+
$this->ttl = $ttlSeconds;
29+
$this->cache = $cache;
30+
}
31+
32+
/**
33+
* @throws InvalidArgumentException
34+
*/
35+
public function isValidated(Schema $schema, DocumentNode $ast): bool
36+
{
37+
try {
38+
$key = $this->buildKey($schema, $ast);
39+
/** @phpstan-ignore-next-line */
40+
return $this->cache->has($key);
41+
} catch (\Throwable $e) {
42+
return false;
43+
}
44+
}
45+
46+
/**
47+
* @throws InvalidArgumentException
48+
*/
49+
public function markValidated(Schema $schema, DocumentNode $ast): void
50+
{
51+
try {
52+
$key = $this->buildKey($schema, $ast);
53+
/** @phpstan-ignore-next-line */
54+
$this->cache->set($key, true, $this->ttl);
55+
} catch (\Throwable $e) {
56+
// ignore silently
57+
}
58+
}
59+
60+
/**
61+
* @throws \GraphQL\Error\Error
62+
* @throws \GraphQL\Error\InvariantViolation
63+
* @throws \GraphQL\Error\SerializationError
64+
* @throws \JsonException
65+
*/
66+
private function buildKey(Schema $schema, DocumentNode $ast): string
67+
{
68+
// NOTE: You can override this strategy if you want to make schema fingerprinting cheaper
69+
$schemaHash = md5(SchemaPrinter::doPrint($schema));
70+
$astHash = md5($ast->__toString());
71+
72+
return self::KEY_PREFIX . $schemaHash . '_' . $astHash;
73+
}
74+
}

0 commit comments

Comments
 (0)