Skip to content

Commit da1c67a

Browse files
authored
Merge pull request #383 from wpengine/feat-rule-excessive-fields
feat: new rule for excessive fields
2 parents 646e45e + a41c93d commit da1c67a

File tree

2 files changed

+143
-0
lines changed

2 files changed

+143
-0
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
<?php
2+
/**
3+
* GraphQL Rule to identify excessive field selection (over-fetching).
4+
*
5+
* @package WPGraphQL\Debug\Analysis\Rules
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace WPGraphQL\Debug\Analysis\Rules;
11+
12+
use GraphQL\Language\AST\FieldNode;
13+
use GraphQL\Language\Parser;
14+
use GraphQL\Language\Visitor;
15+
use GraphQL\Type\Schema;
16+
use GraphQL\Error\SyntaxError;
17+
use GraphQL\Utils\TypeInfo;
18+
use WPGraphQL\Debug\Analysis\Interfaces\AnalyzerItemInterface;
19+
20+
class ExcessiveFields implements AnalyzerItemInterface {
21+
22+
/**
23+
* @var string|null A descriptive note about the analysis result.
24+
*/
25+
protected ?string $internalNote = null;
26+
27+
/**
28+
* Default field threshold.
29+
*
30+
* This value determines how many fields may be requested
31+
* on a single type before the rule considers it "excessive."
32+
*
33+
* Developers may override this using the
34+
* `graphql_debug_excessive_fields_threshold` WordPress filter.
35+
*
36+
* @var int
37+
*/
38+
protected int $fieldThreshold = 15;
39+
40+
/**
41+
* Analyze a GraphQL query for excessive field selections.
42+
*
43+
* @param string $query The raw GraphQL query string.
44+
* @param array $variables Optional query variables.
45+
* @param Schema|null $schema The GraphQL schema (required for type resolution).
46+
*
47+
* @return array{
48+
* triggered: bool,
49+
* message: string
50+
* }
51+
*/
52+
public function analyze( string $query, array $variables = [], ?Schema $schema = null ): array {
53+
if ( null === $schema ) {
54+
$schema = $this->query_analyzer->get_schema();
55+
}
56+
if ( null === $schema ) {
57+
$this->internalNote = 'Excessive field selection analysis requires a GraphQL schema.';
58+
return [
59+
'triggered' => false,
60+
'message' => $this->internalNote,
61+
];
62+
}
63+
64+
$fieldCounts = [];
65+
66+
try {
67+
$ast = Parser::parse( $query );
68+
} catch (SyntaxError $error) {
69+
$this->internalNote = 'Excessive field selection analysis failed due to GraphQL syntax error: ' . $error->getMessage();
70+
error_log( 'WPGraphQL Debug Extensions: ' . $this->internalNote );
71+
return [
72+
'triggered' => false,
73+
'message' => $this->internalNote,
74+
];
75+
}
76+
77+
// Use TypeInfo to correctly resolve parent types during traversal
78+
$typeInfo = new TypeInfo( $schema );
79+
80+
Visitor::visit(
81+
$ast,
82+
Visitor::visitWithTypeInfo(
83+
$typeInfo,
84+
[
85+
'Field' => function (FieldNode $node) use (&$fieldCounts, $typeInfo) {
86+
$parentType = $typeInfo->getParentType();
87+
if ( $parentType ) {
88+
$typeName = $parentType->name;
89+
if ( ! isset( $fieldCounts[ $typeName ] ) ) {
90+
$fieldCounts[ $typeName ] = 0;
91+
}
92+
$fieldCounts[ $typeName ]++;
93+
}
94+
},
95+
]
96+
)
97+
);
98+
99+
$threshold = apply_filters(
100+
'graphql_debug_rule_excessive_fields_threshold',
101+
$this->fieldThreshold,
102+
$query,
103+
$variables,
104+
$schema
105+
);
106+
107+
$triggered = false;
108+
$details = [];
109+
foreach ( $fieldCounts as $type => $count ) {
110+
if ( $count > $threshold ) {
111+
$triggered = true;
112+
$details[] = sprintf(
113+
'Type "%s" selects %d fields, exceeding the threshold of %d.',
114+
$type,
115+
$count,
116+
$threshold
117+
);
118+
}
119+
}
120+
121+
$message = $triggered
122+
? 'Over-fetching detected: ' . implode( ' ', $details )
123+
: 'No excessive fields detected.';
124+
125+
$this->internalNote = $message;
126+
127+
return [
128+
'triggered' => $triggered,
129+
'message' => $message,
130+
];
131+
}
132+
133+
/**
134+
* Return the unique key for this analyzer rule.
135+
*
136+
* @return string
137+
*/
138+
public function getKey(): string {
139+
return 'excessiveFieldsRule';
140+
}
141+
}

plugins/wpgraphql-debug-extensions/src/Plugin.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
use AxeWP\GraphQL\Helper\Helper;
1313
use WPGraphQL\Debug\Analysis\QueryAnalyzer;
14+
use WPGraphQL\Debug\Analysis\Rules\ExcessiveFields;
1415
use WPGraphQL\Debug\Analysis\Rules\NestedQuery;
1516
use WPGraphQL\Utils\QueryAnalyzer as OriginalQueryAnalyzer;
1617
use WPGraphQL\Debug\Analysis\Rules\Complexity;
@@ -86,6 +87,7 @@ private function setup(): void {
8687
'complexity' => [ 'class' => Complexity::class, 'args' => [] ],
8788
'unfiltered_lists' => [ 'class' => UnfilteredLists::class, 'args' => [] ],
8889
'nested_query' => [ 'class' => NestedQuery::class, 'args' => [ ] ],
90+
'excessive_fields' => [ 'class' => ExcessiveFields::class, 'args' => [ ] ],
8991
];
9092
$analyzer_items = apply_filters( 'graphql_debug_extensions_analyzer_items', $analyzer_items );
9193

0 commit comments

Comments
 (0)