Skip to content

Cache result of validation - #1730

Open
shmax wants to merge 50 commits into
webonyx:masterfrom
shmax:validation-cache
Open

Cache result of validation#1730
shmax wants to merge 50 commits into
webonyx:masterfrom
shmax:validation-cache

Conversation

@shmax

@shmax shmax commented Jul 11, 2025

Copy link
Copy Markdown
Contributor

Heyo, I remember there was some discussion about possibly disabling document validation checks a while back. I finally got around to looking into it and realized to my horror that it was indeed gobbling up a lot of resources in my app.

I don't know if you folks ever really agreed on a plan, but I thought I'd float this simple caching solution that leaves it up to the user.

Let me know what you think. If you like the general direction I can do a little polish and write better tests.

@shmax shmax changed the title cache result of validation Cache result of validation Jul 11, 2025
@shmax
shmax force-pushed the validation-cache branch from 2d38364 to a2c38f4 Compare July 11, 2025 03:55
@shmax
shmax force-pushed the validation-cache branch from 9ffbbd7 to 6782bad Compare July 11, 2025 04:07
Comment thread composer.json Outdated
@shmax
shmax force-pushed the validation-cache branch from e3bc588 to 6317931 Compare July 12, 2025 00:05
@shmax
shmax force-pushed the validation-cache branch from 1a59209 to 73c9774 Compare July 12, 2025 00:40
@shmax
shmax force-pushed the validation-cache branch from 5e59b9c to 6d7ace0 Compare July 12, 2025 00:41
@shmax
shmax force-pushed the validation-cache branch from 59f0ea0 to 708c85b Compare July 12, 2025 00:45
@shmax
shmax force-pushed the validation-cache branch from a12854e to f35cb3c Compare July 12, 2025 01:53
@shmax
shmax force-pushed the validation-cache branch from e6fa9a4 to d71e123 Compare July 12, 2025 04:00
Comment thread src/Validator/ValidationCache.php Outdated
Comment thread src/Validator/ValidationCache.php Outdated
Comment thread tests/Executor/ValidationWithCacheTest.php Outdated
Comment thread tests/Executor/ValidationWithCacheTest.php Outdated
Comment thread tests/Executor/ValidationWithCacheTest.php Outdated
Comment thread tests/Executor/ValidationWithCacheTest.php Outdated
Comment thread tests/PsrValidationCacheAdapter.php
Comment thread tests/PsrValidationCacheAdapter.php Outdated
Comment thread src/Validator/ValidationCache.php
@shmax
shmax force-pushed the validation-cache branch from d1f3597 to c2a82a4 Compare July 14, 2025 14:26
@shmax
shmax marked this pull request as ready for review July 14, 2025 17:32
@shmax

shmax commented Jul 24, 2025

Copy link
Copy Markdown
Contributor Author

Not sure I follow. I don't use Lighthouse, and I'm not familiar with it. What does it have to do with what I'm doing in this PR?

@spawnia

spawnia commented Jul 25, 2025

Copy link
Copy Markdown
Collaborator

Not sure I follow. I don't use Lighthouse, and I'm not familiar with it. What does it have to do with what I'm doing in this PR?

Lighthouse is built atop this library. It already implements a validation cache that has proven to be effective. I would like to take the lessons from the implementation effort we made there to inform the implementation in this library. This is to ensure that I can replace the custom implementation in Lighthouse with what we provide here, and we are not missing anything.

@shmax

shmax commented Jul 25, 2025

Copy link
Copy Markdown
Contributor Author

I see. Lighthouse implements its own high-level query flow, and uses DocumentValidator:validate directly. In the meantime, I modified my own code to just disable validation on production (I use persisted queries), so I'm not blocked.

I'll leave you to it.

spawnia added a commit to nuwave/lighthouse that referenced this pull request Dec 29, 2025
Implement the ValidationCache interface from webonyx/graphql-php#1730
to improve validation result caching with automatic cache invalidation.

Cache key now includes:
- Library versions (webonyx/graphql-php and nuwave/lighthouse)
- Schema hash
- Query hash
- Rule configuration hash (max_query_depth, disable_introspection)

This eliminates the need for manual cache clearing when upgrading
graphql-php or lighthouse, as the cache auto-invalidates on version
changes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
spawnia added a commit to nuwave/lighthouse that referenced this pull request Dec 29, 2025
Temporarily depend on the validation-cache branch from webonyx/graphql-php
to test the ValidationCache interface integration.

This will be updated to a proper version constraint once
webonyx/graphql-php#1730 is merged and released.

Note: This will require a major version bump for Lighthouse.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@shmax

shmax commented Dec 29, 2025

