Deprecating plugin functions and hooks (and what we did in WooCommmerce)

Sometimes code needs to change; without doing so you can end up with a non-consistent, bloated mess. When changing things such as functions and hooks however, you do have to consider backwards compatibility so that code which relies on the old things doesn’t just stop breaking without explanation.

In WooCommerce major releases we often have to deal with this problem – in this post I’ll explain how to deprecate code, and how we dealt with it whilst developing 2.1.

How to deprecate code with a warning

There are 3 deprecated functions in WordPress core for telling the user something is being done wrong (when WP_DEBUG is enabled). These are _deprecated_function, _deprecated_argument and _deprecated_file.

You can pass each the name of the function/arg/file and the version in which it was deprecated. With files and functions you can also pass in the replacement (if it exists) and this will also inform the user. Args and files can also be passed some additional explanation via a message (I’ve been pushing for this to be added to deprecated_function for 10 months too, but I’m not making much headway -.-).

[php]_deprecated_function( $function, $version, $replacement );[/php]

[php]_deprecated_argument( $function, $version, $message );[/php]

[php]_deprecated_file( $file, $version, $replacement = null, $message = ” );[/php]

Here is an example in context from WooCommerce:

[php]_deprecated_argument( ‘WC_Checkout::process_checkout()’, ‘2.1’, ‘The “shiptobilling” field is deprecated. The template files are out of date’ );[/php]

If triggered this will output:

NOTICE: WC_Checkout::process_checkout() was called with an argument that is <strong>deprecated</strong> since version 2.1! The “shiptobilling” field is deprecated. The template files are out of date

Deprecating WC functions

To keep things tidy, we moved most deprecated functions to wc-deprecated-functions.php where we declared the old function with a notice and a call to the new function.

Here is an example from that file:

[php]function woocommerce_readfile_chunked( $file, $retbytes = true ) {
_deprecated_function( ‘woocommerce_readfile_chunked’, ‘2.1’, ‘WC_Download_Handler::readfile_chunked()’ );
return WC_Download_Handler::readfile_chunked( $file, $retbytes );
}[/php]

As you can see, this calls the new method, meaning old code won’t break (for now). If the user has WP_DEBUG on, they will be notified.

Since they are also in a single file, when we do remove them (most likely after major 2 point releases) they are easy to locate.

Deprecating WC methods

Methods need to be kept in the same class and cannot be moved. The setup is similar to the functions though. Here is an example from the WooCommerce main class:

[php]public function force_ssl( $content ) {
_deprecated_function( ‘Woocommerce->force_ssl’, ‘2.1’, ‘WC_HTTPS::force_https_url’ );
return WC_HTTPS::force_https_url( $content );
}[/php]

Depreciating WC filter hooks

When filter names change, there isn’t a built in way to handle this. In 2.1, I built a simple filter mapper, to map the old filter’s hooked in functions to the new filter. This prevents hooked in code from breaking.

Here is a sample:


/**
* Handle renamed filters
*/
global $wc_map_deprecated_filters;
$wc_map_deprecated_filters = array(
'woocommerce_cart_item_class' => 'woocommerce_cart_table_item_class'
);
foreach ( $wc_map_deprecated_filters as $new => $old )
add_filter( $new, 'woocommerce_deprecated_filter_mapping' );
function woocommerce_deprecated_filter_mapping( $data, $arg_1 = '', $arg_2 = '', $arg_3 = '' ) {
global $wc_map_deprecated_filters;
$filter = current_filter();
if ( isset( $wc_map_deprecated_filters[ $filter ] ) )
if ( has_filter( $wc_map_deprecated_filters[ $filter ] ) ) {
$data = apply_filters( $wc_map_deprecated_filters[ $filter ], $data, $arg_1, $arg_2, $arg_3 );
_deprecated_function( 'The ' . $wc_map_deprecated_filters[ $filter ] . ' filter', '2.1', $filter );
}
return $data;
}

view raw

gistfile1.php

hosted with ❤ by GitHub

In the above example, we’re mapping woocommerce_cart_table_item_class to the new filter called woocommerce_cart_item_class. To map more, they are just defined as key/value pairs in the $wc_map_deprecated_filters array. This appears to work well.

Soft deprecation

Some functions were just renamed for consistency in 2.1, losing the woocommerce_ prefix for a shorter wc_. To avoid making too much work for devs we decided to “soft deprecate” them. That is, define them as an alias for now, so going forward the new function names are used, and then in a few versions deprecate them. It would be better (for us) to remove them all now, but to ease the transition we thought it best to keep the old ones there without notices.

All in the name of progress

Changes to functions can be frustrating for developers, but in order to progress they are a necessary evil. Using deprecated functions at least keep code functioning giving developers a chance to update their code, and shouldn’t affect live sites anyway (because live stores should never have debugging visible!).


Posted

in

by

Comments

4 responses to “Deprecating plugin functions and hooks (and what we did in WooCommmerce)”

  1. DrewAPicture avatar

    It’s interesting that you guys handle deprecated filters this way.

    I’d personally like to see WP core implement something similar instead of just leaving deprecated hooks where they lay, or in some cases, straight up removing deprecated hooks under the knowledge that add_action or add_filter will fail silently.

    I don’t know that doing it with a global would be the most appealing, but at least in some way *handling* deprecated hooks instead of effectively replacing their logic further down the procedure line or removing it altogether for the reasons mentioned above.

    1. Guest avatar
      Guest

      I don’t know that doing it with a global would be the most appealing, but at least in some way *handling* deprecated hooks instead of effectively replacing their logic further down the procedure line or removing it altogether for the reasons mentioned above.

    2. mikejolley avatar
      mikejolley

      It’s difficult. Our solution isn’t perfect, passing arguments to the filters for instance is a little bit hacky, but it works. A core solution to do this for us would be welcomed.

  2. Luke avatar
    Luke

    Hey Mike – thanks for this. It was a good help. Found a better way of passing arguments through, though. Here’s something a little less hacky:

    https://gist.github.com/lukecarbis/145f5b02d0aad8707808

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.