Commit d3b02f977c9 for woocommerce

commit d3b02f977c9b3c606f3b7d73f546929b63235ff1
Author: Vladimir Reznichenko <kalessil@gmail.com>
Date:   Wed Sep 23 14:28:15 2026 +0200

    [Performance] Address N+1 query pattern in wc_get_formatted_variation (#68043)

    The problem we are fixing: $term = get_term_by( 'slug', $value, $name ) in wc_get_formatted_variation is N+1 query pattern, which is normally not surfacing on pages (mostly because of cache priming by upstream and added to Woo core through 2026). But it still impacts HVMs using persistent object caching: product updates invalidate the term-queries cache group, causing N+1 on the next front-end request and increasing server load. For ERP syncs or peak sale events such as BFCM, when variations are involved, this can heavily hit the database, spilling into longer PHP worker hold times and all the chaos that follows.

    ---------

    Co-authored-by: Marcin Dudek <marcin.dudek.dev@gmail.com>

diff --git a/plugins/woocommerce/changelog/address-n-query-pattern-in-wc_get_formatted_variation b/plugins/woocommerce/changelog/address-n-query-pattern-in-wc_get_formatted_variation
new file mode 100644
index 00000000000..b1e7c27e230
--- /dev/null
+++ b/plugins/woocommerce/changelog/address-n-query-pattern-in-wc_get_formatted_variation
@@ -0,0 +1,4 @@
+Significance: minor
+Type: performance
+
+Addressed N+1 query pattern in wc_get_formatted_variation by bulk-fetching taxonomy terms.
diff --git a/plugins/woocommerce/includes/data-stores/class-wc-product-variation-data-store-cpt.php b/plugins/woocommerce/includes/data-stores/class-wc-product-variation-data-store-cpt.php
index e23406c1a56..ac804cf2e8d 100644
--- a/plugins/woocommerce/includes/data-stores/class-wc-product-variation-data-store-cpt.php
+++ b/plugins/woocommerce/includes/data-stores/class-wc-product-variation-data-store-cpt.php
@@ -88,14 +88,14 @@ class WC_Product_Variation_Data_Store_CPT extends WC_Product_Data_Store_CPT impl
 		$product->set_attributes( wc_get_product_variation_attributes( $product->get_id() ) );

 		$updates = array();
-		/**
-		 * If a variation title is not in sync with the parent e.g. saved prior to 3.0, or if the parent title has changed, detect here and update.
-		 */
-		$new_title = $this->generate_product_title( $product );

+		// If a variation title is not in sync with the parent e.g. saved prior to 3.0, or if the parent title has changed, detect here and update.
+		// This also covers edge-cases such as term name changes, or extensions bypassing product APIs and manipulating product data directly.
+		// Originally introduced in v2.7, this workaround still holds for v11.2 - with all filters added over years we are hardlocked now to keep it.
+		$new_title = $this->generate_product_title( $product );
 		if ( $post_object->post_title !== $new_title ) {
 			$product->set_name( $new_title );
-			$updates = array_merge( $updates, array( 'post_title' => $new_title ) );
+			$updates['post_title'] = $new_title;
 		}

 		if ( ! empty( $updates ) ) {
diff --git a/plugins/woocommerce/includes/wc-product-functions.php b/plugins/woocommerce/includes/wc-product-functions.php
index a7f265bb19b..3c6d301d84c 100644
--- a/plugins/woocommerce/includes/wc-product-functions.php
+++ b/plugins/woocommerce/includes/wc-product-functions.php
@@ -534,12 +534,26 @@ function wc_get_formatted_variation( $variation, $flat = false, $include_names =
 			$return = '<' . $list_type . ' class="variation">';
 		}

+		// Performance note: prefetch parent taxonomy terms to avoid per-attribute get_term_by queries.
+		$taxonomy_terms = array();
+		$parent_id      = $product instanceof WC_Product_Variation ? $product->get_parent_id() : 0;
+		if ( $parent_id ) {
+			foreach ( array_filter( array_keys( $variation_attributes ), 'taxonomy_exists' ) as $taxonomy ) {
+				$terms = get_the_terms( $parent_id, $taxonomy );
+				if ( is_array( $terms ) ) {
+					foreach ( array_filter( $terms, static fn( $term ) => $term instanceof \WP_Term ) as $term ) { // @phpstan-ignore instanceof.alwaysTrue (defensive checks agains get_the_terms filter)
+						$taxonomy_terms[ $taxonomy ][ $term->slug ] = $term;
+					}
+				}
+			}
+		}
+
 		$variation_list = array();

 		foreach ( $variation_attributes as $name => $value ) {
 			// If this is a term slug, get the term's nice name.
 			if ( taxonomy_exists( $name ) ) {
-				$term = get_term_by( 'slug', $value, $name );
+				$term = $taxonomy_terms[ $name ][ $value ] ?? get_term_by( 'slug', $value, $name );
 				if ( ! is_wp_error( $term ) && $term && null !== $term->name && '' !== $term->name ) {
 					$value = $term->name;
 				}
diff --git a/plugins/woocommerce/tests/php/includes/wc-product-functions-test.php b/plugins/woocommerce/tests/php/includes/wc-product-functions-test.php
index 34c5af756b9..86d4420734d 100644
--- a/plugins/woocommerce/tests/php/includes/wc-product-functions-test.php
+++ b/plugins/woocommerce/tests/php/includes/wc-product-functions-test.php
@@ -2853,4 +2853,25 @@ class WC_Product_Functions_Tests extends \WC_Unit_Test_Case {
 			'The next batch must not be primed while the first batch is processing.'
 		);
 	}
+
+	/**
+	 * @testdox wc_get_formatted_variation resolves taxonomy term slugs to human-readable names via the prefetch cache.
+	 */
+	public function test_wc_get_formatted_variation_resolves_taxonomy_term_names(): void {
+		$attribute = WC_Helper_Product::create_product_attribute_object( 'color', array( 'Dark Blue', 'Light Green' ) );
+
+		$product = new WC_Product_Variable();
+		$product->set_name( 'Test Product' );
+		$product->set_attributes( array( $attribute ) );
+		$product->save();
+
+		$variation = new WC_Product_Variation();
+		$variation->set_parent_id( $product->get_id() );
+		$variation->set_attributes( array( 'pa_color' => 'dark-blue' ) );
+		$variation->save();
+
+		$this->assertSame( 'color: Dark Blue', wc_get_formatted_variation( wc_get_product( $variation->get_id() ), true ) );
+
+		$product->delete( true );
+	}
 }