Implement WC_Data_Store and related code & tests.

This commit is contained in:
Justin Shreve 2016-11-08 01:39:47 -08:00
parent cd2a4e89b2
commit 1797c76a14
15 changed files with 387 additions and 28 deletions

View File

@ -27,12 +27,26 @@ abstract class WC_Data {
*/
protected $data = array();
/**
* Extra data for this object. Name value pairs (name + default value).
* Used as a standard way for sub classes (like product types) to add
* additional information to an inherited class.
* @var array
*/
protected $extra_data = array();
/**
* Set to _data on construct so we can track and reset data if needed.
* @var array
*/
protected $default_data = array();
/**
* Contains a reference to the data store for this class.
* @var object
*/
protected $data_store;
/**
* Stores meta in cache for future reads.
* A group must be set to to enable caching.
@ -83,31 +97,28 @@ abstract class WC_Data {
return $this->id;
}
/**
* Creates new object in the database.
*/
public function create() {}
/**
* Read object from the database.
* @param int ID of the object to load.
*/
public function read( $id ) {}
/**
* Updates object data in the database.
*/
public function update() {}
/**
* Updates object data in the database.
*/
public function delete() {}
public function delete( $force_delete = false ) {
if ( $this->data_store ) {
$this->data_store->delete( $this, $force_delete );
}
}
/**
* Save should create or update based on object existance.
*/
public function save() {}
public function save() {
if ( $this->data_store ) {
if ( $this->get_id() ) {
$this->data_store->update( $this );
} else {
$this->data_store->create( $this );
}
return $this->get_id();
}
}
/**
* Change data to JSON format.
@ -125,6 +136,26 @@ abstract class WC_Data {
return array_merge( array( 'id' => $this->get_id() ), $this->data, array( 'meta_data' => $this->get_meta_data() ) );
}
/**
* Returns array of expected data keys for this object.
*
* @since 2.7.0
* @return array
*/
public function get_data_keys() {
return array_keys( $this->data );
}
/**
* Returns all "extra" data keys for an object (for sub objects like product types).
*
* @since 2.7.0
* @return array
*/
public function get_extra_data_keys() {
return array_keys( $this->extra_data );
}
/**
* Filter null meta values from array.
* @return bool
@ -400,7 +431,7 @@ abstract class WC_Data {
/**
* Set all props to default values.
*/
protected function set_defaults() {
public function set_defaults() {
$this->data = $this->default_data;
}

View File

@ -272,7 +272,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
* Delete data from the database.
* @since 2.7.0
*/
public function delete() {
public function delete( $force_delete = false ) {
wp_delete_post( $this->get_id() );
}

View File

@ -277,7 +277,7 @@ if ( ! defined( 'ABSPATH' ) ) {
* Remove a payment token from the database.
* @since 2.6.0
*/
public function delete() {
public function delete( $force_delete = false ) {
global $wpdb;
$this->read( $this->get_id() ); // Make sure we have a token to return after deletion
$wpdb->delete( $wpdb->prefix . 'woocommerce_payment_tokens', array( 'token_id' => $this->get_id() ), array( '%d' ) );

View File

@ -752,7 +752,7 @@ class WC_Coupon extends WC_Legacy_Coupon {
* Delete coupon from the database.
* @since 2.7.0
*/
public function delete() {
public function delete( $force_delete = false ) {
wp_delete_post( $this->get_id() );
do_action( 'woocommerce_delete_coupon', $this->get_id() );
$this->set_id( 0 );

View File

@ -1176,7 +1176,7 @@ class WC_Customer extends WC_Legacy_Customer {
* Delete a customer.
* @since 2.7.0
*/
public function delete() {
public function delete( $force_delete = false ) {
if ( ! $this->get_id() ) {
return;
}

View File

@ -0,0 +1,131 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Data Store.
*
* @since 2.7.0
* @version 2.7.0
* @category Class
* @author WooThemes
*/
class WC_Data_Store {
/**
* Contains an instance of the data store class that we are working with.
*/
private $instance = null;
/**
* Contains an array of default WC supported data stores.
* Format of object name => class name.
* Example: 'product' => 'WC_Product_Data_Store_CPT'
* You can aso pass something like product_<type> for product stores and
* that type will be used first when avaiable, if a store is requested like
* this and doesn't exist, then the store would fall back to 'product'.
* Ran through `woocommerce_data_stores`.
*/
private $stores = array(
);
/**
* Contains the name of the current data store's class name.
*/
private $current_class_name = '';
/**
* Tells WC_Data_Store which object (coupon, product, order, etc)
* store we want to work with.
*
* @param string $object_type Name of object.
*/
public function __construct( $object_type ) {
$this->stores = apply_filters( 'woocommerce_data_stores', $this->stores );
// If this object type can't be found, check to see if we can load one
// level up (so if product_type isn't found, we try product).
if ( ! array_key_exists( $object_type, $this->stores ) ) {
$pieces = explode( '_', $object_type );
$object_type = $pieces[0];
}
if ( array_key_exists( $object_type, $this->stores ) ) {
$store = apply_filters( 'woocommerce_' . $object_type . '_data_store', $this->stores[ $object_type ] );
if ( ! class_exists( $store ) ) {
throw new Exception( __( 'Invalid data store.', 'woocommerce' ) );
}
$this->current_class_name = $store;
$this->instance = new $store;
} else {
throw new Exception( __( 'Invalid data store.', 'woocommerce' ) );
}
}
/**
* Loads a data store for us or returns null if an invalid store.
*
* @param string $object_type Name of object.
* @since 2.7.0
*/
public static function load( $object_type ) {
try {
return new WC_Data_Store( $object_type );
} catch ( Exception $e ) {
return null;
}
}
/**
* Returns the class name of the current data store.
*
* @since 2.7.0
* @return string
*/
public function get_current_class_name() {
return $this->current_class_name;
}
/**
* Reads an object from the data store.
*
* @since 2.7.0
* @param WC_Data
*/
public function read( &$data ) {
$this->instance->read( $data );
}
/**
* Create an object in the data store.
*
* @since 2.7.0
* @param WC_Data
*/
public function create( &$data ) {
$this->instance->create( $data );
}
/**
* Update an object in the data store.
*
* @since 2.7.0
* @param WC_Data
*/
public function update( &$data ) {
$this->instance->update( $data );
}
/**
* Delete an object from the data store.
*
* @since 2.7.0
* @param WC_Data
* @param bool $force_delete True to permently delete, false to trash.
*/
public function delete( &$data, $force_delete = false ) {
$this->instance->delete( $data, $force_delete );
}
}

View File

@ -249,7 +249,7 @@ class WC_Order_Item extends WC_Data implements ArrayAccess {
* Delete data from the database.
* @since 2.7.0
*/
public function delete() {
public function delete( $force_delete = false ) {
if ( $this->get_id() ) {
global $wpdb;
do_action( 'woocommerce_before_delete_order_item', $this->get_id() );

View File

@ -102,7 +102,7 @@ class WC_Order_Refund extends WC_Abstract_Order {
* Delete data from the database.
* @since 2.7.0
*/
public function delete() {
public function delete( $force_delete = false ) {
wp_delete_post( $this->get_id(), true );
}

View File

@ -107,7 +107,7 @@ class WC_Shipping_Zone extends WC_Data {
* Delete a zone.
* @since 2.6.0
*/
public function delete() {
public function delete( $force_delete = false ) {
if ( $this->get_id() ) {
global $wpdb;
$wpdb->delete( $wpdb->prefix . 'woocommerce_shipping_zone_methods', array( 'zone_id' => $this->get_id() ) );

View File

@ -0,0 +1,16 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Shared logic for post/CPT data stores.
*
* @version 2.7.0
* @category Class
* @author WooThemes
*/
class WC_Data_Store_CPT {
}

View File

@ -0,0 +1,38 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Data Store Interface
*
* @version 2.7.0
* @category Interface
* @author WooThemes
*/
interface WC_Object_Data_Store {
/**
* Method to create a new record of a WC_Data based object.
* @param WC_Data
*/
public function create( &$data );
/**
* Method to read a record. Creates a new WC_Data based object.
* @param WC_Data
*/
public function read( &$data );
/**
* Updates a record in the database.
* @param WC_Data
*/
public function update( &$data );
/**
* Deletes a record from the database.
* @param WC_Data
* @param bool $force_delete True to permently delete, false to trash.
*/
public function delete( &$data, $force_delete );
}

View File

@ -0,0 +1,36 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC Dummy Data Store: CPT.
*
* Used to test swapping out data stores.
*
* @version 2.7.0
* @category Class
* @author WooThemes
*/
class WC_Dummy_Data_Store_CPT implements WC_Object_Data_Store {
public function create( &$data ) { }
public function read( &$data ) { }
public function update( &$data ) { }
public function delete( &$data, $force_delete = false ) { }
}
/**
* WC Dummy Data Store: Custom Table.
*
* Used to test swapping out data stores.
*
* @version 2.7.0
* @category Class
* @author WooThemes
*/
class WC_Dummy_Data_Store_Custom_Table implements WC_Object_Data_Store {
public function create( &$data ) { }
public function read( &$data ) { }
public function update( &$data ) { }
public function delete( &$data, $force_delete = false ) { }
}

View File

@ -164,7 +164,7 @@ class WC_Mock_WC_Data extends WC_Data {
/**
* Simple delete.
*/
public function delete() {
public function delete( $force_delete = false ) {
if ( 'user' === $this->meta_type ) {
wp_delete_user( $this->get_id() );
} else {

View File

@ -0,0 +1,104 @@
<?php
/**
* Data Store Tests
* @package WooCommerce\Tests\Product
* @since 2.7.0
*/
class WC_Tests_Data_Store extends WC_Unit_Test_Case {
/**
* Make sure WC_Data_Store returns an exception if we try to load a data
* store that doesn't exist.
*
* @since 2.7.0
*/
function test_invalid_store_throws_exception() {
try {
$product_store = new WC_Data_Store( 'bogus' );
} catch ( Exception $e ) {
$this->assertEquals( $e->getMessage(), 'Invalid data store.' );
return;
}
$this->fail( 'Invalid data store exception not correctly raised.' );
}
/**
* Make sure ::load returns null if an invalid store is found.
*
* @since 2.7.0
*/
function test_invalid_store_load_returns_null() {
$product_store = WC_Data_Store::load( 'product-test' );
$this->assertNull( $product_store );
}
/**
* Make sure we can swap out stores.
*
* @since 2.7.0
*/
function test_store_swap() {
$this->load_dummy_store();
$store = new WC_Data_Store( 'dummy' );
$this->assertEquals( 'WC_Dummy_Data_Store_CPT', $store->get_current_class_name() );
add_filter( 'woocommerce_dummy_data_store', array( $this, 'set_dummy_store' ) );
$store = new WC_Data_Store( 'dummy' );
$this->assertEquals( 'WC_Dummy_Data_Store_Custom_Table', $store->get_current_class_name() );
add_filter( 'woocommerce_dummy_data_store', array( $this, 'set_default_dummy_store' ) );
}
/**
* Test to see if `first_second ``-> returns to `first` if unregistered.
*
* @since 2.7.0
*/
function test_store_sub_type() {
$this->load_dummy_store();
$store = WC_Data_Store::load( 'dummy_sub' );
$this->assertEquals( 'WC_Dummy_Data_Store_CPT', $store->get_current_class_name() );
}
// Helper Functions
/**
* Loads two dummy data store classes that can be swapt out for each other. Adds to the `woocommerce_data_stores` filter.
*
* @since 2.7.0
*/
function load_dummy_store() {
include_once( dirname( dirname( dirname( __FILE__ ) ) ) . '/framework/class-wc-dummy-data-store.php' );
add_filter( 'woocommerce_data_stores', array( $this, 'add_dummy_data_store' ) );
}
/**
* Adds a default class for the 'dummy' data store.
*
* @since 2.7.0
*/
function add_dummy_data_store( $stores ) {
$stores['dummy'] = 'WC_Dummy_Data_Store_CPT';
return $stores;
}
/**
* Helper function/filter to swap out the default dummy store for a different one.
*
* @since 2.7.0
*/
function set_dummy_store( $store ) {
return 'WC_Dummy_Data_Store_Custom_Table';
}
/**
* Helper function/filter to swap out the 'dummy' store for the default one.
*
* @since 2.7.0
*/
function set_default_product_store( $store ) {
return 'WC_Dummy_Data_Store_CPT';
}
}

View File

@ -268,7 +268,6 @@ final class WooCommerce {
include_once( WC_ABSPATH . 'includes/class-wc-api.php' ); // API Class
include_once( WC_ABSPATH . 'includes/class-wc-auth.php' ); // Auth Class
include_once( WC_ABSPATH . 'includes/class-wc-post-types.php' ); // Registers post types
include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-data.php' ); // WC_Data for CRUD
include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-payment-token.php' ); // Payment Tokens
include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-product.php' ); // Products
include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-order.php' ); // Orders
@ -285,6 +284,10 @@ final class WooCommerce {
include_once( WC_ABSPATH . 'includes/class-wc-cache-helper.php' ); // Cache Helper
include_once( WC_ABSPATH . 'includes/class-wc-https.php' ); // https Helper
include_once( WC_ABSPATH . 'includes/class-wc-data-store.php' ); // WC_Data_Store for CRUD
include_once( WC_ABSPATH . 'includes/data-stores/interface-wc-object-data-store.php' );
include_once( WC_ABSPATH . 'includes/data-stores/class-wc-data-store-cpt.php' );
if ( defined( 'WP_CLI' ) && WP_CLI ) {
include_once( WC_ABSPATH . 'includes/class-wc-cli.php' );
}