From f3140889d5512fbfee5fe93beb093a1c3e44f347 Mon Sep 17 00:00:00 2001 From: Justin Shreve Date: Thu, 17 Nov 2016 03:16:07 -0800 Subject: [PATCH] Move meta handling saving/getting logic to a data store, and fix cache handling for meta to reduce queries. --- includes/abstracts/abstract-wc-data.php | 110 ++++++++---------- includes/abstracts/abstract-wc-product.php | 6 + includes/class-wc-coupon.php | 6 + .../data-stores/class-wc-meta-data-store.php | 99 ++++++++++++++++ .../class-wc-meta-data-store-interface.php | 17 +++ tests/unit-tests/coupon/data.php | 7 +- woocommerce.php | 1 + 7 files changed, 185 insertions(+), 61 deletions(-) create mode 100644 includes/data-stores/class-wc-meta-data-store.php create mode 100644 includes/data-stores/interfaces/class-wc-meta-data-store-interface.php diff --git a/includes/abstracts/abstract-wc-data.php b/includes/abstracts/abstract-wc-data.php index 58130a9819b..5311b3c9340 100644 --- a/includes/abstracts/abstract-wc-data.php +++ b/includes/abstracts/abstract-wc-data.php @@ -59,6 +59,12 @@ abstract class WC_Data { */ protected $data_store; + /** + * Contains a reference to the data store for meta handling. + * @var object + */ + protected $meta_data_store; + /** * Stores meta in cache for future reads. * A group must be set to to enable caching. @@ -98,7 +104,8 @@ abstract class WC_Data { * @param int|object|array $read ID to load from the DB (optional) or already queried data. */ public function __construct( $read = 0 ) { - $this->default_data = $this->data; + $this->default_data = $this->data; + $this->meta_data_store = WC_Data_Store::load( 'meta' ); } /** @@ -111,6 +118,26 @@ abstract class WC_Data { return $this->data_store; } + /** + * Get the meta type for this data type. + * + * @since 2.7.0 + * @return string + */ + public function get_meta_type() { + return $this->meta_type; + } + + /** + * Get the object ID field this data type. + * + * @since 2.7.0 + * @return string + */ + public function get_object_id_field_for_meta() { + return $this->object_id_field_for_meta; + } + /** * Returns the unique ID for this object. * @return int @@ -330,7 +357,7 @@ abstract class WC_Data { * @param int $mid Meta ID */ public function delete_meta_data_by_mid( $mid ) { - $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'id' ), $mid ); + $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'id' ), $mid ); if ( $array_keys ) { foreach ( $array_keys as $array_key ) { $this->meta_data[ $array_key ]->value = null; @@ -350,10 +377,12 @@ abstract class WC_Data { /** * Read Meta Data from the database. Ignore any internal properties. + * * @since 2.6.0 + * @param bool $force_read True to force a new DB read (and update cache). */ - public function read_meta_data() { - $this->meta_data = array(); + public function read_meta_data( $force_read = false ) { + $this->meta_data = array(); $cache_loaded = false; if ( ! $this->get_id() ) { @@ -361,37 +390,34 @@ abstract class WC_Data { } if ( ! empty( $this->cache_group ) ) { - $cache_key = WC_Cache_Helper::get_cache_prefix( $this->cache_group ) . $this->get_id(); - $cached_meta = wp_cache_get( $cache_key, $this->cache_group ); + $cache_key = WC_Cache_Helper::get_cache_prefix( $this->cache_group ) . 'object_meta_' . $this->get_id(); + } - if ( false !== $cached_meta ) { - $this->meta_data = $cached_meta; - $cache_loaded = true; + if ( ! $force_read ) { + if ( ! empty( $this->cache_group ) ) { + $cached_meta = wp_cache_get( $cache_key, $this->cache_group ); + if ( false !== $cached_meta ) { + $this->meta_data = $cached_meta; + $cache_loaded = true; + } } } if ( ! $cache_loaded ) { - global $wpdb; - $db_info = $this->get_db_info(); - $raw_meta_data = $wpdb->get_results( $wpdb->prepare( " - SELECT " . $db_info['meta_id_field'] . ", meta_key, meta_value - FROM " . $db_info['table'] . " - WHERE " . $db_info['object_id_field'] . "=%d AND meta_key NOT LIKE 'wp\_%%' ORDER BY " . $db_info['meta_id_field'] . " - ", $this->get_id() ) ); - + $raw_meta_data = $this->meta_data_store->read_meta( $this ); if ( $raw_meta_data ) { $raw_meta_data = array_filter( $raw_meta_data, array( $this, 'exclude_internal_meta_keys' ) ); foreach ( $raw_meta_data as $meta ) { $this->meta_data[] = (object) array( - 'id' => (int) $meta->{ $db_info['meta_id_field'] }, + 'id' => (int) $meta->meta_id, 'key' => $meta->meta_key, 'value' => maybe_unserialize( $meta->meta_value ), ); } - } - if ( ! empty( $this->cache_group ) ) { - wp_cache_set( $cache_key, $this->meta_data, $this->cache_group ); + if ( ! empty( $this->cache_group ) ) { + wp_cache_set( $cache_key, $this->meta_data, $this->cache_group ); + } } } } @@ -404,55 +430,21 @@ abstract class WC_Data { foreach ( $this->meta_data as $array_key => $meta ) { if ( is_null( $meta->value ) ) { if ( ! empty( $meta->id ) ) { - delete_metadata_by_mid( $this->meta_type, $meta->id ); + $this->meta_data_store->delete_meta( $this, $meta ); } } elseif ( empty( $meta->id ) ) { - $new_meta_id = add_metadata( $this->meta_type, $this->get_id(), $meta->key, $meta->value, false ); + $new_meta_id = $this->meta_data_store->add_meta( $this, $meta ); $this->meta_data[ $array_key ]->id = $new_meta_id; } else { - update_metadata_by_mid( $this->meta_type, $meta->id, $meta->value, $meta->key ); + $this->meta_data_store->update_meta( $this, $meta ); } } if ( ! empty( $this->cache_group ) ) { WC_Cache_Helper::incr_cache_prefix( $this->cache_group ); } - $this->read_meta_data(); - } - /** - * Table structure is slightly different between meta types, this function will return what we need to know. - * @since 2.6.0 - * @return array Array elements: table, object_id_field, meta_id_field - */ - protected function get_db_info() { - global $wpdb; - - $meta_id_field = 'meta_id'; // for some reason users calls this umeta_id so we need to track this as well. - $table = $wpdb->prefix; - - // If we are dealing with a type of metadata that is not a core type, the table should be prefixed. - if ( ! in_array( $this->meta_type, array( 'post', 'user', 'comment', 'term' ) ) ) { - $table .= 'woocommerce_'; - } - - $table .= $this->meta_type . 'meta'; - $object_id_field = $this->meta_type . '_id'; - - // Figure out our field names. - if ( 'user' === $this->meta_type ) { - $meta_id_field = 'umeta_id'; - } - - if ( ! empty( $this->object_id_field_for_meta ) ) { - $object_id_field = $this->object_id_field_for_meta; - } - - return array( - 'table' => $table, - 'object_id_field' => $object_id_field, - 'meta_id_field' => $meta_id_field, - ); + $this->read_meta_data( true ); } /** diff --git a/includes/abstracts/abstract-wc-product.php b/includes/abstracts/abstract-wc-product.php index 36859fef73a..8334f9c847b 100644 --- a/includes/abstracts/abstract-wc-product.php +++ b/includes/abstracts/abstract-wc-product.php @@ -27,6 +27,12 @@ class WC_Product extends WC_Abstract_Legacy_Product { */ protected $post_type = 'product'; + /** + * Cache group. + * @var string + */ + protected $cache_group = 'products'; + /** * Stores product data. * diff --git a/includes/class-wc-coupon.php b/includes/class-wc-coupon.php index bbb06d82965..4db4ac18bfd 100644 --- a/includes/class-wc-coupon.php +++ b/includes/class-wc-coupon.php @@ -74,6 +74,12 @@ class WC_Coupon extends WC_Legacy_Coupon { */ protected $meta_type = 'post'; + /** + * Cache group. + * @var string + */ + protected $cache_group = 'coupons'; + /** * Data stored in meta keys, but not considered "meta" for a coupon. * @since 2.7.0 diff --git a/includes/data-stores/class-wc-meta-data-store.php b/includes/data-stores/class-wc-meta-data-store.php new file mode 100644 index 00000000000..02351f7ad7f --- /dev/null +++ b/includes/data-stores/class-wc-meta-data-store.php @@ -0,0 +1,99 @@ +get_db_info( $object ); + return $wpdb->get_results( $wpdb->prepare( " + SELECT " . $db_info['meta_id_field'] . " as meta_id, meta_key, meta_value + FROM " . $db_info['table'] . " + WHERE " . $db_info['object_id_field'] . "=%d AND meta_key NOT LIKE 'wp\_%%' ORDER BY " . $db_info['meta_id_field'] . " + ", $object->get_id() ) ); + } + + /** + * Deletes a piece of meta. + * + * @since 2.7.0 + * @param WC_Data $object + * @param object $meta Object containing ->id, ->key, and possibily ->value. + */ + public function delete_meta( $object, $meta ) { + delete_metadata_by_mid( $object->get_meta_type(), $meta->id ); + } + + /** + * Adds meta. + * + * @since 2.7.0 + * @param WC_Data $object + * @param object $meta Object containing ->key and ->value + * @return int Meta ID + */ + public function add_meta( $object, $meta ) { + return add_metadata( $object->get_meta_type(), $object->get_id(), $meta->key, $meta->value, false ); + } + + /** + * Updates meta. + * + * @since 2.7.0 + * @param WC_Data $object + * @param object $meta Object containing ->id, ->key and ->value + */ + public function update_meta( $object, $meta ) { + update_metadata_by_mid( $object->get_meta_type(), $meta->id, $meta->value, $meta->key ); + } + + /** + * Table structure is slightly different between meta types, this function will return what we need to know. + * + * @since 2.7.0 + * @param WC_Data + * @return array Array elements: table, object_id_field, meta_id_field + */ + protected function get_db_info( &$object ) { + global $wpdb; + + $meta_id_field = 'meta_id'; // for some reason users calls this umeta_id so we need to track this as well. + $table = $wpdb->prefix; + + // If we are dealing with a type of metadata that is not a core type, the table should be prefixed. + if ( ! in_array( $object->get_meta_type(), array( 'post', 'user', 'comment', 'term' ) ) ) { + $table .= 'woocommerce_'; + } + + $table .= $object->get_meta_type() . 'meta'; + $object_id_field = $object->get_meta_type() . '_id'; + + // Figure out our field names. + if ( 'user' === $object->get_meta_type() ) { + $meta_id_field = 'umeta_id'; + } + + if ( ! empty( $object->get_object_id_field_for_meta() ) ) { + $object_id_field = $object->get_object_id_field_for_meta(); + } + + return array( + 'table' => $table, + 'object_id_field' => $object_id_field, + 'meta_id_field' => $meta_id_field, + ); + } + +} diff --git a/includes/data-stores/interfaces/class-wc-meta-data-store-interface.php b/includes/data-stores/interfaces/class-wc-meta-data-store-interface.php new file mode 100644 index 00000000000..8cc7a4f0e14 --- /dev/null +++ b/includes/data-stores/interfaces/class-wc-meta-data-store-interface.php @@ -0,0 +1,17 @@ +expected_doing_it_wrong = array_merge( $this->expected_doing_it_wrong, $legacy_keys ); $coupon = WC_Helper_Coupon::create_coupon(); - add_post_meta( $coupon->get_id(), 'test_coupon_field', 'testing', true ); + $coupon->add_meta_data( 'test_coupon_field', 'testing', true ); + $coupon->save_meta_data(); $coupon = new WC_Coupon( $coupon->get_id() ); $this->assertEquals( $coupon->get_id(), $coupon->id ); @@ -194,7 +195,9 @@ class WC_Tests_Coupon_Data extends WC_Unit_Test_Case { $coupon = WC_Helper_Coupon::create_coupon(); $coupon_id = $coupon->get_id(); $meta_value = time() . '-custom-value'; - add_post_meta( $coupon_id, 'test_coupon_field', $meta_value, true ); + $coupon->add_meta_data( 'test_coupon_field', $meta_value, true ); + $coupon->save_meta_data(); + $coupon = new WC_Coupon( $coupon_id ); $custom_fields = $coupon->get_meta_data(); $this->assertCount( 1, $custom_fields ); diff --git a/woocommerce.php b/woocommerce.php index d3f4dd5843f..e505fe33877 100644 --- a/woocommerce.php +++ b/woocommerce.php @@ -322,6 +322,7 @@ final class WooCommerce { include_once( WC_ABSPATH . 'includes/data-stores/abstract-wc-order-data-store-cpt.php' ); include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-data-store-cpt.php' ); include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-refund-data-store-cpt.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-meta-data-store.php' ); $this->query = new WC_Query(); $this->api = new WC_API();