Commit 0e1861682a9 for woocommerce
commit 0e1861682a97299deeec617d52dbfed5a56fca69
Author: Alba Rincón <albarin@users.noreply.github.com>
Date: Thu Jun 4 11:40:15 2026 +0200
Batch-prime product image caches across product collection endpoints (#65436)
* Batch-prime product image caches in product collection endpoints
* Use ProductUtil::prime_image_caches in CartSchema
* Add changefile(s) from automation for the following project(s): woocommerce
* Use ProductUtil import alias instead of FQN
* Shorten comment.
* Make ProductUtil::prime_image_caches an instance method resolved via the container
---------
Co-authored-by: woocommercebot <woocommercebot@users.noreply.github.com>
diff --git a/plugins/woocommerce/changelog/65436-performance-batch-prime-product-collection-images b/plugins/woocommerce/changelog/65436-performance-batch-prime-product-collection-images
new file mode 100644
index 00000000000..9ee0889fd20
--- /dev/null
+++ b/plugins/woocommerce/changelog/65436-performance-batch-prime-product-collection-images
@@ -0,0 +1,4 @@
+Significance: patch
+Type: performance
+
+Batch-prime product image attachment caches across the products collection endpoints (Store API, REST v3, v4) and reuse the shared helper in the Store API cart.
\ No newline at end of file
diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php
index 7abd1ac3518..d4296b32f87 100644
--- a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php
+++ b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php
@@ -14,6 +14,7 @@ use Automattic\WooCommerce\Enums\ProductTaxStatus;
use Automattic\WooCommerce\Enums\ProductType;
use Automattic\WooCommerce\Enums\CatalogVisibility;
use Automattic\WooCommerce\Internal\CostOfGoodsSold\CogsAwareRestControllerTrait;
+use Automattic\WooCommerce\Internal\Utilities\ProductUtil;
use Automattic\WooCommerce\Utilities\I18nUtil;
use Automattic\WooCommerce\Utilities\MetaDataUtil;
@@ -520,6 +521,12 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
$this->exclude_status = array();
}
+ // Batch-prime image attachment caches for the whole collection, rather than once per
+ // product when get_images() runs during serialization.
+ if ( ! empty( $result['objects'] ) ) {
+ wc_get_container()->get( ProductUtil::class )->prime_image_caches( $result['objects'] );
+ }
+
return $result;
}
diff --git a/plugins/woocommerce/src/Internal/RestApi/Routes/V4/Products/Controller.php b/plugins/woocommerce/src/Internal/RestApi/Routes/V4/Products/Controller.php
index a851fe00518..bf4fb934f72 100644
--- a/plugins/woocommerce/src/Internal/RestApi/Routes/V4/Products/Controller.php
+++ b/plugins/woocommerce/src/Internal/RestApi/Routes/V4/Products/Controller.php
@@ -18,6 +18,7 @@ use Automattic\WooCommerce\Enums\ProductTaxStatus;
use Automattic\WooCommerce\Enums\ProductType;
use Automattic\WooCommerce\Enums\CatalogVisibility;
use Automattic\WooCommerce\Internal\CostOfGoodsSold\CogsAwareRestControllerTrait;
+use Automattic\WooCommerce\Internal\Utilities\ProductUtil;
use Automattic\WooCommerce\Utilities\I18nUtil;
use Automattic\WooCommerce\Utilities\MetaDataUtil;
use WC_REST_Products_V2_Controller;
@@ -709,6 +710,12 @@ class Controller extends WC_REST_Products_V2_Controller {
$this->exclude_status = array();
}
+ // Batch-prime image attachment caches for the whole collection, rather than once per
+ // product when get_images() runs during serialization.
+ if ( ! empty( $result['objects'] ) ) {
+ wc_get_container()->get( ProductUtil::class )->prime_image_caches( $result['objects'] );
+ }
+
return $result;
}
diff --git a/plugins/woocommerce/src/Internal/Utilities/ProductUtil.php b/plugins/woocommerce/src/Internal/Utilities/ProductUtil.php
index 885c01a37ab..0e31d4242c0 100644
--- a/plugins/woocommerce/src/Internal/Utilities/ProductUtil.php
+++ b/plugins/woocommerce/src/Internal/Utilities/ProductUtil.php
@@ -44,4 +44,22 @@ class ProductUtil {
}
}
}
+
+ /**
+ * Prime featured and gallery image attachment caches for a collection of products in a single
+ * batched query, instead of priming each product's images separately.
+ *
+ * @param array $products Products whose image attachments should be primed. Non-product items are ignored.
+ * @return void
+ */
+ public function prime_image_caches( array $products ): void {
+ $products = array_filter( $products, static fn( $product ) => $product instanceof \WC_Product );
+ $featured = array_map( static fn( $product ) => $product->get_image_id(), $products );
+ $gallery = array_map( static fn( $product ) => $product->get_gallery_image_ids(), $products );
+ $image_ids = array_filter( array_unique( array_map( 'intval', array_merge( $featured, ...$gallery ) ) ) );
+ if ( ! empty( $image_ids ) ) {
+ // Prime caches to reduce future queries.
+ _prime_post_caches( $image_ids );
+ }
+ }
}
diff --git a/plugins/woocommerce/src/StoreApi/Schemas/V1/CartSchema.php b/plugins/woocommerce/src/StoreApi/Schemas/V1/CartSchema.php
index 0f0c0303738..55b2a3cf52c 100644
--- a/plugins/woocommerce/src/StoreApi/Schemas/V1/CartSchema.php
+++ b/plugins/woocommerce/src/StoreApi/Schemas/V1/CartSchema.php
@@ -1,6 +1,7 @@
<?php
namespace Automattic\WooCommerce\StoreApi\Schemas\V1;
+use Automattic\WooCommerce\Internal\Utilities\ProductUtil;
use Automattic\WooCommerce\StoreApi\SchemaController;
use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema;
use Automattic\WooCommerce\StoreApi\Utilities\CartController;
@@ -343,29 +344,18 @@ class CartSchema extends AbstractSchema {
// Get visible cross sells products.
$cross_sells = array();
$cross_sell_ids = $cart->get_cross_sells();
- $image_ids = array();
if ( ! empty( $cross_sell_ids ) ) {
// Prime caches to reduce future queries.
_prime_post_caches( $cross_sell_ids );
$cross_sells = array_values( array_filter( array_map( 'wc_get_product', $cross_sell_ids ), 'wc_products_array_filter_visible' ) );
/** @var \WC_Product[] $cross_sells */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort
- // Identify which images need priming.
- $ids = array_map( static fn( $product ) => array( (int) $product->get_image_id(), ...$product->get_gallery_image_ids() ), $cross_sells );
- $image_ids[] = array_values( array_filter( array_merge( ...$ids ) ) );
}
$cart_all_items = $cart->get_cart();
$cart_line_items = array_values( array_filter( $cart_all_items, static fn( $item ) => ( $item['data'] ?? null ) instanceof \WC_Product ) );
- if ( ! empty( $cart_line_items ) ) {
- // Identify which images need priming.
- $ids = array_map( static fn( $item ) => array( (int) $item['data']->get_image_id(), ...$item['data']->get_gallery_image_ids() ), $cart_line_items );
- $image_ids[] = array_values( array_filter( array_merge( ...array_values( $ids ) ) ) );
- }
- if ( ! empty( $image_ids ) ) {
- // Prime caches to reduce future queries.
- _prime_post_caches( array_unique( array_merge( ...$image_ids ) ) );
- }
+ // Batch-prime image attachment caches for cross-sells and cart line items in one query.
+ wc_get_container()->get( ProductUtil::class )->prime_image_caches( array_merge( $cross_sells, array_column( $cart_line_items, 'data' ) ) );
return [
'items' => $this->get_item_responses_from_schema( $this->item_schema, $cart_all_items ),
diff --git a/plugins/woocommerce/src/StoreApi/Utilities/ProductQuery.php b/plugins/woocommerce/src/StoreApi/Utilities/ProductQuery.php
index 98ef5bb250b..e838b5c683d 100644
--- a/plugins/woocommerce/src/StoreApi/Utilities/ProductQuery.php
+++ b/plugins/woocommerce/src/StoreApi/Utilities/ProductQuery.php
@@ -7,6 +7,7 @@ use Automattic\WooCommerce\Enums\ProductStatus;
use Automattic\WooCommerce\Enums\ProductType;
use Automattic\WooCommerce\Enums\CatalogVisibility;
use Automattic\WooCommerce\Internal\ProductFilters\Interfaces\QueryClausesGenerator;
+use Automattic\WooCommerce\Internal\Utilities\ProductUtil;
use Automattic\WooCommerce\StoreApi\Exceptions\RouteException;
use WC_Tax;
@@ -356,8 +357,14 @@ class ProductQuery implements QueryClausesGenerator {
_prime_post_caches( $results['results'] );
}
+ $objects = array_map( 'wc_get_product', $results['results'] );
+
+ // Batch-prime image attachment caches for the whole collection, rather than once per
+ // product when ProductSchema::get_images() runs during serialization.
+ wc_get_container()->get( ProductUtil::class )->prime_image_caches( $objects );
+
return array(
- 'objects' => array_map( 'wc_get_product', $results['results'] ),
+ 'objects' => $objects,
'total' => $results['total'],
'pages' => $results['pages'],
);