Copy link
Copy Markdown
Contributor Author

I see you're working on stuff around this, which is great. Holler if I can be of any help.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in mechanism to cache successful GraphQL document validation results, allowing repeated executions of the same query (against the same schema/rules) to skip the validation step for performance gains.

Changes:

  • Introduces GraphQL\Validator\ValidationCache and threads an optional cache instance through GraphQL::executeQuery(), GraphQL::promiseToExecute(), and DocumentValidator::validate().
  • Adds a PSR-16-based cache adapter and a spy adapter for testing, plus a new executor test asserting that validation is cached across repeated calls.
  • Documents the new validation caching feature and updates class reference signatures; adds dev dependencies used by the new test.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/Validator/ValidationCache.php Defines the new cache interface used to short-circuit successful validations.
src/Validator/DocumentValidator.php Implements validation short-circuiting and marking validated results.
src/GraphQL.php Adds optional cache parameter and passes it into validation.
src/Server/ServerConfig.php Adjusts PHPDoc types for validation rules.
tests/PsrValidationCacheAdapter.php Adds a PSR-16-based reference adapter used by tests/docs.
tests/Executor/TestClasses/SpyValidationCacheAdapter.php Adds spy wrapper to count cache method invocations.
tests/Executor/ValidationWithCacheTest.php Adds test verifying validation runs once across repeated executions when cache is provided.
docs/executing-queries.md Adds “Validation Caching” documentation and sample adapter implementation.
docs/class-reference.md Updates documented method signatures to include the new cache parameter.
composer.json Adds dev dependencies (psr/simple-cache, symfony/cache) used by the new test.
Comments suppressed due to low confidence (2)

src/Server/ServerConfig.php:131

  • The PHPDoc type for validation rules was widened to array|callable|null, but the established phpstan type alias still documents this as array<ValidationRule>|null|callable(...): array<ValidationRule> (see ServerConfig.php:31). Keeping the PHPDoc specific improves generated docs/IDE help and avoids implying arbitrary arrays are accepted.
    /**
     * @var array|callable|null
     *
     * @phpstan-var ValidationRulesOption
     */
    private $validationRules;

