Commit 844c1a9dbd6 for woocommerce
commit 844c1a9dbd6e8c11e77c6b1d20aaa49fe767ae9d
Author: HaMed Elaraby <48021363+wehamed@users.noreply.github.com>
Date: Tue Aug 25 16:37:04 2026 +0300
Fix inconsistent order item cache shape primed by the order item data store (#67513)
Fix inconsistent order item cache shape
The order data store's read_items() primes the item-{id} cache entry
(order-items group) with order_item_type, order_item_id, order_id and
order_item_name, but the individual order item data store's read() writes
to the same cache key with only order_id and order_item_name. The cached
row's shape therefore depends on which code path primed it, and rows primed
by the item data store cannot be consumed by WC_Order_Factory::get_order_item(),
which requires both order_item_type and order_item_id.
Select the same four columns in the item data store so the cache shape is
consistent regardless of which path primed it.
Co-authored-by: Seghir Nadir <nadir.seghir@gmail.com>
diff --git a/plugins/woocommerce/changelog/fix-31656-order-item-cache-shape b/plugins/woocommerce/changelog/fix-31656-order-item-cache-shape
new file mode 100644
index 00000000000..d91a4b1ca38
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-31656-order-item-cache-shape
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Ensure the cached order item data always includes the item type and item ID, so the cache shape is consistent between the order data store and the order item data store.
diff --git a/plugins/woocommerce/includes/data-stores/abstract-wc-order-item-type-data-store.php b/plugins/woocommerce/includes/data-stores/abstract-wc-order-item-type-data-store.php
index 4f79e2f5de0..05ae66b7c65 100644
--- a/plugins/woocommerce/includes/data-stores/abstract-wc-order-item-type-data-store.php
+++ b/plugins/woocommerce/includes/data-stores/abstract-wc-order-item-type-data-store.php
@@ -162,7 +162,7 @@ abstract class Abstract_WC_Order_Item_Type_Data_Store extends WC_Data_Store_WP i
$data = wp_cache_get( 'item-' . $item->get_id(), 'order-items' );
if ( false === $data ) {
- $data = $wpdb->get_row( $wpdb->prepare( "SELECT order_id, order_item_name FROM {$wpdb->prefix}woocommerce_order_items WHERE order_item_id = %d LIMIT 1;", $item->get_id() ) );
+ $data = $wpdb->get_row( $wpdb->prepare( "SELECT order_item_type, order_item_id, order_id, order_item_name FROM {$wpdb->prefix}woocommerce_order_items WHERE order_item_id = %d LIMIT 1;", $item->get_id() ) );
wp_cache_set( 'item-' . $item->get_id(), $data, 'order-items' );
}
diff --git a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-item-data-store-test.php b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-item-data-store-test.php
index cc33296fd18..0bba6369441 100644
--- a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-item-data-store-test.php
+++ b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-item-data-store-test.php
@@ -212,4 +212,62 @@ class WC_Order_Item_Data_Store_Test extends WC_Unit_Test_Case {
$this->assertEquals( 56.78, $item2->get_cogs_value() );
}
+
+ /**
+ * @testdox The 'item-{id}' object cache entry has the same shape whether it was populated by the order data store or by the individual order item data store.
+ *
+ * @see https://github.com/woocommerce/woocommerce/issues/31656
+ */
+ public function test_cached_item_row_shape_is_identical_across_data_store_paths() {
+ $order = WC_Helper_Order::create_order();
+ $item_id = reset( $order->get_items() )->get_id();
+ $order_id = $order->get_id();
+
+ // Populate the cache via the order data store (bulk read of the order items).
+ wp_cache_delete( 'item-' . $item_id, 'order-items' );
+ wp_cache_delete( 'order-items-' . $order_id, 'orders' );
+ WC_Data_Store::load( 'order' )->read_items( wc_get_order( $order_id ), 'line_item' );
+ $from_order_store = wp_cache_get( 'item-' . $item_id, 'order-items' );
+
+ // Populate the cache via the individual order item data store.
+ wp_cache_delete( 'item-' . $item_id, 'order-items' );
+ wp_cache_delete( 'order-items-' . $order_id, 'orders' );
+ new WC_Order_Item_Product( $item_id );
+ $from_item_store = wp_cache_get( 'item-' . $item_id, 'order-items' );
+
+ $this->assertIsObject( $from_order_store );
+ $this->assertIsObject( $from_item_store );
+
+ $order_store_keys = array_keys( (array) $from_order_store );
+ $item_store_keys = array_keys( (array) $from_item_store );
+ sort( $order_store_keys );
+ sort( $item_store_keys );
+
+ $this->assertSame( $order_store_keys, $item_store_keys );
+ $this->assertSame( array( 'order_id', 'order_item_id', 'order_item_name', 'order_item_type' ), $order_store_keys );
+ }
+
+ /**
+ * @testdox A cached order item row that was populated by the individual order item data store can be converted back into an order item via WC_Order_Factory::get_order_item().
+ *
+ * @see https://github.com/woocommerce/woocommerce/issues/31656
+ */
+ public function test_cached_order_item_row_from_item_store_is_factory_compatible() {
+ $order = WC_Helper_Order::create_order();
+ $item_id = reset( $order->get_items() )->get_id();
+ $order_id = $order->get_id();
+
+ wp_cache_delete( 'item-' . $item_id, 'order-items' );
+ wp_cache_delete( 'order-items-' . $order_id, 'orders' );
+ new WC_Order_Item_Product( $item_id );
+
+ $cached_row = wp_cache_get( 'item-' . $item_id, 'order-items' );
+
+ $this->assertIsObject( $cached_row );
+
+ $item = WC_Order_Factory::get_order_item( $cached_row );
+
+ $this->assertInstanceOf( WC_Order_Item_Product::class, $item );
+ $this->assertSame( (int) $item_id, $item->get_id() );
+ }
}