2017-02-06

Bookings is a complex extension which extends WooCommerce product types to add it’s own ‘Booking’ product type. Due to this, it’s a good example to CRUDify and implement data stores, both new concepts in 2.7.

Upon reviewing the current class, it’s obvious there is some room for refactoring due to the class for example rendering HTML. Ideally this should be avoided to keep the focus of the class data-only. The ideal structure is:

Bookings class – extends WC_Product and handles booking product data getters and setters.

Bookings data store – extends the core data stores to handle the storing of the booking class data to the database.

Functions/Display classes to handle HTML output.

Additionally, the extension needs to continue to be compatible with current 2.6.x versions of WooCommerce.

In testing, the extension actually works fine under 2.7, albeit with some notices such as:

These could have simply been patched on a case by case basis, but for Bookings we’ve chosen to fully CRUDify it which I’ll demonstrate in this post.

Making a home for deprecated methods

First since I intend to move HTML output functions out of the class, I need somewhere to place my deprecated methods for sake of tidyness. To do this, I created a WC_Legacy_Product_Booking class which extends WC_Product, and then made the original WC_Product_Booking class extend the legacy class so those methods are still available.

Defining the data store

Data stores handle the communication with the database and the actual CRUD actions. First I defined my data-store class:

Then I registered it via the WooCommerce filter woocommerce_data_stores in the main class constructor:

And the method:

Getters and setters

Next I went through all the methods and collected the names of all of the booking specific meta keys where data is stored, and defined them in the class without their _wc_booking_ prefix. For each I added a basic getter and setter method.

For example, one item of meta data was named . I defined this in an array and added a getter and setter like this:

In this example, qty prop defaults to 1, and we use get_prop/set_prop to get and set the values (this is part of WooCommerce’s data classes and handles formatting, filtering, and changes). I repeated this for all props.

Reading props via the data store class

After declaring the props, getters, and setters, we need to implement a way to get the data into the props when the product is loaded. We do this via the data store.

First I defined a list of meta keys and how they translate to the new props (getters and setters):

In the above example, the meta key is _wc_booking_qty and the props are set_qty and get_qty so I defined qty.

Next I extended one of the methods the core product class defines to load meta data; read_product_data. In this, I call the parent read (it’s a product and thus should inherit everything the parent product loads), and then load all booking specific meta data.

Saving props to the database

The data-store class also handles saving the props to the database on demand. The custom-post-type version of our data stores calls a helper method named update_post_meta which saves all the meta data to the database after the post has been created. We can override this method to run our own custom meta data saving.

This is looping over our props and updating the meta key values with the prop values.

Replacing the WP Admin save logic

WP Admin saves booking data using a series of update_post_meta calls in the meta box. With CRUD we can swap this out to instead set the props, and then call save on our CRUD object.

This is how it was handled with meta directly:

Not only can we switch to prop setters here, we can also lessen the amount of formatting of values needed since this is handled by the setters themselves. We still need to prepare some values in a way the setters can understand, but this can be handled on a case by case basis.

The above function, compatible with WC 2.6.x, was hooked into the process product meta action:

WC 2.7 (beta 2) has a new action which fires after props are set, but before save, which passes the product object. This is a perfect place to set additional props and have them all save at the same time.

Hooking into this action is done like this:

And then the callback checks the product type before running save logic:

Actions pass variables by reference, so we can actually set props directly on `$product`.

For the save code, we just need to set props using the setter methods added earlier.

For backwards compatibility, we can call this on the old meta action shown earlier and reuse the above method, calling save manually.

Replacing get_post_meta calls in form fields

The post meta boxes have fields which allow users to edit product settings. These typically look something like:

In this example, WooCommerce would load meta called _wc_booking_requires_confirmation. In other cases, value can be manually defined and is usually a get_post_meta call.

To be compatible with CRUD, we actually want to load the values from the product objects directly using the getters.

In the new example, $bookable_product is something I loaded like this:

This ensures it’s of WC_Product_Booking type so I have access to bookings methods.

As for the method call to get_requires_confirmation, I pass edit context to the method so that the value is unfiltered.

Optimising code with CRUD

Working through the code, I noticed lots of places which could benefit with CRUD usage. One good example was the duplicate product logic:

I love the fact that we can use the CRUD system to make this a breeze:

Fixing other deprecation notices

Working through my debug logs I fixed deprecation calls with conditional logic, such as this:

which fixed the notices:

Fixing AJAX select2 inputs

2.7 updated Select2 to version 4 meaning some ajax inputs which use hidden input boxes will no longer work. Example:

These need to be switched to a real select box for v4 compatibility, so I did this with a conditional like so:

Making data stores and objects compatible with 2.6.x

This turned out to be the most challenging aspect of the development process so I will warn you in advance supporting CRUD in 2.6.x and 2.7.x is possible but complex. If the plugin is not mission critical you could consider setting a minimum requirement of 2.7.x when live, but we couldn’t do this with bookings. So here is what I had to do:

I removed all implements from the data-store and CRUD classes since the interfaces are not necessarily required and are not present in 2.6.x.

Instead of extending WC_Data, I duplicated the 2.7.x version of WC_Data, called it WC_Bookings_Data. and extended that instead.

I have 2 versions of WC_Bookings_Data loaded conditionally – 1 with all the methods, and 1 which just extends WC_Data for 2.7.x since that code is not required there.

I copied across data store dependencies into bookings and only load them if using a version lower than 2.7.x.

WC_Data_Exception

WC_Data_Store_WP

WC_Data_Store

WC_Product_Data_Store_CPT

Since 2.6.x does not have full CRUD objects for products, I decided to have my CRUD implementation ONLY handle meta data when running 2.6.x for bookable products.

I added a proxy class for the bookable product type to load either a legacy class (which contained all data store methods) or the regular WC_Product class. This code looked like this:

I had to do this since PHP does not support multiple inheritance and traits are 5.4+.

Wrapping up

I applied the same structure to bookable resources, bookings themselves, and person types; implementing data stores where it made sense.

Then it was a case of:

ensuring any meta calls or get_posts function calls were swapped for CRUD methods and new methods in the data store to keep that logic together in one place.

fixing all other deprecated function and arg calls (checking the debug log for these).

triple checking 2.6.x backwards compatibility.

The next version of bookings will be 2.7.x ready and fully backwards compatible <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f642.png" alt="

Show more