Skip to content

Commit c91420c

Browse files
committed
Merge branch 'validation-cache' of github.com:shmax/graphql-php into validation-cache
2 parents e8fedcc + 7a2b3dc commit c91420c

2 files changed

Lines changed: 61 additions & 23 deletions

File tree

docs/executing-queries.md

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -210,14 +210,15 @@ $server = new StandardServer([
210210
'validationRules' => $myValidationRules
211211
]);
212212
```
213+
213214
## Validation Caching
214215

215-
Validation is a required step in GraphQL execution, but it can become a performance bottleneck when the same queries are
216-
run repeatedly — especially in production environments where queries are often static or pre-generated (e.g., persisted
217-
queries or queries emitted by client libraries).
216+
Validation is a required step in GraphQL execution, but it can become a performance bottleneck.
217+
In production environments, queries are often static or pre-generated (e.g. persisted queries or queries emitted by client libraries).
218+
This means that many queries will be identical and their validation results can be reused.
218219

219-
To optimize for this, `graphql-php` supports pluggable validation caching. By implementing the `GraphQL\Validator\ValidationCache` interface and passing it to
220-
`GraphQL::executeQuery()`, you can skip validation for queries that are already known to be valid.
220+
To optimize for this, `graphql-php` allows skipping validation for known valid queries.
221+
Leverage pluggable validation caching by passing an implementation of the `GraphQL\Validator\ValidationCache` interface to `GraphQL::executeQuery()`:
221222

222223
```php
223224
use GraphQL\Validator\ValidationCache;
@@ -239,15 +240,26 @@ $result = GraphQL::executeQuery(
239240
```
240241

241242
### Key Generation Tips
242-
You are responsible for generating your own cache keys in a way that uniquely identifies the schema, the query, and
243-
(optionally) any custom validation rules. Here are some tips:
244243

245-
* Hash your schema once at build time and store the result in an environment variable or constant.
246-
* Avoid using serialize() on schema objects — closures and internal references may cause errors.
247-
* If using custom validation rules, be sure to account for them in your key (e.g., by serializing or listing their class names).
248-
* Consider including the graphql-php version number to account for internal rule changes across versions.
244+
You are responsible for generating cache keys that are unique and dependent on the following inputs:
245+
246+
- the client-given query
247+
- the current schema
248+
- the passed validation rules and their implementation
249+
- the implementation of `graphql-php`
250+
251+
Here are some tips:
252+
253+
- Using `serialize()` directly on the schema object may error due to closures or circular references.
254+
Instead, use `GraphQL\Utils\SchemaPrinter::doPrint($schema)` to get a stable string representation of the schema.
255+
- If using custom validation rules, be sure to account for them in your key (e.g., by serializing or listing their class names and versioning them).
256+
- Include the version number of the `webonyx/graphql-php` package to account for implementation changes in the library.
257+
- Use a stable hash function like `md5()` or `sha256()` to generate the key from the schema, AST, and rules.
258+
- Improve performance even further by hashing inputs known before deploying such as the schema or the installed package version.
259+
You may store the hash in an environment variable or a constant to avoid recalculating it on every request.
249260

250261
### Sample Implementation
262+
251263
```php
252264
use GraphQL\Validator\ValidationCache;
253265
use GraphQL\Language\AST\DocumentNode;
@@ -258,7 +270,7 @@ use Composer\InstalledVersions;
258270

259271
/**
260272
* Reference implementation of ValidationCache using PSR-16 cache.
261-
*
273+
*
262274
* @see GraphQl\Tests\PsrValidationCacheAdapter
263275
*/
264276
class MyPsrValidationCacheAdapter implements ValidationCache
@@ -289,18 +301,44 @@ class MyPsrValidationCacheAdapter implements ValidationCache
289301

290302
private function buildKey(Schema $schema, DocumentNode $ast, ?array $rules = null): string
291303
{
292-
// Use a stable hash for schema. In production, prefer a build-time constant:
293-
// $schemaHash = $_ENV['SCHEMA_VERSION'] ?? 'v1';
304+
// Include package version to account for implementation changes
305+
$libraryVersion = \Composer\InstalledVersions::getVersion('webonyx/graphql-php')
306+
?? throw new \RuntimeException('webonyx/graphql-php version not found. Ensure the package is installed.');
307+
308+
// Use a stable hash for the schema
294309
$schemaHash = md5(SchemaPrinter::doPrint($schema));
295310

296311
// Serialize AST and rules — both are predictable and safe in this context
297312
$astHash = md5(serialize($ast));
298313
$rulesHash = md5(serialize($rules));
299314

300-
// Include graphql-php version to account for internal changes
301-
$libraryVersion = \Composer\InstalledVersions::getVersion('webonyx/graphql-php') ?: 'unknown';
302-
303315
return "graphql_validation_{$libraryVersion}_{$schemaHash}_{$astHash}_{$rulesHash}";
304316
}
305317
}
306-
```
318+
```
319+
320+
An optimized version of `buildKey` might leverage a key prefix for inputs known before deployment.
321+
For example, you may run the following once during deployment and save the output in an environment variable `GRAPHQL_VALIDATION_KEY_PREFIX`:
322+
323+
```php
324+
$libraryVersion = \Composer\InstalledVersions::getVersion('webonyx/graphql-php')
325+
?? throw new \RuntimeException('webonyx/graphql-php version not found. Ensure the package is installed.');
326+
327+
$schemaHash = md5(SchemaPrinter::doPrint($schema));
328+
329+
echo "{$libraryVersion}_{$schemaHash}";
330+
```
331+
332+
Then use the environment variable in your key generation:
333+
334+
```php
335+
private function buildKey(Schema $schema, DocumentNode $ast, ?array $rules = null): string
336+
{
337+
$keyPrefix = getenv('GRAPHQL_VALIDATION_KEY_PREFIX')
338+
?? throw new \RuntimeException('Environment variable GRAPHQL_VALIDATION_KEY_PREFIX is not set.');
339+
$astHash = md5(serialize($ast));
340+
$rulesHash = md5(serialize($rules));
341+
342+
return "graphql_validation_{$keyPrefix}_{$astHash}_{$rulesHash}";
343+
}
344+
```

src/Validator/ValidationCache.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@
1414
* You are responsible for defining how cache keys are computed.
1515
*
1616
* Some things to keep in mind when generating keys:
17-
* - PHP's `serialize` method is fast, but can't handle certain structures such as closures.
18-
* - If your `schema` includes closures or is too large or complex to serialize,
17+
* - PHP's `serialize()` function is fast, but can't handle certain structures such as closures.
18+
* - If your `$schema` includes closures or is too large or complex to serialize,
1919
* consider using a build-time version number or environment-based fingerprint instead.
20-
* - Keep in mind that there are internal `rules` that are applied in addition to any you pass in,
21-
* and it's possible these may shift or expand as the library evolves, so it might make sense
22-
* to include the library version number in your keys.
20+
* - Keep in mind that there are internal `$rules` that are applied in addition to any you pass in,
21+
* and it's possible these may shift or expand as the library evolves,
22+
* so it might make sense to include the library version number in your keys.
2323
*
2424
* @see PsrValidationCacheAdapter for a simple reference implementation.
2525
*/

0 commit comments

Comments
 (0)