Data store classes act as a bridge between WooCommerce's data CRUD classes (`WC_Product`, `WC_Order`, `WC_Customer`, etc) and the database layer. With the database logic separate from data, WooCommerce becomes more flexible. The data stores shipped with WooCommerce core (powered by WordPress' custom posts system and some custom tables) can be swapped out for a different database structure, type, or even be powered by an external API.
This guide will walk through the structure of a data store class, how to create a new data store, how to replace a core data store, and how to call a data store from your own code.
The examples in this guide will look at the [`WC_Coupon`](https://github.com/woocommerce/woocommerce/blob/dcecf0f22890f3cd92fbea13a98c11b2537df2a8/includes/class-wc-coupon.php#L19) CRUD data class and [`WC_Coupon_Data_Store_CPT`](https://github.com/woocommerce/woocommerce/blob/dcecf0f22890f3cd92fbea13a98c11b2537df2a8/includes/data-stores/class-wc-coupon-data-store-cpt.php), an implementation of a coupon data store using WordPress custom post types. This is how coupons are currently stored in WooCommerce.
The important thing to know about `WC_Coupon` or any other CRUD data class when working with data stores is which props (properties) they contain. This is defined in the [`data`](https://github.com/woocommerce/woocommerce/blob/dcecf0f22890f3cd92fbea13a98c11b2537df2a8/includes/class-wc-coupon.php#L26) array of each class.
## Structure
Every data store for a CRUD object should implement the `WC_Object_Data_Store_Interface` interface.
`WC_Object_Data_Store_Interface` includes the following methods:
*`create`
*`read`
*`update`
*`delete`
*`read_meta`
*`delete_meta`
*`add_meta`
*`update_meta`
The `create`, `read`, `update`, and `delete` methods should handle the CRUD logic for your props:
*`create` should create a new entry in the database. Example: Create a coupon.
*`read` should query a single entry from the database and set properties based on the response. Example: Read a coupon.
*`update` should make changes to an existing entry. Example: Update or edit a coupon.
*`delete` should remove an entry from the database. Example: Delete a coupon.
All data stores must implement handling for these methods.
In addition to handling your props, other custom data can be passed. This is considered `meta`. For example, coupons can have custom data provided by plugins.
The `read_meta`, `delete_meta`, `add_meta`, and `update_meta` methods should be defined so meta can be read and managed from the correct source. In the case of our WooCommerce core classes, we define them in `WC_Data_Store_WP` and then use the same code for all of our data stores. They all use the WordPress meta system. You can redefine these if meta should come from a different source.
Your data store can also implement other methods to replace direct queries. For example, the coupons data store has a public `get_usage_by_user_id` method. Data stores should always define and implement an interface for the methods they expect, so other developers know what methods they need to write. Put another way, in addition to the `WC_Object_Data_Store_Interface` interface, `WC_Coupon_Data_Store_CPT` also implements `WC_Coupon_Data_Store_Interface`.
## Replacing a data store
Let's look at how we would replace the `WC_Coupon_Data_Store_CPT` class with a `WC_Coupon_Data_Store_Custom_Table` class. Our examples will just provide stub functions, instead of a full working solution. Imagine that we would like to store coupons in a table named `wc_coupons` with the following columns:
class WC_Coupon_Data_Store_Custom_Table extends WC_Data_Store_WP implements WC_Coupon_Data_Store_Interface, WC_Object_Data_Store_Interface {
}
```
Note that we implement the main `WC_Object_Data_Store_Interface` interface as well as the ` WC_Coupon_Data_Store_Interface` interface. Together, these represent all the methods we need to provide logic for.
We would then define the CRUD handling for these properties:
*@param array $args Array of args to pass to the delete method.
*/
public function delete( &$coupon, $args = array() ) {
// A lot of objects in WordPress and WooCommerce support
// the concept of trashing. This usually is a flag to move the object
// to a "recycling bin". Since coupons support trashing, your layer should too.
// If an actual delete occurs, set the coupon ID to 0.
$args = wp_parse_args( $args, array(
'force_delete' => false,
) );
$id = $coupon->get_id();
if ( $args['force_delete'] ) {
// Delete Query
$coupon->set_id( 0 );
do_action( 'woocommerce_delete_coupon', $id );
} else {
// Trash Query
do_action( 'woocommerce_trash_coupon', $id );
}
}
```
We are extending `WC_Data_Store_WP` so our classes will continue to use WordPress' meta system.
As mentioned in the structure section, we are responsible for implementing the methods defined by `WC_Coupon_Data_Store_Interface`. Each interface describes the methods and parameters it accepts, and what your function should do.
A coupons replacement would look like the following:
*@param string $used_by Either user ID or billing email
*/
public function increase_usage_count( &$coupon, $used_by = '' ) {
}
/**
* Decrease usage count for current coupon.
*
*@param WC_Coupon
*@param string $used_by Either user ID or billing email
*/
public function decrease_usage_count( &$coupon, $used_by = '' ) {
}
/**
* Get the number of uses for a coupon by user ID.
*
*@param WC_Coupon
*@param id $user_id
*@return int
*/
public function get_usage_by_user_id( &$coupon, $user_id ) {
}
/**
* Return a coupon code for a specific ID.
*@param int $id
*@return string Coupon Code
*/
public function get_code_by_id( $id ) {
}
/**
* Return an array of IDs for for a specific coupon code.
* Can return multiple to check for existence.
*@param string $code
*@return array Array of IDs.
*/
public function get_ids_by_code( $code ) {
}
```
Once all the data store methods are defined and logic written, we need to tell WooCommerce to load our new class instead of the built-in class. This is done using the `woocommerce_data_stores` filter. An array of data store slugs is mapped to default WooCommerce classes. Example:
Our class would then be loaded by WooCommerce core, instead of `WC_Coupon_Data_Store_CPT`.
## Creating a new data store
### Defining a new product type
Does your extension create a new product type? Each product type has a data store in addition to a parent product data store. The parent store handles shared properties like name or description and the child handles more specific data.
For example, the external product data store handles "button text" and "external URL". The variable data store handles the relationship between parent products and their variations.
Check out [this walkthrough](https://developer.woocommerce.com/2017/02/06/wc-2-7-extension-compatibility-examples-3-bookings/) for more information on this process.
If your extension introduces a new database table, new custom post type, or some new form of data not related to products, orders, etc, then you should implement your own data store.
Your data store should still implement `WC_Object_Data_Store_Interface` and provide the normal CRUD functions. Your data store should be the main point of entry for interacting with your data, so any other queries or operations should also have methods.
The [shipping zone data store](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/includes/data-stores/class-wc-shipping-zone-data-store.php) serves as a good example for a "simple" data store using a custom table. The coupons code is a good example for a data store using a custom post type.
All you need to do to register your data store is add it to the `woocommerce_data_stores` filter:
Currently, several WooCommerce screens still rely on WordPress to list objects. Examples of this include coupons and products.
If you replace data via a data store, some parts of the existing UI may fail. An example of this may be lists of coupons when using the `type` filter. This filter uses meta data, and is in turn passed to WordPress which runs a query using the `WP_Query` class. This cannot handle data outside of the regular meta tables (Ref #19937). To get around this, usage of `WP_Query` would need to be deprecated and replaced with custom query classes and functions.