Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php
/**
* Interface for a debug extension analyzer item (metric or rule).
*
* @package WPGraphQL\Debug\Analysis\Interfaces
*/

declare(strict_types=1);

namespace WPGraphQL\Debug\Analysis\Interfaces;

use GraphQL\Type\Schema;

interface AnalyzerItemInterface {
/**
* Executes the analysis (calculates metric or evaluates rule).
*
* @param string $query The GraphQL query string.
* @param array<string,mixed> $variables Optional: Variables provided with the query.
* @param Schema|null $schema Optional: The GraphQL schema.
* @return array<string,mixed> An associative array representing the analysis result.
* For metrics, it might contain 'value' and 'note'.
* For rules, it might contain 'triggered' and 'message'.
*/
public function analyze( string $query, array $variables = [], ?Schema $schema = null ): array;

/**
* Returns the key under which this item's result should appear in the 'debugExtensions' output.
* E.g., 'complexity', 'nestedQueryRule', 'excessiveFieldsRule'.
*
* @return string The unique key for the analyzer item.
*/
public function getKey(): string;
}

This file was deleted.

158 changes: 86 additions & 72 deletions plugins/wpgraphql-debug-extensions/src/Analysis/QueryAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,99 +2,113 @@
/**
* Extends WPGraphQL's Query Analyzer to add custom heuristic rules and metrics.
*
* @package WPGraphQL\Debug
* @package WPGraphQL\Debug\Analysis
*/

declare(strict_types=1);

namespace WPGraphQL\Debug\Analysis;

use WPGraphQL\Debug\Analysis\Metrics\Complexity;
use WPGraphQL\Debug\Analysis\Interfaces\AnalyzerItemInterface;
use WPGraphQL\Utils\QueryAnalyzer as OriginalQueryAnalyzer;