src/Server/ServerConfig.php:331

  • Same as the property PHPDoc above: the return PHPDoc was widened to array|callable|null, which loses the ValidationRule element type information that is documented elsewhere in this class (ServerConfig.php:31).
    /**
     * @return array|callable|null
     *
     * @phpstan-return ValidationRulesOption
     */
    public function getValidationRules()
    {
        return $this->validationRules;
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +105 to +109
if (isset($cache)
&& $cache->isValidated($schema, $ast, $rules)
) {
return [];
}
Comment on lines +134 to +138
if (isset($cache)
&& $errors === []
) {
$cache->markValidated($schema, $ast, $rules);
}
Comment on lines +39 to +44
public function isValidated(Schema $schema, DocumentNode $ast, ?array $rules = null): bool
{
$key = $this->buildKey($schema, $ast);

return $this->cache->has($key); // @phpstan-ignore missingType.checkedException (annotated as a union with Throwable)
}
Comment on lines +55 to +60
public function markValidated(Schema $schema, DocumentNode $ast, ?array $rules = null): void
{
$key = $this->buildKey($schema, $ast);

$this->cache->set($key, true, $this->ttlSeconds); // @phpstan-ignore missingType.checkedException (annotated as a union with Throwable)
}
Comment on lines +15 to +20
public function isValidated(Schema $schema, DocumentNode $ast, ?array $rules = null): bool
{
++$this->isValidatedCalls;

return parent::isValidated($schema, $ast);
}
Comment on lines +22 to +27
public function markValidated(Schema $schema, DocumentNode $ast, ?array $rules = null): void
{
++$this->markValidatedCalls;

parent::markValidated($schema, $ast);
}
Comment thread docs/executing-queries.md
Comment on lines +274 to +277
* Reference implementation of ValidationCache using PSR-16 cache.
*
* @see GraphQl\Tests\PsrValidationCacheAdapter
*/
Comment thread docs/executing-queries.md
Comment on lines +292 to +302
public function isValidated(Schema $schema, DocumentNode $ast, ?array $rules = null): bool
{
$key = $this->buildKey($schema, $ast);
return $this->cache->has($key);
}

public function markValidated(Schema $schema, DocumentNode $ast, ?array $rules = null): void
{
$key = $this->buildKey($schema, $ast);
$this->cache->set($key, true, $this->ttlSeconds);
}
@shmax

shmax commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

@spawnia are you still planning on merging this at some point? Did you want me to address all these copilot comments?

@spawnia

spawnia commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

I still see it as a useful feature addition, but I don't want to make promises that I will merge. I did a big mechanical sweep over all open PRs (merge master, fix mechanical issues, request automated reviews) to try and refine them to a point where my judgement is needed again.

@shmax

shmax commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

Well, let me know if you decide to move forward (I don't personally need it, as I'm halfway through migrating my entire codebase to Node.js, anyway).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Not ready to approve

There are correctness issues where cache keys and cache calls ignore or inconsistently handle the effective validation rules, which can make caching behave incorrectly.

Review details

Comments suppressed due to low confidence (9)

src/Validator/DocumentValidator.php:109

  • Cache lookup happens before default rules are resolved, and it uses the original $rules argument (possibly null). This makes cache keys inconsistent with the actual rules used for validation (allRules()), so a cache hit can be missed and later markValidated may store under a different key. Resolve $finalRules first (and short-circuit when empty) and pass $finalRules to the cache.
        if (isset($cache)
            && $cache->isValidated($schema, $ast, $rules)
        ) {
            return [];
        }

src/Validator/DocumentValidator.php:138

  • markValidated() is called with the original $rules argument instead of the resolved $finalRules actually used for validation, which can store under a different key than the lookup and undermine caching correctness.
        if (isset($cache)
            && $errors === []
        ) {
            $cache->markValidated($schema, $ast, $rules);
        }

tests/PsrValidationCacheAdapter.php:43

  • isValidated() computes the cache key without including the $rules argument, even though buildKey() accepts rules and includes them in the hash. This causes different validation rule sets to share the same key.
    public function isValidated(Schema $schema, DocumentNode $ast, ?array $rules = null): bool
    {
        $key = $this->buildKey($schema, $ast);

        return $this->cache->has($key); // @phpstan-ignore missingType.checkedException (annotated as a union with Throwable)

tests/PsrValidationCacheAdapter.php:60

  • markValidated() computes the cache key without including the $rules argument, so successful validations with different rule sets can overwrite/share the same cache entry.
    public function markValidated(Schema $schema, DocumentNode $ast, ?array $rules = null): void
    {
        $key = $this->buildKey($schema, $ast);

        $this->cache->set($key, true, $this->ttlSeconds); // @phpstan-ignore missingType.checkedException (annotated as a union with Throwable)
    }

tests/Executor/TestClasses/SpyValidationCacheAdapter.php:20

  • Spy adapter drops the $rules argument when delegating to the parent, so tests won’t reflect real behavior when custom validation rules are used (and it further hides key-generation bugs). Pass $rules through.
    public function isValidated(Schema $schema, DocumentNode $ast, ?array $rules = null): bool
    {
        ++$this->isValidatedCalls;

        return parent::isValidated($schema, $ast);
    }

tests/Executor/TestClasses/SpyValidationCacheAdapter.php:27

  • Spy adapter drops the $rules argument when delegating to the parent. Pass $rules through so the adapter observes the same cache-key inputs as production code.
    public function markValidated(Schema $schema, DocumentNode $ast, ?array $rules = null): void
    {
        ++$this->markValidatedCalls;

        parent::markValidated($schema, $ast);
    }

docs/executing-queries.md:277

  • The @see reference uses the wrong namespace casing (GraphQl vs GraphQL), so it doesn’t point to the actual test adapter class.
 * Reference implementation of ValidationCache using PSR-16 cache.
 *
 * @see GraphQl\Tests\PsrValidationCacheAdapter
 */

docs/executing-queries.md:296

  • In the sample implementation, isValidated()/markValidated() call buildKey() without passing $rules, so the example contradicts the guidance that keys should depend on the validation rule set.
    public function isValidated(Schema $schema, DocumentNode $ast, ?array $rules = null): bool
    {
        $key = $this->buildKey($schema, $ast);
        return $this->cache->has($key);
    }

composer.json:35

  • Dev dependencies pin psr/simple-cache to v1 only and symfony/cache to ^5.4 only. This can cause unnecessary dependency conflicts for contributors (PSR simple-cache has v2/v3 widely used; Symfony cache has compatible newer majors on newer PHP). Consider widening constraints while remaining compatible with supported PHP versions.
    "psr/simple-cache": "^1.0",
    "react/http": "^1.6",
    "react/promise": "^2.0 || ^3.0",
    "rector/rector": "^2.0",
    "symfony/cache": "^5.4",
  • Files reviewed: 10/10 changed files
  • Comments generated: 0 new
  • Review effort level: Low

Note

Your feedback helps us improve the quality of this feature.
Please use 👍 or 👎 to tell us whether this assessment is correct.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants