Drop CacheHydration class and do it directly.

Primary reason for dropping the cache hydration is that seems like we can acheive the same results without it, so no need to add this additional complexity to our code.
This commit is contained in:
vedanshujain 2020-09-16 16:58:32 +05:30
parent 975783b2b4
commit a5fb3178f0
8 changed files with 131 additions and 384 deletions

View File

@ -544,8 +544,34 @@ abstract class WC_Data {
wc_doing_it_wrong( 'get_meta_cache_key', 'ID needs to be set before fetching a cache key.', '4.4.0' );
return false;
}
return WC_Cache_Helper::get_cache_prefix( $this->cache_group ) . WC_Cache_Helper::get_cache_prefix( 'object_' . $this->get_id() ) . 'object_meta_' . $this->get_id();
return self::generate_meta_cache_key( $this->get_id(), $this->cache_group );
}
/**
* Generate cache key from id and group.
*
* @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.
*
* @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.
@ -578,6 +604,10 @@ abstract class WC_Data {
}
$raw_meta_data = $cache_loaded ? $cached_meta : $this->data_store->read_meta( $this );
if ( $cache_loaded ) {
// Filter the raw meta data again, in case we cached in an earlier version where filter conditions were different.
$raw_meta_data = $this->data_store->filter_raw_data( $this, $raw_meta_data );
}
if ( $raw_meta_data ) {
$this->set_from_raw_meta_data( $raw_meta_data );

View File

@ -10,8 +10,6 @@
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Models\CacheHydration;
/**
* Order factory class
*/
@ -46,43 +44,6 @@ class WC_Order_Factory {
}
}
/**
* Get order using hydration object to initialize some of the data. Falls back to get_order method in case of error.
*
* @param mixed $order Order ID to get.
* @param CacheHydration $cache_hydration Object to initialize caches from.
*
* @return WC_Order|bool
*/
public static function get_order_from_hydration( $order, $cache_hydration ) {
$order_id = self::get_order_id( $order );
if ( ! $order_id ) {
return false;
}
$order_type = WC_Data_Store::load( 'order' )->get_order_type( $order );
$classname = self::get_class_name_for_order( $order_id, $order_type );
try {
// Lets create an empty object first to see if data store supports post hydration.
// We do this code gymnastics because we don't know if data store for this class (which could be modified by a filter) supports loading from WP_Post or not.
$order_object = new $classname( new stdClass() );
$order_object->set_id( $order_id );
$data_store = $order_object->get_data_store();
$data_store->read_from_hydration( $order_object, $cache_hydration );
if ( $order_object->get_object_read() ) {
return $order_object;
} else {
return new $classname( $order_id );
}
} catch ( Exception $e ) {
// Fallback to `wc_get_order` because looks like this class does not support initializing from hydration.
wc_caught_exception( $e, __FUNCTION__, array( $order_id, $classname ) );
return self::get_order( $order_id ); // TODO: Is this fallback necessary?
}
}
/**
* Helper method to fetch class name for an order.
*

View File

@ -1792,16 +1792,6 @@ class WC_Order extends WC_Abstract_Order {
|--------------------------------------------------------------------------
*/
/**
* Prime refunds cache, can be used before get_refunds to skip DB query.
*
* @param array $refunds array of WC_Order_Refund objects.
*/
public function prime_refunds_cache( $refunds ) {
$cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refunds' . $this->get_id();
wp_cache_set( $cache_key, $refunds, $this->cache_group );
}
/**
* Get order refunds.
*

View File

@ -6,7 +6,6 @@
*/
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Models\CacheHydration;
if ( ! defined( 'ABSPATH' ) ) {
exit;
@ -115,45 +114,6 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
}
}
/**
* Initialize objects using hydration.
*
* @param WC_Order $order Order object.
* @param CacheHydration $hydration Hydration object.
*/
public function read_from_hydration( $order, $hydration ) {
$post_object = $hydration->get_data_for_object( 'post', $order->get_id() );
$order->set_id( $post_object->ID );
$order->set_defaults();
$this->read_from_post( $order, $post_object );
if ( $hydration->has_collection( 'raw_meta_data', $post_object->ID ) ) {
$raw_meta_data = $hydration->get_collection_for_object( 'raw_meta_data', $post_object->ID );
$order->set_meta_data_from_raw_data( $raw_meta_data );
}
if ( $hydration->has_collection( 'order-items', $post_object->ID ) ) {
$this->read_items_from_hydration( $order, $hydration );
}
if ( $hydration->has_collection( 'refunds' ) ) {
$refunds = $hydration->get_data_for_object( 'refunds', $post_object->ID ) ?? array();
$order->prime_refunds_cache( $refunds );
}
$order->set_object_read( true );
/**
* In older versions, discounts may have been stored differently.
* Update them now so if the object is saved, the correct values are
* stored. @todo When meta is flattened, handle this during migration.
*/
if ( version_compare( $order->get_version( 'edit' ), '2.3.7', '<' ) && $order->get_prices_include_tax( 'edit' ) ) {
$order->set_discount_total( (float) get_post_meta( $order->get_id(), '_cart_discount', true ) - (float) get_post_meta( $order->get_id(), '_cart_discount_tax', true ) );
}
}
/**
* Helper function to read data from post_object.
*
@ -179,30 +139,6 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
$this->read_order_data( $order, $post_object );
}
/**
* Initialize objects from Hydration.
*
* @param WC_Order $order Order object.
* @param CacheHydration $cache_hydration Hydration Object.
*/
private function read_items_from_hydration( $order, $cache_hydration ) {
$items = $cache_hydration->get_collection_for_object( 'order-items', $order->get_id() );
if ( ! is_array( $items ) ) {
return;
}
$order_items = array();
foreach ( $items as $order_item ) {
$class_name = WC_Order_Factory::get_order_item_class( $order_item );
$order_item->metadata = $cache_hydration->get_collection_for_object( 'order-item-meta-data', $order_item->order_item_id );
if ( $class_name && class_exists( $class_name ) ) {
$order_items[] = new $class_name( $order_item );
}
}
}
/**
* Method to update an order in the database.
*

View File

@ -9,8 +9,6 @@ if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use Automattic\WooCommerce\Models\CacheHydration;
/**
* WC Order Data Store: Stored in CPT.
*
@ -871,6 +869,7 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
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 );
}
@ -895,128 +894,143 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
*
* @return array Orders.
*/
protected function compile_orders( $order_ids, $query_vars, $query ) {
private function compile_orders( $order_ids, $query_vars, $query ) {
if ( empty( $order_ids ) ) {
return array();
}
$orders = array();
$cache_hydration = new CacheHydration();
$raw_meta_data = self::fetch_raw_meta_cache_for_orders( $order_ids );
$cache_hydration->add_to_collection( $raw_meta_data, 'raw_meta_data', 'object_id' );
if ( isset( $query_vars['type'] ) && 'shop_order' === $query_vars['type'] ) {
$refunds = wc_get_orders(
array(
'type' => 'shop_order_refund',
'post_parent__in' => $order_ids,
'limit' => - 1,
)
);
$cache_hydration->add_to_collection( $refunds, 'refunds', 'get_parent_id' );
$refunds_by_order_id = $cache_hydration->get_collection( 'refunds' );
foreach ( $order_ids as $order_id ) {
if ( ! isset( $refunds_by_order_id[ $order_id ] ) ) {
// Set empty rows where refund is empty.
$cache_hydration->append_collection_for_object( 'refunds', $order_id, array() );
}
}
}
self::prime_order_item_caches_for_orders( $order_ids, $cache_hydration, true );
// 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 ) {
// Lets do some hydrations so that we don't have to fetch data from DB later.
$cache_hydration->set_data_for_object( 'post', $post->ID, $post );
$orders[] = wc_get_order( $post, $cache_hydration );
$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'] ) && is_array( $query_vars['fields'] ) && ! in_array( 'refunds', $query_vars['fields'] ) ) {
return;
}
$refunds = wc_get_orders(
array(
'type' => 'shop_order_refund',
'post_parent__in' => $order_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 ( $order_ids as $order_id ) {
$cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refunds' . $order_id;
$refunds = array();
if ( isset( $order_refunds[ $order_id ] ) ) {
$refunds = $order_refunds[ $order_id ];
}
wp_cache_set( $cache_key, $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. (Optionally) order-item meta.
*
* @param array $order_ids Array of order ids.
* @param CacheHydration $cache_hydration Cache hydration object.
* @param bool $prime_meta_cache Whether to also prime order item meta cache.
* @param array $order_ids Order Ids to prime cache for.
* @param array $query_vars Query vars for the query.
*/
private static function prime_order_item_caches_for_orders( $order_ids, $cache_hydration, $prime_meta_cache ) {
private function prime_order_item_caches_for_orders( $order_ids, $query_vars ) {
global $wpdb;
if ( isset( $query_vars['fields'] ) && is_array( $query_vars['fields'] ) ) {
$line_items = array(
'line_items',
'shipping_lines',
'fee_lines',
'coupon_lines',
);
if ( 0 > count( array_intersect( $line_items, $query_vars['fields'] ) ) ) {
return;
}
}
$order_ids = esc_sql( $order_ids );
$order_id_string = implode( ',', $order_ids );
$order_items_collection = $wpdb->get_results(
$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 ( $order_id_string ) ORDER BY order_item_id;"
);
foreach ( $order_items_collection as $item ) {
foreach ( $order_items as $item ) {
wp_cache_set( 'item-' . $item->order_item_id, $item, 'order-items' );
}
$cache_hydration->add_to_collection( $order_items_collection, 'order-items', 'order_id' );
foreach ( $cache_hydration->get_collection( 'order-items' ) as $order_id => $order_items ) {
wp_cache_set( 'order-items-' . $order_id, $order_items, 'orders' );
}
if ( $prime_meta_cache ) {
$order_item_ids = wp_list_pluck( $order_items_collection, 'order_item_id' );
self::prime_order_item_meta_caches_for_orders( $order_item_ids, $cache_hydration );
$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' );
}
$order_item_ids = wp_list_pluck( $order_items, 'order_item_id' );
update_meta_cache( 'order_item', $order_item_ids );
}
/**
* Prime cache for order item meta and store in CacheHydration object.
* Prime cache for raw meta data for orders in bulk. Difference between this and WP build in metadata is that this method also fetches `meta_id` field which we use and cache it.
*
* @param array $order_item_ids Array of order ids.
* @param CacheHydration $cache_hydration Cache hydration object.
* @param array $order_ids Order Ids to prime cache for.
* @param array $query_vars Query vars for the query.
*/
private static function prime_order_item_meta_caches_for_orders( $order_item_ids, CacheHydration $cache_hydration ) {
global $wpdb;
if ( ! empty( $order_item_ids ) ) {
$order_item_ids_string = implode( ',', $order_item_ids );
// Next two lines fetches same data, but as of now its very complicated to use same results in both of these.
// This is because `update_meta_cache` discards `meta_id` which we need while priming order-item-meta-data.
update_meta_cache( 'order_item', $order_item_ids );
$items_meta_data = $wpdb->get_results(
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"SELECT order_item_id, meta_id, meta_key, meta_value
FROM {$wpdb->prefix}woocommerce_order_itemmeta
WHERE order_item_id in ( $order_item_ids_string )
ORDER BY meta_id"
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
);
$cache_hydration->add_to_collection( $items_meta_data, 'order-item-meta-data', 'order_item_id' );
} else {
$cache_hydration->add_to_collection( array(), 'order-item-meta-data', 'order_item_id' );
}
}
/**
* Fetch metadata for posts in bulk.
*
* @param array $order_ids Array of order IDs.
*
* @return array|object|null Raw meta data for orders.
*/
private static function fetch_raw_meta_cache_for_orders( $order_ids ) {
private function prime_raw_meta_cache_for_orders( $order_ids, $query_vars ) {
global $wpdb;
$order_ids = esc_sql( $order_ids );
$order_ids_in = "'" . implode( $order_ids, "', '" ) . "'";
$raw_meta_data = $wpdb->get_results(
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$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
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
);
return $raw_meta_data;
$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' );
}
/**

View File

@ -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';

View File

@ -10,8 +10,6 @@
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Models\CacheHydration;
/**
* Standard way of retrieving orders based on certain parameters.
*
@ -73,19 +71,15 @@ function wc_get_orders( $args ) {
*
* @since 2.2
*
* @param mixed $the_order Post object or post ID of the order.
* @param CacheHydration $cache_hydration Optional, cache_hydration to initialize cache data from if available.
* @param mixed $the_order Post object or post ID of the order.
*
* @return bool|WC_Order|WC_Order_Refund
*/
function wc_get_order( $the_order = false, $cache_hydration = null ) {
function wc_get_order( $the_order = false ) {
if ( ! did_action( 'woocommerce_after_register_post_type' ) ) {
wc_doing_it_wrong( __FUNCTION__, 'wc_get_order should not be called before post types are registered (woocommerce_after_register_post_type action)', '2.5' );
return false;
}
if ( is_a( $cache_hydration, CacheHydration::class ) ) {
return WC()->order_factory->get_order_from_hydration( $the_order, $cache_hydration );
}
return WC()->order_factory->get_order( $the_order );
}

View File

@ -1,179 +0,0 @@
<?php
/**
* Glorified key-value store with some helper methods to manage cache objects.
*
* @package Automattic\WooCommerce\Models
*/
namespace Automattic\WooCommerce\Models;
/**
* Class CacheHydration. Used for storing data used for priming caches later.
*/
class CacheHydration {
/**
* Internal object for storing singular data for different objects and keys.
*
* @var array
*/
protected $data_hydration = array();
/**
* Internal object for storing collections for different objects and keys.
*
* @var array
*/
protected $collection_hydration = array();
/**
* Set singular data for an object.
*
* @param string $key Name to identify data.
* @param string|int $object_id ID of object.
* @param mixed $value Value to store for object ID.
*/
public function set_data_for_object( $key, $object_id, $value ) {
if ( ! $this->has_data( $key ) ) {
$this->data_hydration[ $key ] = array();
}
$this->data_hydration[ $key ][ $object_id ] = $value;
}
/**
* Returns data for key, object ID.
*
* @param string $key Name to identify data.
* @param string|int $object_id ID of object.
*
* @return mixed|null Value to store for object ID. Null if not present.
*/
public function get_data_for_object( $key, $object_id ) {
if ( ! isset( $this->data_hydration[ $key ] ) ) {
return null;
}
if ( ! isset( $this->data_hydration[ $key ][ $object_id ] ) ) {
return null;
}
return $this->data_hydration[ $key ][ $object_id ];
}
/**
* Check if data exists for key, object ID.
*
* @param string $key Name to identify data.
* @param string|int|null $object_id ID of object. If null then only key will be checked.
*
* @return bool|null True if value stored for key or object ID. Null if not present.
* */
public function has_data( $key, $object_id = null ) {
if ( null === $object_id ) {
return key_exists( $key, $this->data_hydration );
}
return key_exists( $key, $this->data_hydration ) && isset( $this->data_hydration[ $key ][ $object_id ] );
}
/**
* Check if collection exists for key, object ID.
*
* @param string $key Name to identify collection.
* @param string|int|null $object_id ID of object. If null then only key will be checked.
*
* @return bool|null True if value stored for key or object ID. Null if not present.
* */
public function has_collection( $key, $object_id = null ) {
if ( null === $object_id ) {
return key_exists( $key, $this->collection_hydration );
}
return key_exists( $key, $this->collection_hydration ) && isset( $this->collection_hydration[ $key ][ $object_id ] );
}
/**
* Adds rows to a collection.
*
* @param array $data Actual collection data. Should be an array of std objects.
* @param string $key Name to identify collection.
* @param string $index_function Name of the index key.
* */
public function add_to_collection( $data, $key, $index_function ) {
if ( ! isset( $this->collection_hydration[ $key ] ) ) {
$this->collection_hydration[ $key ] = array();
}
foreach ( $data as $row ) {
if ( method_exists( $row, $index_function ) ) {
$index = $row->$index_function();
} else {
$index = $row->$index_function;
}
$this->append_collection_for_object( $key, $index, $row );
}
}
/**
* Set row to a collection.
*
* @param string $key Name to identify collection.
* @param string|int $object_id ID of object.
* @param object $row std object for row.
* */
public function set_collection_for_object( $key, $object_id, $row ) {
if ( ! isset( $this->collection_hydration[ $key ] ) ) {
$this->collection_hydration[ $key ] = array();
}
$this->collection_hydration[ $key ][ $object_id ] = array( $row );
}
/**
* Append row to a collection.
*
* @param string $key Name to identify collection.
* @param string|int $object_id ID of object.
* @param object $row std object for row.
* */
public function append_collection_for_object( $key, $object_id, $row ) {
if ( ! isset( $this->collection_hydration[ $key ] ) ) {
$this->collection_hydration[ $key ] = array();
}
if ( ! isset( $this->collection_hydration[ $key ][ $object_id ] ) ) {
$this->collection_hydration[ $key ][ $object_id ] = array();
}
$this->collection_hydration[ $key ][ $object_id ][] = $row;
}
/**
* Get full collection for a key.
*
* @param string $key Collection key.
*
* @return mixed|null Collection Array (or null if key is not present).
*/
public function get_collection( $key ) {
if ( ! isset( $this->collection_hydration[ $key ] ) ) {
return null;
}
return $this->collection_hydration[ $key ];
}
/**
* Get collection for object and key.
*
* @param string $key Collection key.
* @param int|string $object_id Object ID.
*
* @return mixed|null Collection (or null if key or object ID is not present).
*/
public function get_collection_for_object( $key, $object_id ) {
if ( ! isset( $this->collection_hydration[ $key ] ) || ! isset( $this->collection_hydration[ $key ][ $object_id ] ) ) {
return null;
}
return $this->collection_hydration[ $key ][ $object_id ];
}
}