Managing the cart using the REST API after WooCommerce 3.6

Published on: Updated: April 17, 2019

WooCommerce 3.6 lands on 17 April, and with it come some fairly significant changes from a development point of view.

One of the performance improvements included in this update is to remove the loading of the cart and other frontend code for REST requests. Without going into all the details, the previous checks for loading cart functions meant they were loaded for all request types (including the REST API). From the 3.6 release, the cart and associated functions will only load on the frontend.

This is a worthwhile improvement in and of itself, and hats off to the WooCommerce team for all the great work they’ve done on this release. However, there is a downside – if you have a plugin which interacts with the cart in any way via the REST API, you’ll suddenly find your code no longer works in 3.6!

Our WooCommerce quick view plugin does just that. We allow all product types to be added to the cart from a modal window, and we use the REST API to facilitate that. Without wishing to rewrite the plugin to use an alternate method (e.g. AJAX) we found a workaround for this post 3.6, which may be helpful to other plugin developers.

First the good news: the WooCommerce autoloader takes care of including any classes referenced during your REST request. So even though the cart class is no longer included upfront, if you reference WC_Cart in your code, then the autoloader will load it for you.

However, you’ll probably also need the cart functions under /woocommerce/includes/wc-cart-functions.php. This won’t be autoloaded, so we’ll need to include that:

include_once WC_ABSPATH . 'includes/wc-cart-functions.php';

Since the cart often writes notices (e.g. when adding to the cart), you should include those too:

include_once WC_ABSPATH . 'includes/wc-notice-functions.php';

Next we need to initialise the session, as this is now only done on the frontend:

if ( null === WC()->session ) {
    $session_class = apply_filters( 'woocommerce_session_handler', 'WC_Session_Handler' );
    WC()->session = new $session_class();
    WC()->session->init();
}

This code is basically a straight copy of what WooCommerce itself does during WooCommerce->init().

Next, we need to initialise the customer and cart objects as these will also be null for REST requests:

if ( null === WC()->customer ) {
    WC()->customer = new WC_Customer( get_current_user_id(), true );
}
if ( null === WC()->cart ) {
    WC()->cart = new WC_Cart();
}

Finally, we need to load the cart from the session. Even though we’ve created the cart at this point, the cart contents will be empty.

The cart storage is handled by the WC_Cart_Session class. If you look at the code, you’ll see that the initial read of the cart is done on the wp_loaded hook. As we are executing a REST request which runs during parse_request, this hook will have already happened.

So we need to force the cart to refresh its contents from the session. The simplest way to do that is to call the get_cart() method:

WC()->cart->get_cart();

In our plugin, we put all this together inside a check_prerequisites function which runs right at the start of our ‘Add to cart’ REST request. Here’s the full code:

/**
 * Check any prerequisites required for our add to cart request.
 */
private function check_prerequisites() {
	if ( defined( 'WC_ABSPATH' ) ) {
		// WC 3.6+ - Cart and notice functions are not included during a REST request.
		include_once WC_ABSPATH . 'includes/wc-cart-functions.php';
		include_once WC_ABSPATH . 'includes/wc-notice-functions.php';
	}

	if ( null === WC()->session ) {
		$session_class = apply_filters( 'woocommerce_session_handler', 'WC_Session_Handler' );

		//Prefix session class with global namespace if not already namespaced
		if ( false === strpos( $session_class, '\\' ) ) {
			$session_class = '\\' . $session_class;
		}

		WC()->session = new $session_class();
		WC()->session->init();
	}

	if ( null === WC()->customer ) {
		WC()->customer = new \WC_Customer( get_current_user_id(), true );
	}

	if ( null === WC()->cart ) {
		WC()->cart = new \WC_Cart();

		// We need to force a refresh of the cart contents from session here (cart contents are normally refreshed on wp_loaded, which has already happened by this point).
		WC()->cart->get_cart();
	}
}

Filed under: WordPress Development