Commit cf26536d6e for woocommerce

commit cf26536d6ec3203d55077406f4f29aa298226fb0
Author: Vladimir Reznichenko <kalessil@gmail.com>
Date:   Fri Jan 30 08:33:40 2026 +0100

    [Performance] Products: consolidate cache priming in product data stores (#62999)

    Consolidated previous work on adding cache priming in product data stores to make it more uniform, and added priming where it was missing (bulk-products construction involved).

diff --git a/plugins/woocommerce/changelog/performance-62987-cache-priming-in-product-data-stores b/plugins/woocommerce/changelog/performance-62987-cache-priming-in-product-data-stores
new file mode 100644
index 0000000000..bd334f3231
--- /dev/null
+++ b/plugins/woocommerce/changelog/performance-62987-cache-priming-in-product-data-stores
@@ -0,0 +1,4 @@
+Significance: minor
+Type: performance
+
+Products: consolidate cache priming calls within product data stores.
diff --git a/plugins/woocommerce/includes/abstracts/abstract-wc-product.php b/plugins/woocommerce/includes/abstracts/abstract-wc-product.php
index cf5ec54636..3cb8eea96f 100644
--- a/plugins/woocommerce/includes/abstracts/abstract-wc-product.php
+++ b/plugins/woocommerce/includes/abstracts/abstract-wc-product.php
@@ -1939,7 +1939,7 @@ class WC_Product extends WC_Abstract_Legacy_Product {
 	/**
 	 * Returns the children IDs if applicable. Overridden by child classes.
 	 *
-	 * @return array of IDs
+	 * @return int[] of IDs
 	 */
 	public function get_children() {
 		return array();
diff --git a/plugins/woocommerce/includes/class-wc-product-grouped.php b/plugins/woocommerce/includes/class-wc-product-grouped.php
index 3d08e6c3c9..33357ff3eb 100644
--- a/plugins/woocommerce/includes/class-wc-product-grouped.php
+++ b/plugins/woocommerce/includes/class-wc-product-grouped.php
@@ -142,7 +142,7 @@ class WC_Product_Grouped extends WC_Product {
 	 * Return the children of this product.
 	 *
 	 * @param  string $context What the value is for. Valid values are view and edit.
-	 * @return array
+	 * @return int[]
 	 */
 	public function get_children( $context = 'view' ) {
 		return $this->get_prop( 'children', $context );
@@ -152,11 +152,14 @@ class WC_Product_Grouped extends WC_Product {
 	 * Return the product's children - visible only.
 	 *
 	 * @since 9.8.0
-	 * @return array Child products
+	 * @return WC_Product[] Child products
 	 */
 	public function get_visible_children() {
 		$grouped_products = array_map( 'wc_get_product', $this->get_children() );
-		return array_filter( $grouped_products, 'wc_products_array_filter_visible_grouped' );
+		$grouped_products = array_filter( $grouped_products, 'wc_products_array_filter_visible_grouped' );
+		/** @var WC_Product[] $grouped_products */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort
+
+		return $grouped_products;
 	}

 	/**
diff --git a/plugins/woocommerce/includes/class-wc-product-variable.php b/plugins/woocommerce/includes/class-wc-product-variable.php
index d8b597f3b8..400aac33ad 100644
--- a/plugins/woocommerce/includes/class-wc-product-variable.php
+++ b/plugins/woocommerce/includes/class-wc-product-variable.php
@@ -216,7 +216,7 @@ class WC_Product_Variable extends WC_Product {
 	 * This is lazy loaded as it's not used often and does require several queries.
 	 *
 	 * @param bool|string $visible_only Visible only.
-	 * @return array Children ids
+	 * @return int[] Children ids
 	 */
 	public function get_children( $visible_only = '' ) {
 		if ( is_bool( $visible_only ) ) {
@@ -240,7 +240,7 @@ class WC_Product_Variable extends WC_Product {
 	 * This is lazy loaded as it's not used often and does require several queries.
 	 *
 	 * @since 3.0.0
-	 * @return array Children ids
+	 * @return int[] Children ids
 	 */
 	public function get_visible_children() {
 		if ( null === $this->visible_children ) {
diff --git a/plugins/woocommerce/includes/data-stores/class-wc-product-data-store-cpt.php b/plugins/woocommerce/includes/data-stores/class-wc-product-data-store-cpt.php
index 6098c7b98e..bc15fb8cb0 100644
--- a/plugins/woocommerce/includes/data-stores/class-wc-product-data-store-cpt.php
+++ b/plugins/woocommerce/includes/data-stores/class-wc-product-data-store-cpt.php
@@ -1506,15 +1506,22 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
 			return $count;
 		}

-		// Get existing variations so we don't create duplicates.
-		$existing_variations = array_map( 'wc_get_product', $product->get_children() );
 		$existing_attributes = array();

-		foreach ( $existing_variations as $existing_variation ) {
-			$existing_attributes[] = $existing_variation->get_attributes();
+		$child_ids = $product->get_children();
+		if ( ! empty( $child_ids ) ) {
+			_prime_post_caches( $child_ids );
+			// Get existing variations so we don't create duplicates.
+			foreach ( $child_ids as $child_id ) {
+				$child = wc_get_product( $child_id );
+				if ( $child ) {
+					$existing_attributes[] = $child->get_attributes();
+				}
+			}
 		}

 		$possible_attributes = array_reverse( wc_array_cartesian( $attributes ) );
+		$product_id          = $product->get_id();

 		foreach ( $possible_attributes as $possible_attribute ) {
 			// Allow any order if key/values -- do not use strict mode.
@@ -1526,7 +1533,7 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
 			foreach ( $metadata as $meta ) {
 				$variation->add_meta_data( $meta['key'], $meta['value'] );
 			}
-			$variation->set_parent_id( $product->get_id() );
+			$variation->set_parent_id( $product_id );
 			$variation->set_attributes( $possible_attribute );
 			$variation_id = $variation->save();

diff --git a/plugins/woocommerce/includes/data-stores/class-wc-product-grouped-data-store-cpt.php b/plugins/woocommerce/includes/data-stores/class-wc-product-grouped-data-store-cpt.php
index 41e45b815b..d770a9e447 100644
--- a/plugins/woocommerce/includes/data-stores/class-wc-product-grouped-data-store-cpt.php
+++ b/plugins/woocommerce/includes/data-stores/class-wc-product-grouped-data-store-cpt.php
@@ -70,31 +70,31 @@ class WC_Product_Grouped_Data_Store_CPT extends WC_Product_Data_Store_CPT implem
 	 * @param WC_Product $product Product object.
 	 */
 	protected function update_prices_from_children( &$product ) {
-		$child_ids    = $product->get_children( 'edit' );
+		$product_id   = $product->get_id();
 		$child_prices = array();

-		// Prime caches for all child products at once to reduce queries.
-		if ( is_callable( '_prime_post_caches' ) && ! empty( $child_ids ) ) {
+		$child_ids = $product->get_children( 'edit' );
+		if ( ! empty( $child_ids ) ) {
 			_prime_post_caches( $child_ids );
-		}
-
-		foreach ( $child_ids as $child_id ) {
-			$child = wc_get_product( $child_id );
-			if ( $child ) {
-				$child_prices[] = $child->get_price( 'edit' );
+			foreach ( $child_ids as $child_id ) {
+				$child = wc_get_product( $child_id );
+				if ( $child ) {
+					$child_prices[] = $child->get_price( 'edit' );
+				}
 			}
+			$child_prices = array_filter( $child_prices );
 		}
-		$child_prices = array_filter( $child_prices );
-		delete_post_meta( $product->get_id(), '_price' );
-		delete_post_meta( $product->get_id(), '_sale_price' );
-		delete_post_meta( $product->get_id(), '_regular_price' );
+
+		delete_post_meta( $product_id, '_price' );
+		delete_post_meta( $product_id, '_sale_price' );
+		delete_post_meta( $product_id, '_regular_price' );

 		if ( ! empty( $child_prices ) ) {
-			add_post_meta( $product->get_id(), '_price', min( $child_prices ) );
-			add_post_meta( $product->get_id(), '_price', max( $child_prices ) );
+			add_post_meta( $product_id, '_price', min( $child_prices ) );
+			add_post_meta( $product_id, '_price', max( $child_prices ) );
 		}

-		$this->update_lookup_table( $product->get_id(), 'wc_product_meta_lookup' );
+		$this->update_lookup_table( $product_id, 'wc_product_meta_lookup' );

 		/**
 		 * Fire an action for this direct update so it can be detected by other code.
@@ -102,6 +102,6 @@ class WC_Product_Grouped_Data_Store_CPT extends WC_Product_Data_Store_CPT implem
 		 * @since 3.6
 		 * @param int $product_id Product ID that was updated directly.
 		 */
-		do_action( 'woocommerce_updated_product_price', $product->get_id() );
+		do_action( 'woocommerce_updated_product_price', $product_id );
 	}
 }
diff --git a/plugins/woocommerce/includes/data-stores/class-wc-product-variable-data-store-cpt.php b/plugins/woocommerce/includes/data-stores/class-wc-product-variable-data-store-cpt.php
index 3805a8ebae..3f88f99949 100644
--- a/plugins/woocommerce/includes/data-stores/class-wc-product-variable-data-store-cpt.php
+++ b/plugins/woocommerce/includes/data-stores/class-wc-product-variable-data-store-cpt.php
@@ -315,12 +315,12 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple

 				$variation_ids = $product->get_visible_children();

-				if ( is_callable( '_prime_post_caches' ) ) {
+				if ( ! empty( $variation_ids ) ) {
 					_prime_post_caches( $variation_ids );
 				}

 				$tax_display_mode = $for_display ? get_option( 'woocommerce_tax_display_shop' ) : null;
-
+				$price_decimals   = wc_get_price_decimals();
 				foreach ( $variation_ids as $variation_id ) {
 					$variation = wc_get_product( $variation_id );

@@ -359,6 +359,11 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
 						 */
 						$price = apply_filters( 'woocommerce_variation_prices_price', $variation->get_price( 'edit' ), $variation, $product );

+						// Skip empty prices.
+						if ( '' === $price ) {
+							continue;
+						}
+
 						/**
 						 * Filters the regular price for a product variation before it is used in price calculations and caching.
 						 *
@@ -385,11 +390,6 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
 						 */
 						$sale_price = apply_filters( 'woocommerce_variation_prices_sale_price', $variation->get_sale_price( 'edit' ), $variation, $product );

-						// Skip empty prices.
-						if ( '' === $price ) {
-							continue;
-						}
-
 						// If sale price does not equal price, the product is not yet on sale.
 						if ( $sale_price === $regular_price || $sale_price !== $price ) {
 							$sale_price = $regular_price;
@@ -444,9 +444,9 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
 							}
 						}

-						$prices_array['price'][ $variation_id ]         = wc_format_decimal( $price, wc_get_price_decimals() );
-						$prices_array['regular_price'][ $variation_id ] = wc_format_decimal( $regular_price, wc_get_price_decimals() );
-						$prices_array['sale_price'][ $variation_id ]    = wc_format_decimal( $sale_price, wc_get_price_decimals() );
+						$prices_array['price'][ $variation_id ]         = wc_format_decimal( $price, $price_decimals );
+						$prices_array['regular_price'][ $variation_id ] = wc_format_decimal( $regular_price, $price_decimals );
+						$prices_array['sale_price'][ $variation_id ]    = wc_format_decimal( $sale_price, $price_decimals );

 						if ( has_filter( 'woocommerce_variation_prices_array' ) ) {
 							$original_prices_array = $prices_array;
diff --git a/plugins/woocommerce/includes/legacy/abstract-wc-legacy-product.php b/plugins/woocommerce/includes/legacy/abstract-wc-legacy-product.php
index 532410a0de..4b0fe30282 100644
--- a/plugins/woocommerce/includes/legacy/abstract-wc-legacy-product.php
+++ b/plugins/woocommerce/includes/legacy/abstract-wc-legacy-product.php
@@ -588,6 +588,7 @@ abstract class WC_Abstract_Legacy_Product extends WC_Data {
 				$children = $product->get_children( 'edit' );
 			}

+			/** @var int[] $children */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort
 			foreach ( $children as $child_id ) {
 				$all_meta = get_post_meta( $child_id );

diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index 86c3d4f138..b0dca53332 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -21996,12 +21996,6 @@ parameters:
 			count: 1
 			path: includes/data-stores/class-wc-product-data-store-cpt.php

-		-
-			message: '#^Cannot call method get_attributes\(\) on WC_Product\|false\|null\.$#'
-			identifier: method.nonObject
-			count: 1
-			path: includes/data-stores/class-wc-product-data-store-cpt.php
-
 		-
 			message: '#^Method WC_Product_Data_Store_CPT\:\:clear_caches\(\) has no return type specified\.$#'
 			identifier: missingType.return
@@ -22182,12 +22176,6 @@ parameters:
 			count: 1
 			path: includes/data-stores/class-wc-product-data-store-cpt.php

-		-
-			message: '#^Call to function is_callable\(\) with ''_prime_post_caches'' will always evaluate to true\.$#'
-			identifier: function.alreadyNarrowedType
-			count: 1
-			path: includes/data-stores/class-wc-product-grouped-data-store-cpt.php
-
 		-
 			message: '#^Method WC_Product\:\:get_children\(\) invoked with 1 parameter, 0 required\.$#'
 			identifier: arguments.count
@@ -22254,12 +22242,6 @@ parameters:
 			count: 1
 			path: includes/data-stores/class-wc-product-variable-data-store-cpt.php

-		-
-			message: '#^Call to function is_callable\(\) with ''_prime_post_caches'' will always evaluate to true\.$#'
-			identifier: function.alreadyNarrowedType
-			count: 1
-			path: includes/data-stores/class-wc-product-variable-data-store-cpt.php
-
 		-
 			message: '#^Method WC_Product_Variable_Data_Store_CPT\:\:delete_variations\(\) has no return type specified\.$#'
 			identifier: missingType.return
@@ -25566,12 +25548,6 @@ parameters:
 			count: 1
 			path: includes/legacy/abstract-wc-legacy-product.php

-		-
-			message: '#^Argument of an invalid type array\|true supplied for foreach, only iterables are supported\.$#'
-			identifier: foreach.nonIterable
-			count: 1
-			path: includes/legacy/abstract-wc-legacy-product.php
-
 		-
 			message: '#^Call to an undefined method WC_Abstract_Legacy_Product\:\:child_has_dimensions\(\)\.$#'
 			identifier: method.notFound