/**
* Class QueryAnalyzerExtension
* Class QueryAnalyzer
*
* This class hooks into the WPGraphQL Query Analyzer to add custom analysis.
*/
class QueryAnalyzer {

/**
* @var QueryAnalyzer The instance of the WPGraphQL Query Analyzer.
*/
protected OriginalQueryAnalyzer $query_analyzer;
/**
* @var OriginalQueryAnalyzer The instance of the WPGraphQL Query Analyzer from the core plugin.
*/
protected OriginalQueryAnalyzer $query_analyzer;

/**
* @var string|null The GraphQL query string for the current request.
*/
protected ?string $currentQuery = null;
/**
* @var AnalyzerItemInterface[] An array of registered analyzer items (metrics and rules).
*/
protected array $analyzerItems = [];

/**
* @var array<string,mixed> The variables for the current GraphQL request.
*/
protected array $currentVariables = [];
/**
* Constructor for the QueryAnalyzerExtension.
*
* @param OriginalQueryAnalyzer $query_analyzer The instance of the WPGraphQL Query Analyzer.
*/
public function __construct( OriginalQueryAnalyzer $query_analyzer ) {
$this->query_analyzer = $query_analyzer;
}

/**
* Constructor for the QueryAnalyzerExtension.
*
* @param OriginalQueryAnalyzer $query_analyzer The instance of the WPGraphQL Query Analyzer.
*/
public function __construct( OriginalQueryAnalyzer $query_analyzer ) {
$this->query_analyzer = $query_analyzer;
}
/**
* Adds an AnalyzerItem (metric or rule) to be processed.
*
* @param AnalyzerItemInterface $item The item to add.
* @return void
*/
public function addAnalyzerItem( AnalyzerItemInterface $item ): void {
$this->analyzerItems[] = $item;
}

/**
* Initializes the extension by adding necessary WordPress hooks.
*/
public function init(): void {
add_filter( 'graphql_query_analyzer_graphql_keys', [ $this, 'addMetricsToAnalyzerOutput' ], 10, 5 );
}
/**
* Initializes the extension by adding necessary WordPress hooks.
*/
public function init(): void {
// This filter allows us to inject custom data into the 'debugExtensions' part of the GraphQL response.
add_filter( 'graphql_query_analyzer_graphql_keys', [ $this, 'addAnalysisToOutput' ], 10, 5 );
}

/**
* Adds new metrics and analysis results to the Query Analyzer's output.
* This method is a callback for the 'graphql_query_analyzer_graphql_keys' filter.
*
* @param array<string,mixed> $graphql_keys Existing data from the Query Analyzer.
* @param string $return_keys The keys returned to the X-GraphQL-Keys header.
* @param string $skipped_keys The keys that were skipped.
* @param string[] $return_keys_array The keys returned in array format.
* @param string[] $skipped_keys_array The keys skipped in array format.
* @return array<string,mixed> The modified GraphQL keys with custom metrics.
*/
public function addMetricsToAnalyzerOutput(
array $graphql_keys,
string $return_keys,
string $skipped_keys,
array $return_keys_array,
array $skipped_keys_array
): array {
$complexityValue = null;
$complexityNote = 'Could not compute complexity';
/**
* Adds new metrics and analysis results to the Query Analyzer's output.
* This method is a callback for the 'graphql_query_analyzer_graphql_keys' filter.
*
* @param array<string,mixed> $graphql_keys Existing data from the Query Analyzer.
* @param string $return_keys The keys returned to the X-GraphQL-Keys header. (unused here)
* @param string $skipped_keys The keys that were skipped. (unused here)
* @param string[] $return_keys_array The keys returned in array format. (unused here)
* @param string[] $skipped_keys_array The keys skipped in array format. (unused here)
* @return array<string,mixed> The modified GraphQL keys with custom metrics.
*/
public function addAnalysisToOutput(
array $graphql_keys,
string $return_keys, // Keep for filter signature, but not used.
string $skipped_keys, // Keep for filter signature, but not used.
array $return_keys_array, // Keep for filter signature, but not used.
array $skipped_keys_array // Keep for filter signature, but not used.
): array {
if ( ! isset( $graphql_keys['debugExtensions'] ) ) {
$graphql_keys['debugExtensions'] = [];
}

$request = $this->query_analyzer->get_request();
$currentQuery = $request->params->query ?? null;
$currentVariables = (array) ( $request->params->variables ?? [] );
$request = $this->query_analyzer->get_request();
$currentQuery = $request->params->query ?? null;
$currentVariables = (array) ( $request->params->variables ?? [] );
$schema = $this->query_analyzer->get_schema();

// Add some logging to debug.
error_log( 'QueryAnalyzerExtension: addCustomMetricsToAnalyzerOutput called.' );
error_log( 'QueryAnalyzerExtension: Retrieved Query: ' . ( $currentQuery ?? 'NULL' ) );
error_log( 'QueryAnalyzerExtension: Retrieved Variables: ' . print_r( $currentVariables, true ) );
if ( ! empty( $currentQuery ) ) {
try {
$complexityMetrics = new Complexity();
$schema = $this->query_analyzer->get_schema();
$complexityValue = $complexityMetrics->calculate( $currentQuery, $currentVariables, $schema );
foreach ( $this->analyzerItems as $item ) {
try {
if ( ! empty( $currentQuery ) ) {
$result = $item->analyze( $currentQuery, $currentVariables, $schema );
} else {
$result = [
'value' => null,
'note' => 'No query provided for analysis.',
];
}
} catch ( \Exception $e ) {
error_log( sprintf(
'WPGraphQL Debug Extensions: Analysis item "%s" failed: %s',
$item->getKey(),
$e->getMessage()
) );
$result = [
'value' => null,
'note' => 'Analysis failed: ' . $e->getMessage(),
'error' => true,
];
}

} catch (\Exception $e) {
error_log( 'WPGraphQL Debug Extensions: Complexity calculation failed: ' . $e->getMessage() );
$complexityNote .= ': ' . $e->getMessage();
}
}
if ( ! isset( $graphql_keys['debugExtensions'] ) ) {
$graphql_keys['debugExtensions'] = [];
}
$graphql_keys['debugExtensions']['complexity'] = $complexityValue;
$graphql_keys['debugExtensions'][ $item->getKey() ] = $result;
}

return $graphql_keys;
}
return $graphql_keys;
}
}
Loading