Commit 4a340aa901 for woocommerce

commit 4a340aa901f086c0ef84f8c54da2a593a8954988
Author: Vladimir Reznichenko <kalessil@gmail.com>
Date:   Tue Feb 10 10:33:40 2026 +0100

    [Performance] Orders: detach legacy reports transients invalidation from order persistence (async) (#63118)

    Reduces the number of SQLs (-19%, when object cache is not enabled) on the checkout page by deferring the transient deletion of legacy reports (part of the order cache clean) for 1 minute and ensuring only a single event is scheduled at a time.

diff --git a/plugins/woocommerce/changelog/performance-63110-clean-legacy-reporting-transients-async b/plugins/woocommerce/changelog/performance-63110-clean-legacy-reporting-transients-async
new file mode 100644
index 0000000000..ef415b8e2a
--- /dev/null
+++ b/plugins/woocommerce/changelog/performance-63110-clean-legacy-reporting-transients-async
@@ -0,0 +1,4 @@
+Significance: minor
+Type: performance
+
+Performance: separate the transient deletion of legacy reports from the order cache refresh and execute these operations asynchronously.
diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-reports.php b/plugins/woocommerce/includes/admin/class-wc-admin-reports.php
index a1d396558c..b926a3bdd9 100644
--- a/plugins/woocommerce/includes/admin/class-wc-admin-reports.php
+++ b/plugins/woocommerce/includes/admin/class-wc-admin-reports.php
@@ -24,13 +24,57 @@ if ( class_exists( 'WC_Admin_Reports', false ) ) {
 class WC_Admin_Reports {

 	/**
-	 * Register the proper hook handlers.
+	 * Register the hook handlers for integrating with admin.
 	 */
 	public static function register_hook_handlers() {
 		add_filter( 'woocommerce_after_dashboard_status_widget_parameter', array( __CLASS__, 'get_report_instance' ) );
 		add_filter( 'woocommerce_dashboard_status_widget_reports', array( __CLASS__, 'replace_dashboard_status_widget_reports' ) );
 	}

+	/**
+	 * Register the hook handlers for integrating with orders.
+	 *
+	 * @internal
+	 * @since 10.6.0
+	 */
+	public static function register_orders_hook_handlers(): void {
+		add_action( 'woocommerce_delete_shop_order_transients', array( __CLASS__, 'delete_legacy_reports_transients' ), 10, 1 );
+		add_action( 'woocommerce_delete_legacy_report_transients', array( __CLASS__, 'delete_legacy_reports_transients' ), 10, 2 );
+	}
+
+	/**
+	 * Execute legacy reports transient deletion (sync or async depending on the context)
+	 *
+	 * @internal
+	 * @since 10.6.0
+	 *
+	 * @param int  $order_id Order ID (unused, exists for compatibility between the hooks we are integrating with).
+	 * @param bool $defer    Whether to defer the deletion or execute.
+	 * @return void
+	 */
+	public static function delete_legacy_reports_transients( int $order_id, bool $defer = true ): void {
+		// Deferring is only making sense on sites without object cache enabled (if enabled, no SQLs being executed).
+		if ( $defer && ! wp_using_ext_object_cache() ) {
+			static $skip_consequent;
+
+			// Schedule the deletion, cap the execution to single pending event at any given time.
+			$schedule = ! $skip_consequent && ! as_has_scheduled_action( 'woocommerce_delete_legacy_report_transients', null, 'woocommerce' );
+			if ( $schedule ) {
+				as_schedule_single_action( time() + MINUTE_IN_SECONDS, 'woocommerce_delete_legacy_report_transients', array( $order_id, false ), 'woocommerce' );
+			}
+			$skip_consequent = true;
+
+			return;
+		}
+
+		delete_transient( 'wc_admin_report' );
+		foreach ( self::get_reports() as $report_group ) {
+			foreach ( $report_group['reports'] as $report_key => $report ) {
+				delete_transient( 'wc_report_' . $report_key );
+			}
+		}
+	}
+
 	/**
 	 * Get an instance of WC_Admin_Report.
 	 *
diff --git a/plugins/woocommerce/includes/class-woocommerce.php b/plugins/woocommerce/includes/class-woocommerce.php
index c81a67655e..4f104cd65c 100644
--- a/plugins/woocommerce/includes/class-woocommerce.php
+++ b/plugins/woocommerce/includes/class-woocommerce.php
@@ -399,6 +399,9 @@ final class WooCommerce {

 		$container->get( Automattic\WooCommerce\Internal\ProductFilters\MainQueryController::class )->register();
 		$container->get( Automattic\WooCommerce\Internal\ProductFilters\CacheController::class )->register();
+
+		// Integration point between legacy reports and orders APIs (the reports caches invalidation focused).
+		\WC_Admin_Reports::register_orders_hook_handlers();
 	}

 	/**
diff --git a/plugins/woocommerce/includes/wc-order-functions.php b/plugins/woocommerce/includes/wc-order-functions.php
index 792fc19326..9a2dcaae20 100644
--- a/plugins/woocommerce/includes/wc-order-functions.php
+++ b/plugins/woocommerce/includes/wc-order-functions.php
@@ -502,29 +502,15 @@ function wc_delete_shop_order_transients( $order = 0 ) {
 	if ( is_numeric( $order ) ) {
 		$order = wc_get_order( $order );
 	}
-	$reports             = WC_Admin_Reports::get_reports();
-	$transients_to_clear = array(
-		'wc_admin_report',
-	);
-
-	foreach ( $reports as $report_group ) {
-		foreach ( $report_group['reports'] as $report_key => $report ) {
-			$transients_to_clear[] = 'wc_report_' . $report_key;
-		}
-	}
-
-	foreach ( $transients_to_clear as $transient ) {
-		delete_transient( $transient );
-	}

 	// Clear customer's order related caches.
+	$order_id = 0;
 	if ( is_a( $order, 'WC_Order' ) ) {
-		$order_id = $order->get_id();
-		Users::delete_site_user_meta( $order->get_customer_id(), 'wc_money_spent' );
-		Users::delete_site_user_meta( $order->get_customer_id(), 'wc_order_count' );
-		Users::delete_site_user_meta( $order->get_customer_id(), 'wc_last_order' );
-	} else {
-		$order_id = 0;
+		$order_id    = $order->get_id();
+		$customer_id = $order->get_customer_id();
+		Users::delete_site_user_meta( $customer_id, 'wc_money_spent' );
+		Users::delete_site_user_meta( $customer_id, 'wc_order_count' );
+		Users::delete_site_user_meta( $customer_id, 'wc_last_order' );
 	}

 	// Increments the transient version to invalidate cache.
diff --git a/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-reports-test.php b/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-reports-test.php
new file mode 100644
index 0000000000..ec90423d76
--- /dev/null
+++ b/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-reports-test.php
@@ -0,0 +1,28 @@
+<?php
+
+declare( strict_types = 1);
+
+/**
+ * Tests for `WC_Admin_Reports` class.
+ */
+final class WC_Admin_Reports_Test extends WC_Unit_Test_Case {
+	/**
+	 * Verify the workflows execution in `delete_legacy_reports_transients`.
+	 */
+	public function test_delete_legacy_reports_transients(): void {
+		// Verify the integration point invocation.
+		$this->assertSame( 10, has_action( 'woocommerce_delete_shop_order_transients', array( \WC_Admin_Reports::class, 'delete_legacy_reports_transients' ) ) );
+		$this->assertTrue( has_action( 'woocommerce_delete_legacy_report_transients' ) );
+
+		// Verify the defer-workflow: nov verifying for pending AS action as other tests already triggered the deferred workflow.
+		// Accordingly, we can only verify that we entered into defer-workflow + rely on manual testing for this PR.
+		set_transient( 'wc_admin_report', 'Verify defer' );
+		\WC_Admin_Reports::delete_legacy_reports_transients( 0, true );
+		$this->assertSame( 'Verify defer', get_transient( 'wc_admin_report' ) );
+
+		// Verify the purge-workflow.
+		set_transient( 'wc_admin_report', 'Verify deletion' );
+		do_action( 'woocommerce_delete_legacy_report_transients', 0, false ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
+		$this->assertFalse( get_transient( 'wc_admin_report' ) );
+	}
+}