Repository: wp-graphql/wp-graphql (PHP)

Issue:
Eagerly Load Types should only be true for introspection requests
### What problem does this address?

When a type is defined as `eagerlyLoadType => true` there is a run-time performance cost for each query and this is typically only needed for Introspection queries. 

### What is your proposed solution?

We should consider "forcing" eagerlyLoadType to be false for non introspection queries and true for introspection. 

### What alternatives have you considered?

_No response_

### Additional Context

see: https://github.com/wpengine/wp-graphql-content-blocks/issues/134#issuecomment-1648475383



Fix (patch):
diff --git a/cli/wp-cli.php b/cli/wp-cli.php
index e2b9ade46..002e60c10 100644
--- a/cli/wp-cli.php
+++ b/cli/wp-cli.php
@@ -39,8 +39,16 @@ public function generate_static_schema( $args, $assoc_args ) {
 		 * Generate the Schema
 		 */
 		WP_CLI::line( 'Getting the Schema...' );
+
+		// Set the introspection query flag
+		WPGraphQL::set_is_introspection_query( true );
+
+		// Get the schema
 		$schema = WPGraphQL::get_schema();
 
+		// Reset the introspection query flag
+		WPGraphQL::set_is_introspection_query( false );
+
 		/**
 		 * Format the Schema
 		 */
diff --git a/src/Registry/TypeRegistry.php b/src/Registry/TypeRegistry.php
index 11ddfb838..2d8bc844c 100644
--- a/src/Registry/TypeRegistry.php
+++ b/src/Registry/TypeRegistry.php
@@ -4,6 +4,7 @@
 
 use GraphQL\Error\Error;
 use GraphQL\Type\Definition\Type;
+use WPGraphQL;
 use WPGraphQL\Data\DataSource;
 use WPGraphQL\Mutation\CommentCreate;
 use WPGraphQL\Mutation\CommentDelete;
@@ -407,10 +408,10 @@ public function init_type_registry( self $type_registry ) {
 		 *
 		 * @var \WP_Post_Type[] $allowed_post_types
 		 */
-		$allowed_post_types = \WPGraphQL::get_allowed_post_types( 'objects' );
+		$allowed_post_types = WPGraphQL::get_allowed_post_types( 'objects' );
 
 		/** @var \WP_Taxonomy[] $allowed_taxonomies */
-		$allowed_taxonomies = \WPGraphQL::get_allowed_taxonomies( 'objects' );
+		$allowed_taxonomies = WPGraphQL::get_allowed_taxonomies( 'objects' );
 
 		foreach ( $allowed_post_types as $post_type_object ) {
 			PostObject::register_types( $post_type_object );
@@ -714,7 +715,7 @@ public function register_type( string $type_name, $config ): void {
 			return $this->prepare_type( $type_name, $config );
 		};
 
-		if ( is_array( $config ) && isset( $config['eagerlyLoadType'] ) && true === $config['eagerlyLoadType'] && ! isset( $this->eager_type_map[ $this->format_key( $type_name ) ] ) ) {
+		if ( WPGraphQL::is_introspection_query() && is_array( $config ) && isset( $config['eagerlyLoadType'] ) && true === $config['eagerlyLoadType'] && ! isset( $this->eager_type_map[ $this->format_key( $type_name ) ] ) ) {
 			$this->eager_type_map[ $this->format_key( $type_name ) ] = $this->format_key( $type_name );
 		}
 	}
diff --git a/src/Request.php b/src/Request.php
index e2a4a5e54..699c2a91c 100644
--- a/src/Request.php
+++ b/src/Request.php
@@ -151,9 +151,6 @@ public function __construct( array $data = [] ) {
 		// Get the Type Registry
 		$this->type_registry = \WPGraphQL::get_type_registry();
 
-		// Get the Schema
-		$this->schema = \WPGraphQL::get_schema();
-
 		// Get the App Context
 		$this->app_context = \WPGraphQL::get_app_context();
 
@@ -292,6 +289,9 @@ private function before_execute(): void {
 			$this->do_action( $this->params );
 		}
 
+		// Get the Schema
+		$this->schema = \WPGraphQL::get_schema();
+
 		/**
 		 * This action runs before execution of a GraphQL request (regardless if it's a single or batch request)
 		 *
diff --git a/src/Utils/QueryAnalyzer.php b/src/Utils/QueryAnalyzer.php
index 94c032328..18b148071 100644
--- a/src/Utils/QueryAnalyzer.php
+++ b/src/Utils/QueryAnalyzer.php
@@ -32,7 +32,7 @@
 class QueryAnalyzer {
 
 	/**
-	 * @var \GraphQL\Type\Schema
+	 * @var \WPGraphQL\WPSchema|null
 	 */
 	protected $schema;
 
@@ -107,6 +107,17 @@ class QueryAnalyzer {
 	 */
 	protected $is_enabled_for_query;
 
+	/**
+	 * @param \WPGraphQL\Request $request The GraphQL request being executed
+	 */
+	public function __construct( Request $request ) {
+		$this->request       = $request;
+		$this->runtime_nodes = [];
+		$this->models        = [];
+		$this->list_types    = [];
+		$this->queried_types = [];
+	}
+
 	/**
 	 * Checks whether the Query Analyzer is enabled on the site.
 	 *
@@ -133,15 +144,17 @@ public static function is_enabled(): bool {
 	}
 
 	/**
-	 * @param \WPGraphQL\Request $request The GraphQL request being executed
+	 * Get the GraphQL Schema.
+	 * If the schema is not set, it will be set.
+	 *
+	 * @throws \Exception
 	 */
-	public function __construct( Request $request ) {
-		$this->request       = $request;
-		$this->schema        = $request->schema;
-		$this->runtime_nodes = [];
-		$this->models        = [];
-		$this->list_types    = [];
-		$this->queried_types = [];
+	public function get_schema(): ?WPSchema {
+		if ( ! $this->schema ) {
+			$this->schema = WPGraphQL::get_schema();
+		}
+
+		return $this->schema;
 	}
 
 	/**
@@ -256,9 +269,9 @@ public function determine_graphql_keys( ?string $query, ?string $operation, ?arr
 
 		// if there's a query (either saved or part of the request params)
 		// get the GraphQL Types being asked for by the query
-		$this->list_types    = $this->set_list_types( $this->schema, $query );
-		$this->queried_types = $this->set_query_types( $this->schema, $query );
-		$this->models        = $this->set_query_models( $this->schema, $query );
+		$this->list_types    = $this->set_list_types( $this->get_schema(), $query );
+		$this->queried_types = $this->set_query_types( $this->get_schema(), $query );
+		$this->models        = $this->set_query_models( $this->get_schema(), $query );
 
 		/**
 		 * @param \WPGraphQL\Utils\QueryAnalyzer $query_analyzer The instance of the query analyzer
@@ -352,7 +365,7 @@ public static function get_wrapped_field_type( Type $type, FieldDefinition $fiel
 		}
 
 		if ( $type instanceof NonNull || $type instanceof ListOfType ) {
-			if ( $type instanceof ListOfType && isset( $parent_type->name ) ) {
+			if ( $type instanceof ListOfType && ! empty( $parent_type->name ) ) {
 				$is_list_type = true;
 			}
 
diff --git a/src/WPGraphQL.php b/src/WPGraphQL.php
index 96eee7711..4cc10eef3 100644
--- a/src/WPGraphQL.php
+++ b/src/WPGraphQL.php
@@ -62,7 +62,12 @@ final class WPGraphQL {
 	/**
 	 * @var bool
 	 */
-	protected static $is_graphql_request;
+	protected static $is_graphql_request = false;
+
+	/**
+	 * @var bool
+	 */
+	protected static $is_introspection_query = false;
 
 	/**
 	 * The instance of the WPGraphQL object
@@ -140,12 +145,32 @@ public static function set_is_graphql_request( $is_graphql_request = false ) {
 	}
 
 	/**
-	 * @return bool
+	 * Whether the request is a graphql request or not
 	 */
-	public static function is_graphql_request() {
+	public static function is_graphql_request(): bool {
 		return self::$is_graphql_request;
 	}
 
+	/**
+	 * Set whether the request is an introspection query or not
+	 *
+	 * @param bool $is_introspection_query
+	 *
+	 * @since todo
+	 */
+	public static function set_is_introspection_query( bool $is_introspection_query = false ): void {
+		self::$is_introspection_query = $is_introspection_query;
+	}
+
+	/**
+	 * Whether the request is an introspection query or not (query for __type or __schema)
+	 *
+	 * @since todo
+	 */
+	public static function is_introspection_query(): bool {
+		return self::$is_introspection_query;
+	}
+
 	/**
 	 * Sets up actions to run at certain spots throughout WordPress and the WPGraphQL execution
 	 * cycle
@@ -203,6 +228,7 @@ static function () {
 
 		// Throw an exception
 		add_action( 'do_graphql_request', [ $this, 'min_php_version_check' ] );
+		add_action( 'do_graphql_request', [ $this, 'introspection_check' ], 10, 4 );
 
 		// Initialize Admin functionality
 		add_action( 'after_setup_theme', [ $this, 'init_admin' ] );
@@ -219,6 +245,41 @@ static function () {
 		);
 	}
 
+	/**
+	 * @param ?string                         $query     The GraphQL query
+	 * @param ?string                         $operation The name of the operation
+	 * @param ?array<mixed>                   $variables Variables to be passed to your GraphQL
+	 *                                                   request
+	 * @param \GraphQL\Server\OperationParams $params    The Operation Params. This includes any
+	 *                                                   extra params,
+	 *
+	 * @throws \GraphQL\Error\SyntaxError
+	 * @throws \Exception
+	 */
+	public function introspection_check( ?string $query, ?string $operation, ?array $variables, \GraphQL\Server\OperationParams $params ): void {
+
+		if ( empty( $query ) ) {
+			return;
+		}
+
+		$ast              = \GraphQL\Language\Parser::parse( $query );
+		$is_introspection = false;
+
+		\GraphQL\Language\Visitor::visit(
+			$ast,
+			[
+				'Field' => static function ( \GraphQL\Language\AST\FieldNode $node ) use ( &$is_introspection ) {
+					if ( '__schema' === $node->name->value || '__type' === $node->name->value ) {
+						$is_introspection = true;
+						return \GraphQL\Language\Visitor::stop();
+					}
+				},
+			]
+		);
+
+		self::set_is_introspection_query( $is_introspection );
+	}
+
 	/**
 	 * Check if the minimum PHP version requirement is met before execution begins.
 	 *
@@ -233,7 +294,7 @@ public function min_php_version_check() {
 			throw new \Exception(
 				esc_html(
 					sprintf(
-						// translators: %1$s is the current PHP version, %2$s is the minimum required PHP version.
+					// translators: %1$s is the current PHP version, %2$s is the minimum required PHP version.
 						__( 'The server\'s current PHP version %1$s is lower than the WPGraphQL minimum required version: %2$s', 'wp-graphql' ),
 						PHP_VERSION,
 						GRAPHQL_MIN_PHP_VERSION