Merge pull request #27734 from woocommerce/enhancement/optimize-wc-order
Optimize function `wc_get_orders` by priming caches beforehand
This commit is contained in:
commit
ae163eb28b
|
@ -519,6 +519,50 @@ abstract class WC_Data {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to compute meta cache key. Different from WP Meta cache key in that meta data cached using this key also contains meta_id column.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_meta_cache_key() {
|
||||
if ( ! $this->get_id() ) {
|
||||
wc_doing_it_wrong( 'get_meta_cache_key', 'ID needs to be set before fetching a cache key.', '4.7.0' );
|
||||
return false;
|
||||
}
|
||||
return self::generate_meta_cache_key( $this->get_id(), $this->cache_group );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate cache key from id and group.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param int|string $id Object ID.
|
||||
* @param string $cache_group Group name use to store cache. Whole group cache can be invalidated in one go.
|
||||
*
|
||||
* @return string Meta cache key.
|
||||
*/
|
||||
public static function generate_meta_cache_key( $id, $cache_group ) {
|
||||
return WC_Cache_Helper::get_cache_prefix( $cache_group ) . WC_Cache_Helper::get_cache_prefix( 'object_' . $id ) . 'object_meta_' . $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prime caches for raw meta data. This includes meta_id column as well, which is not included by default in WP meta data.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param array $raw_meta_data_collection Array of objects of { object_id => array( meta_row_1, meta_row_2, ... }.
|
||||
* @param string $cache_group Name of cache group.
|
||||
*/
|
||||
public static function prime_raw_meta_data_cache( $raw_meta_data_collection, $cache_group ) {
|
||||
foreach ( $raw_meta_data_collection as $object_id => $raw_meta_data_array ) {
|
||||
$cache_key = self::generate_meta_cache_key( $object_id, $cache_group );
|
||||
wp_cache_set( $cache_key, $raw_meta_data_array, $cache_group );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read Meta Data from the database. Ignore any internal properties.
|
||||
* Uses it's own caches because get_metadata does not provide meta_ids.
|
||||
|
@ -540,7 +584,7 @@ abstract class WC_Data {
|
|||
|
||||
if ( ! empty( $this->cache_group ) ) {
|
||||
// Prefix by group allows invalidation by group until https://core.trac.wordpress.org/ticket/4476 is implemented.
|
||||
$cache_key = WC_Cache_Helper::get_cache_prefix( $this->cache_group ) . WC_Cache_Helper::get_cache_prefix( 'object_' . $this->get_id() ) . 'object_meta_' . $this->get_id();
|
||||
$cache_key = $this->get_meta_cache_key();
|
||||
}
|
||||
|
||||
if ( ! $force_read ) {
|
||||
|
@ -550,7 +594,9 @@ abstract class WC_Data {
|
|||
}
|
||||
}
|
||||
|
||||
$raw_meta_data = $cache_loaded ? $cached_meta : $this->data_store->read_meta( $this );
|
||||
// We filter the raw meta data again when loading from cache, in case we cached in an earlier version where filter conditions were different.
|
||||
$raw_meta_data = $cache_loaded ? $this->data_store->filter_raw_meta_data( $this, $cached_meta ) : $this->data_store->read_meta( $this );
|
||||
|
||||
if ( $raw_meta_data ) {
|
||||
foreach ( $raw_meta_data as $meta ) {
|
||||
$this->meta_data[] = new WC_Meta_Data(
|
||||
|
|
|
@ -93,14 +93,13 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
|
|||
/**
|
||||
* Method to read an order from the database.
|
||||
*
|
||||
* @param WC_Data $order Order object.
|
||||
* @param WC_Order $order Order object.
|
||||
*
|
||||
* @throws Exception If passed order is invalid.
|
||||
*/
|
||||
public function read( &$order ) {
|
||||
$order->set_defaults();
|
||||
$post_object = get_post( $order->get_id() );
|
||||
|
||||
if ( ! $order->get_id() || ! $post_object || ! in_array( $post_object->post_type, wc_get_order_types(), true ) ) {
|
||||
throw new Exception( __( 'Invalid order.', 'woocommerce' ) );
|
||||
}
|
||||
|
|
|
@ -94,7 +94,20 @@ class WC_Data_Store_WP {
|
|||
$object->get_id()
|
||||
)
|
||||
);
|
||||
return $this->filter_raw_meta_data( $object, $raw_meta_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to filter internal meta keys from all meta data rows for the object.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WC_Data $object WC_Data object.
|
||||
* @param array $raw_meta_data Array of std object of meta data to be filtered.
|
||||
*
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function filter_raw_meta_data( &$object, $raw_meta_data ) {
|
||||
$this->internal_meta_keys = array_merge( array_map( array( $this, 'prefix_key' ), $object->get_data_keys() ), $this->internal_meta_keys );
|
||||
$meta_data = array_filter( $raw_meta_data, array( $this, 'exclude_internal_meta_keys' ) );
|
||||
return apply_filters( "woocommerce_data_store_wp_{$this->meta_type}_read_meta", $meta_data, $object, $this );
|
||||
|
|
|
@ -734,11 +734,12 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
|
|||
* Get the order type based on Order ID.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @param int $order_id Order ID.
|
||||
* @param int|WP_Post $order Order | Order id.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_order_type( $order_id ) {
|
||||
return get_post_type( $order_id );
|
||||
public function get_order_type( $order ) {
|
||||
return get_post_type( $order );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -865,7 +866,13 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
|
|||
$query = new WP_Query( $args );
|
||||
}
|
||||
|
||||
$orders = ( isset( $query_vars['return'] ) && 'ids' === $query_vars['return'] ) ? $query->posts : array_filter( array_map( 'wc_get_order', $query->posts ) );
|
||||
if ( isset( $query_vars['return'] ) && 'ids' === $query_vars['return'] ) {
|
||||
$orders = $query->posts;
|
||||
} else {
|
||||
update_post_caches( $query->posts ); // We already fetching posts, might as well hydrate some caches.
|
||||
$order_ids = wp_list_pluck( $query->posts, 'ID' );
|
||||
$orders = $this->compile_orders( $order_ids, $query_vars, $query );
|
||||
}
|
||||
|
||||
if ( isset( $query_vars['paginate'] ) && $query_vars['paginate'] ) {
|
||||
return (object) array(
|
||||
|
@ -878,6 +885,213 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
|
|||
return $orders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile order response and set caches as needed for order ids.
|
||||
*
|
||||
* @param array $order_ids List of order IDS to compile.
|
||||
* @param array $query_vars Original query arguments.
|
||||
* @param WP_Query $query Query object.
|
||||
*
|
||||
* @return array Orders.
|
||||
*/
|
||||
private function compile_orders( $order_ids, $query_vars, $query ) {
|
||||
if ( empty( $order_ids ) ) {
|
||||
return array();
|
||||
}
|
||||
$orders = array();
|
||||
|
||||
// Lets do some cache hydrations so that we don't have to fetch data from DB for every order.
|
||||
$this->prime_raw_meta_cache_for_orders( $order_ids, $query_vars );
|
||||
$this->prime_refund_caches_for_order( $order_ids, $query_vars );
|
||||
$this->prime_order_item_caches_for_orders( $order_ids, $query_vars );
|
||||
|
||||
foreach ( $query->posts as $post ) {
|
||||
$orders[] = wc_get_order( $post );
|
||||
}
|
||||
|
||||
return $orders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prime refund cache for orders.
|
||||
*
|
||||
* @param array $order_ids Order Ids to prime cache for.
|
||||
* @param array $query_vars Query vars for the query.
|
||||
*/
|
||||
private function prime_refund_caches_for_order( $order_ids, $query_vars ) {
|
||||
if ( ! isset( $query_vars['type'] ) || ! ( 'shop_order' === $query_vars['type'] ) ) {
|
||||
return;
|
||||
}
|
||||
if ( isset( $query_vars['fields'] ) && 'all' !== $query_vars['fields'] ) {
|
||||
if ( is_array( $query_vars['fields'] ) && ! in_array( 'refunds', $query_vars['fields'] ) ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
$cache_keys_mapping = array();
|
||||
foreach ( $order_ids as $order_id ) {
|
||||
$cache_keys_mapping[ $order_id ] = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refunds' . $order_id;
|
||||
}
|
||||
$non_cached_ids = array();
|
||||
$cache_values = wc_cache_get_multiple( array_values( $cache_keys_mapping ), 'orders' );
|
||||
foreach ( $order_ids as $order_id ) {
|
||||
if ( false === $cache_values[ $cache_keys_mapping[ $order_id ] ] ) {
|
||||
$non_cached_ids[] = $order_id;
|
||||
}
|
||||
}
|
||||
if ( empty( $non_cached_ids ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$refunds = wc_get_orders(
|
||||
array(
|
||||
'type' => 'shop_order_refund',
|
||||
'post_parent__in' => $non_cached_ids,
|
||||
'limit' => - 1,
|
||||
)
|
||||
);
|
||||
$order_refunds = array_reduce(
|
||||
$refunds,
|
||||
function ( $order_refunds_array, WC_Order_Refund $refund ) {
|
||||
if ( ! isset( $order_refunds_array[ $refund->get_parent_id() ] ) ) {
|
||||
$order_refunds_array[ $refund->get_parent_id() ] = array();
|
||||
}
|
||||
$order_refunds_array[ $refund->get_parent_id() ][] = $refund;
|
||||
return $order_refunds_array;
|
||||
},
|
||||
array()
|
||||
);
|
||||
foreach ( $non_cached_ids as $order_id ) {
|
||||
$refunds = array();
|
||||
if ( isset( $order_refunds[ $order_id ] ) ) {
|
||||
$refunds = $order_refunds[ $order_id ];
|
||||
}
|
||||
wp_cache_set( $cache_keys_mapping[ $order_id ], $refunds, 'orders' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prime following caches:
|
||||
* 1. item-$order_item_id For individual items.
|
||||
* 2. order-items-$order-id For fetching items associated with an order.
|
||||
* 3. order-item meta.
|
||||
*
|
||||
* @param array $order_ids Order Ids to prime cache for.
|
||||
* @param array $query_vars Query vars for the query.
|
||||
*/
|
||||
private function prime_order_item_caches_for_orders( $order_ids, $query_vars ) {
|
||||
global $wpdb;
|
||||
if ( isset( $query_vars['fields'] ) && 'all' !== $query_vars['fields'] ) {
|
||||
$line_items = array(
|
||||
'line_items',
|
||||
'shipping_lines',
|
||||
'fee_lines',
|
||||
'coupon_lines',
|
||||
);
|
||||
|
||||
if ( is_array( $query_vars['fields'] ) && 0 === count( array_intersect( $line_items, $query_vars['fields'] ) ) ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
$cache_keys = array_map(
|
||||
function ( $order_id ) {
|
||||
return 'order-items-' . $order_id;
|
||||
},
|
||||
$order_ids
|
||||
);
|
||||
$cache_values = wc_cache_get_multiple( $cache_keys, 'orders' );
|
||||
$non_cached_ids = array();
|
||||
foreach ( $order_ids as $order_id ) {
|
||||
if ( false === $cache_values[ 'order-items-' . $order_id ] ) {
|
||||
$non_cached_ids[] = $order_id;
|
||||
}
|
||||
}
|
||||
if ( empty( $non_cached_ids ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$non_cached_ids = esc_sql( $non_cached_ids );
|
||||
$non_cached_ids_string = implode( ',', $non_cached_ids );
|
||||
$order_items = $wpdb->get_results(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
"SELECT order_item_type, order_item_id, order_id, order_item_name FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id in ( $non_cached_ids_string ) ORDER BY order_item_id;"
|
||||
);
|
||||
if ( empty( $order_items ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$order_items_for_all_orders = array_reduce(
|
||||
$order_items,
|
||||
function ( $order_items_collection, $order_item ) {
|
||||
if ( ! isset( $order_items_collection[ $order_item->order_id ] ) ) {
|
||||
$order_items_collection[ $order_item->order_id ] = array();
|
||||
}
|
||||
$order_items_collection[ $order_item->order_id ][] = $order_item;
|
||||
return $order_items_collection;
|
||||
}
|
||||
);
|
||||
foreach ( $order_items_for_all_orders as $order_id => $items ) {
|
||||
wp_cache_set( 'order-items-' . $order_id, $items, 'orders' );
|
||||
}
|
||||
foreach ( $order_items as $item ) {
|
||||
wp_cache_set( 'item-' . $item->order_item_id, $item, 'order-items' );
|
||||
}
|
||||
$order_item_ids = wp_list_pluck( $order_items, 'order_item_id' );
|
||||
update_meta_cache( 'order_item', $order_item_ids );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prime cache for raw meta data for orders in bulk. Difference between this and WP built-in metadata is that this method also fetches `meta_id` field which we use and cache it.
|
||||
*
|
||||
* @param array $order_ids Order Ids to prime cache for.
|
||||
* @param array $query_vars Query vars for the query.
|
||||
*/
|
||||
private function prime_raw_meta_cache_for_orders( $order_ids, $query_vars ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( isset( $query_vars['fields'] ) && 'all' !== $query_vars['fields'] ) {
|
||||
if ( is_array( $query_vars['fields'] ) && ! in_array( 'meta_data', $query_vars['fields'] ) ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$cache_keys_mapping = array();
|
||||
foreach ( $order_ids as $order_id ) {
|
||||
$cache_keys_mapping[ $order_id ] = WC_Order::generate_meta_cache_key( $order_id, 'orders' );
|
||||
}
|
||||
$cache_values = wc_cache_get_multiple( array_values( $cache_keys_mapping ), 'orders' );
|
||||
$non_cached_ids = array();
|
||||
foreach ( $order_ids as $order_id ) {
|
||||
if ( false === $cache_values[ $cache_keys_mapping[ $order_id ] ] ) {
|
||||
$non_cached_ids[] = $order_id;
|
||||
}
|
||||
}
|
||||
if ( empty( $non_cached_ids ) ) {
|
||||
return;
|
||||
}
|
||||
$order_ids = esc_sql( $non_cached_ids );
|
||||
$order_ids_in = "'" . implode( "', '", $order_ids ) . "'";
|
||||
$raw_meta_data_array = $wpdb->get_results(
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
"SELECT post_id as object_id, meta_id, meta_key, meta_value
|
||||
FROM {$wpdb->postmeta}
|
||||
WHERE post_id IN ( $order_ids_in )
|
||||
ORDER BY post_id"
|
||||
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
);
|
||||
$raw_meta_data_collection = array_reduce(
|
||||
$raw_meta_data_array,
|
||||
function ( $collection, $raw_meta_data ) {
|
||||
if ( ! isset( $collection[ $raw_meta_data->object_id ] ) ) {
|
||||
$collection[ $raw_meta_data->object_id ] = array();
|
||||
}
|
||||
$collection[ $raw_meta_data->object_id ][] = $raw_meta_data;
|
||||
return $collection;
|
||||
},
|
||||
array()
|
||||
);
|
||||
WC_Order::prime_raw_meta_data_cache( $raw_meta_data_collection, 'orders' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the order type of a given item which belongs to WC_Order.
|
||||
*
|
||||
|
|
|
@ -47,12 +47,15 @@ class WC_Order_Refund_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT im
|
|||
*/
|
||||
public function delete( &$order, $args = array() ) {
|
||||
$id = $order->get_id();
|
||||
$parent_order_id = $order->get_parent_id();
|
||||
$refund_cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refunds' . $parent_order_id;
|
||||
|
||||
if ( ! $id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_delete_post( $id );
|
||||
wp_cache_delete( $refund_cache_key, 'orders' );
|
||||
$order->set_id( 0 );
|
||||
do_action( 'woocommerce_delete_order_refund', $id );
|
||||
}
|
||||
|
|
|
@ -283,6 +283,7 @@ abstract class WC_REST_CRUD_Controller extends WC_REST_Posts_Controller {
|
|||
$args['post_parent__in'] = $request['parent'];
|
||||
$args['post_parent__not_in'] = $request['parent_exclude'];
|
||||
$args['s'] = $request['search'];
|
||||
$args['fields'] = $this->get_fields_for_response( $request );
|
||||
|
||||
if ( 'date' === $args['orderby'] ) {
|
||||
$args['orderby'] = 'date ID';
|
||||
|
|
|
@ -2501,3 +2501,23 @@ function wc_is_running_from_async_action_scheduler() {
|
|||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return isset( $_REQUEST['action'] ) && 'as_async_request_queue_runner' === $_REQUEST['action'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Polyfill for wp_cache_get_multiple for WP versions before 5.5.
|
||||
*
|
||||
* @param array $keys Array of keys to get from group.
|
||||
* @param string $group Optional. Where the cache contents are grouped. Default empty.
|
||||
* @param bool $force Optional. Whether to force an update of the local cache from the persistent
|
||||
* cache. Default false.
|
||||
* @return array|bool Array of values.
|
||||
*/
|
||||
function wc_cache_get_multiple( $keys, $group = '', $force = false ) {
|
||||
if ( function_exists( 'wp_cache_get_multiple' ) ) {
|
||||
return wp_cache_get_multiple( $keys, $group, $force );
|
||||
}
|
||||
$values = array();
|
||||
foreach ( $keys as $key ) {
|
||||
$values[ $key ] = wp_cache_get( $key, $group, $force );
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ function wc_get_orders( $args ) {
|
|||
*
|
||||
* @since 2.2
|
||||
*
|
||||
* @param mixed $the_order Post object or post ID of the order.
|
||||
* @param mixed $the_order Post object or post ID of the order.
|
||||
*
|
||||
* @return bool|WC_Order|WC_Order_Refund
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Class WC_Order_Data_Store_CPT_Test.
|
||||
*/
|
||||
class WC_Order_Data_Store_CPT_Test extends WC_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
* Test that refund cache are invalidated correctly when refund is deleted.
|
||||
*/
|
||||
public function test_refund_cache_invalidation() {
|
||||
$order = WC_Helper_Order::create_order();
|
||||
|
||||
$refund = wc_create_refund(
|
||||
array(
|
||||
'order_id' => $order->get_id(),
|
||||
'reason' => 'testing',
|
||||
'amount' => 1,
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertNotWPError( $refund );
|
||||
|
||||
// Prime cache.
|
||||
$fetched_order = wc_get_orders(
|
||||
array(
|
||||
'post__in' => array( $order->get_id() ),
|
||||
'type' => 'shop_order',
|
||||
)
|
||||
)[0];
|
||||
|
||||
$refund_cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refunds' . $order->get_id();
|
||||
$cached_refunds = wp_cache_get( $refund_cache_key, 'orders' );
|
||||
|
||||
$this->assertEquals( $cached_refunds[0]->get_id(), $fetched_order->get_refunds()[0]->get_id() );
|
||||
|
||||
$refund->delete( true );
|
||||
|
||||
// Cache should be cleared now.
|
||||
$cached_refunds = wp_cache_get( $refund_cache_key, 'orders' );
|
||||
$this->assertEquals( false, $cached_refunds );
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue