Commit e415135cc9f for woocommerce

commit e415135cc9f6d04520a25c4110d0398748b28c5a
Author: Alba Rincón <albarin@users.noreply.github.com>
Date:   Thu Apr 30 16:20:42 2026 +0200

    Add configurable parsed query cache TTL to GraphQL settings (#64488)

    * Add configurable parsed query cache TTL to GraphQL settings

    * Add changefile(s) from automation for the following project(s): woocommerce

    * Reorder GraphQL settings page fields by purpose

    ---------

    Co-authored-by: woocommercebot <woocommercebot@users.noreply.github.com>

diff --git a/plugins/woocommerce/changelog/64488-64301-graphql-settings-cache-ttl b/plugins/woocommerce/changelog/64488-64301-graphql-settings-cache-ttl
new file mode 100644
index 00000000000..cda42ccecfa
--- /dev/null
+++ b/plugins/woocommerce/changelog/64488-64301-graphql-settings-cache-ttl
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add configurable parsed query cache TTL to GraphQL settings
\ No newline at end of file
diff --git a/plugins/woocommerce/src/Internal/Api/Main.php b/plugins/woocommerce/src/Internal/Api/Main.php
index ed369553577..92303abdbd5 100644
--- a/plugins/woocommerce/src/Internal/Api/Main.php
+++ b/plugins/woocommerce/src/Internal/Api/Main.php
@@ -62,6 +62,14 @@ class Main {
 	 */
 	public const OPTION_OBJECT_CACHE_ENABLED = 'woocommerce_graphql_object_cache_enabled';

+	/**
+	 * Option name for the "Parsed query cache TTL" setting.
+	 *
+	 * Time-to-live (in seconds) applied to parsed-query AST entries written
+	 * to the WP object cache by both the standard-query and APQ paths.
+	 */
+	public const OPTION_QUERY_CACHE_TTL = 'woocommerce_graphql_query_cache_ttl';
+
 	/**
 	 * Check whether the Dual Code & GraphQL API feature is active.
 	 *
diff --git a/plugins/woocommerce/src/Internal/Api/QueryCache.php b/plugins/woocommerce/src/Internal/Api/QueryCache.php
index 99bac689c7c..f92c8fd22df 100644
--- a/plugins/woocommerce/src/Internal/Api/QueryCache.php
+++ b/plugins/woocommerce/src/Internal/Api/QueryCache.php
@@ -27,17 +27,22 @@ class QueryCache {
 	private const CACHE_KEY_PREFIX = 'graphql_ast_v15_';

 	/**
-	 * Time-to-live (in seconds) for a cached parsed query.
+	 * Default time-to-live (in seconds) applied when the option is unset or non-positive.
 	 *
 	 * See {@see self::get_cache_ttl()} for the accessor.
 	 */
-	private const CACHE_TTL = DAY_IN_SECONDS;
+	public const DEFAULT_CACHE_TTL = DAY_IN_SECONDS;

 	/**
 	 * The time-to-live (in seconds) for a cached parsed query.
+	 *
+	 * Reads the {@see Main::OPTION_QUERY_CACHE_TTL} store option; falls back
+	 * to {@see self::DEFAULT_CACHE_TTL} when the option is unset, empty, or
+	 * non-positive.
 	 */
 	public static function get_cache_ttl(): int {
-		return self::CACHE_TTL;
+		$value = (int) get_option( Main::OPTION_QUERY_CACHE_TTL, self::DEFAULT_CACHE_TTL );
+		return $value > 0 ? $value : self::DEFAULT_CACHE_TTL;
 	}

 	/**
diff --git a/plugins/woocommerce/src/Internal/Api/Settings.php b/plugins/woocommerce/src/Internal/Api/Settings.php
index 42dae14bfc8..812ad644b9e 100644
--- a/plugins/woocommerce/src/Internal/Api/Settings.php
+++ b/plugins/woocommerce/src/Internal/Api/Settings.php
@@ -63,6 +63,14 @@ class Settings {
 				'type'  => 'title',
 				'id'    => 'woocommerce_graphql_options',
 			),
+			array(
+				'title'    => __( 'Endpoint URL', 'woocommerce' ),
+				'desc'     => __( 'Path relative to /wp-json/ where the GraphQL endpoint is exposed. Needs at least two segments (namespace/route), e.g. wc/graphql.', 'woocommerce' ),
+				'desc_tip' => true,
+				'id'       => Main::OPTION_ENDPOINT_URL,
+				'default'  => GraphQLController::DEFAULT_ENDPOINT_URL,
+				'type'     => 'text',
+			),
 			array(
 				'title'   => __( 'Enable GET endpoint', 'woocommerce' ),
 				'desc'    => __( 'Allow GraphQL queries over GET in addition to POST', 'woocommerce' ),
@@ -70,13 +78,6 @@ class Settings {
 				'default' => 'yes',
 				'type'    => 'checkbox',
 			),
-			array(
-				'title'   => __( 'Enable APQ caching', 'woocommerce' ),
-				'desc'    => __( 'Cache parsed queries using the Apollo Automatic Persisted Queries protocol', 'woocommerce' ),
-				'id'      => Main::OPTION_APQ_ENABLED,
-				'default' => 'yes',
-				'type'    => 'checkbox',
-			),
 			array(
 				'title'             => __( 'Maximum query depth', 'woocommerce' ),
 				'desc'              => __( 'Reject queries whose selection nesting exceeds this depth.', 'woocommerce' ),
@@ -101,12 +102,19 @@ class Settings {
 				'type'    => 'checkbox',
 			),
 			array(
-				'title'    => __( 'Endpoint URL', 'woocommerce' ),
-				'desc'     => __( 'Path relative to /wp-json/ where the GraphQL endpoint is exposed. Needs at least two segments (namespace/route), e.g. wc/graphql.', 'woocommerce' ),
-				'desc_tip' => true,
-				'id'       => Main::OPTION_ENDPOINT_URL,
-				'default'  => GraphQLController::DEFAULT_ENDPOINT_URL,
-				'type'     => 'text',
+				'title'   => __( 'Enable APQ caching', 'woocommerce' ),
+				'desc'    => __( 'Cache parsed queries using the Apollo Automatic Persisted Queries protocol', 'woocommerce' ),
+				'id'      => Main::OPTION_APQ_ENABLED,
+				'default' => 'yes',
+				'type'    => 'checkbox',
+			),
+			array(
+				'title'             => __( 'Parsed query cache TTL', 'woocommerce' ),
+				'desc'              => __( 'Time in seconds before cached parsed queries expire.', 'woocommerce' ),
+				'id'                => Main::OPTION_QUERY_CACHE_TTL,
+				'default'           => (string) QueryCache::DEFAULT_CACHE_TTL,
+				'type'              => 'number',
+				'custom_attributes' => array( 'min' => '1' ),
 			),
 			array(
 				'type' => 'sectionend',
diff --git a/plugins/woocommerce/tests/php/src/Internal/Api/SettingsTest.php b/plugins/woocommerce/tests/php/src/Internal/Api/SettingsTest.php
index 7cc7540d516..8864d59a5f8 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Api/SettingsTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Api/SettingsTest.php
@@ -5,6 +5,7 @@ namespace Automattic\WooCommerce\Tests\Internal\Api;

 use Automattic\WooCommerce\Internal\Api\GraphQLController;
 use Automattic\WooCommerce\Internal\Api\Main;
+use Automattic\WooCommerce\Internal\Api\QueryCache;
 use Automattic\WooCommerce\Internal\Api\Settings;
 use Automattic\WooCommerce\Internal\Features\FeaturesController;
 use WC_Unit_Test_Case;
@@ -284,4 +285,20 @@ class SettingsTest extends WC_Unit_Test_Case {

 		$this->assertSame( array(), $result );
 	}
+
+	/**
+	 * @testdox add_settings defines the parsed query cache TTL field with min=1 and the default constant as default.
+	 */
+	public function test_add_settings_defines_query_cache_ttl_field(): void {
+		$fields = $this->sut->add_settings( array(), Settings::SECTION_ID );
+		$by_id  = array_column( $fields, null, 'id' );
+
+		$this->assertArrayHasKey( Main::OPTION_QUERY_CACHE_TTL, $by_id );
+		$this->assertSame( 'number', $by_id[ Main::OPTION_QUERY_CACHE_TTL ]['type'] );
+		$this->assertSame(
+			(string) QueryCache::DEFAULT_CACHE_TTL,
+			$by_id[ Main::OPTION_QUERY_CACHE_TTL ]['default']
+		);
+		$this->assertSame( '1', $by_id[ Main::OPTION_QUERY_CACHE_TTL ]['custom_attributes']['min'] );
+	}
 }