All You Need to Know about Backward Compatibility

5 min read
389

Backward compatibility is a product property that allows for interoperability with an older legacy system.

WordPress CMS attentively cares about backward compatibility, and you can easily update your website from any version without risk. However, we can’t say the same about themes and plugins. Often, some functionality stops working after updating a plugin or theme. It’s disappointing and time-consuming.

The excellent tone is to deprecate code before removing or breaking changes. I would be thrilled if our developers in the WordPress community started to care about backward compatibility and help others update code smoothly, without ASAP fixes on the production, because a plugin removes a function you used in your theme. So, please share the article with your developer friends.

Also, don’t be afraid. Even very experienced developers have problems with backward compatibility if they have not encountered it in their work. This requires some changes in thinking and practice.

What Should I Do as a Developer?

Every function, class, constant, public method, or property of your plugin/theme can be reused elsewhere in another plugin/theme. Today, I released a new function in my plugin, and after the release, I can’t delete the function without breaking backward compatibility.

From the moment your code has been released, anyone can use the public interface of your application. In other words, if you changed your existing code, renamed the function, and created a new class, you must consider backward compatibility.

To keep backward compatibility means not making breaking changes. Instead, you have to add a deprecated message, and only after some time (half-year, a year, the next major release, etc.) can you break these changes and remove the code.

The following text is a cheat sheet for code deprecation.

The Difference Between Plain PHP and WordPress Deprecation

To deprecate something in PHP, you need to use the trigger_error function with a message and the E_USER_DEPRECATED error type:

trigger_error(
	sprintf(
		'Function %1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.'
		$function,
		$version,
		$replacement
	),
	E_USER_DEPRECATED
);

WordPress has a list of functions which prefer to use for a few reasons:

  1. All error messages have the same formatting.
  2. WordPress translates error messages if possible, making the error message user-friendly for non-English speakers.
  3. WordPress fires the error message ONLY if the WP_DEBUG constant is enabled.

List of WordPress deprecation functions:

  • _deprecated_function
  • _deprecated_argument
  • _deprecated_file
  • _deprecated_constructor
  • _deprecated_hook
  • apply_filters_deprecated
  • do_action_deprecated

Deprecation

Deprecation is the discouragement of using some terminology, feature, design, or practice, typically because it has been superseded or is no longer considered efficient or safe, without completely removing it or prohibiting its use.

Steps to deprecate something:

  1. Keep previous code working
  2. Let customers or developers know about future changes
  3. Suggest a replacement
  4. Remove the code in the next major version

Deprecate Functions

When removing, renaming, or changing a code with breaking changes, we need to process the following steps:

  1. Add the @deprecation tag to PHPDoc.
  2. Trigger and deprecation error at the beginning of the function.
  3. If possible, add a message with a replacement function, method, etc.
/**
 * @since 1.2.3
 * @deprecated 2.0.0
 */
function wppunk_old_function() {
	_deprecated_function( __FUNCTION__, '2.0.0 of the {Plugin Name} plugin' );
	// function code.

	// or with possible replacement.
	_deprecated_function( __FUNCTION__, '2.0.0 of the {Plugin Name} plugin', 'wppunk_new_function' );
	wppunk_new_function();
}

The deprecated function code must work as it worked before the changes. PR author and reviewer should attentively test the code and compare it with the production version.

Add your plugin or theme name to the deprecation message. It should help customers quickly guess which part of their website requires extra work.

Deprecate Arguments

Never change the arguments’ order. It makes the function/method a few times more complicated. If you want to change the order of the arguments, please, create a new function and deprecate the current one.

To deprecate an argument:

  1. Add a message you deprecated the argument to the @since tag.
  2. Rename the deprecated argument to $deprecated in the function body and PHPDoc.
  3. Add the null default value for the deprecated argument.
  4. Trigger deprecation message only when the argument is used.
/**
 * @since 1.2.3
 * @since 2.0.0 2nd argument was deprecated.
 *        
 * @param string $arg1       The first argument.
 * @param string $deprecated The second argument.
 */
function wppunk_function( $arg1, $deprecated = null ) {

    if ( $deprecated !== null ) {
        _deprecated_argument( __FUNCTION__, '2.0.0 of the {Plugin Name} plugin', 'The `$arg2` argument was deprecated.' );
    }

    // function code.
}

Deprecate Constants

Unfortunately, PHP doesn’t have a way to trigger an error when a constant is used. So, it would be nice to be careful when using constants.

To deprecate a constant, add the @deprecate tag to PHPDoc:

/**
 * @since 1.2.3
 * @deprecated 2.0.0
 */
define( 'WPPUNK_CONSTANT', 123 );

/**
 * @since 1.2.3
 * @deprecated 2.0.0
 */
const WPPUNK_CONSTANT_2 = 234;

Deprecate a Class

To deprecate a class:

  1. Add the @deprecate PHPDoc tag.
  2. Deprecate every property, constant, and method.
  3. If the class doesn’t have the __construct method, add and deprecate it. In this way, customers can see an error as soon as possible.
/**
 * @since 1.2.3
 * @deprecated 2.0.0
 */
class WPPunk_Old_Legacy {
	/**
	 * @since 1.2.3
	 * @deprecated 2.0.0
	 */
	public function __construct() {
		_deprecated_constructor( __CLASS__, '2.0.0 of the {Plugin Name} plugin' );
	}
}

Rename a Class

You don’t need to deprecate a class if you only rename the class. The class_alias function can help you here:

class_alias( 'WPPunkExample', 'WPPunk_Example' );

Deprecate Class Constants or Properties

By analogs with regular constants, we can’t trigger an error for the deprecation of class constants and static properties. So, we can only add the @deprecate PHPDoc tag:

class Example {
	/**
	 * @since 1.2.3
	 * @deprecated 2.0.0
	 */
	public const CONTANT = 123;

	/**
	 * @since 1.2.3
	 * @deprecated 2.0.0
	 */
	public static $property = 123;
}

We don’t need to deprecate contacts and static properties with the private access modifier.

Deprecate Object Properties

To deprecate object property:

  1. Make the property dynamic by removing property declaration (public $property1 at the top of the class)
  2. Add __get and __set magic methods and trigger errors ONLY for the property.

For instance, we need to deprecate the $property1 property:

class Example {
    public $property1 = 123;
}

We need to modify the class:

class Example {
    public function __set( $name, $value ) {

        if ( $name === 'property1' ) {
            _deprecated_argument(
                esc_html( __CLASS__ . '::' . $name ),
                '2.0.0 of the {Plugin Name} plugin'
            );
        }

        $this->{$name} = $value;
    }


    public function __get( $name ) {
        if ( $name === 'property1' ) {
            _deprecated_argument(
                esc_html( __CLASS__ . '::' . $name ),
                '2.0.0 of the {Plugin Name} plugin'
            );
        }

        return property_exists( $this, $name ) ? $this->{$name} : null;
    }
}

Deprecate Class and Object Methods

To deprecate class or object method:

  1. Add the @deprecate tag to PHPDoc
  2. Trigger and deprecation error at the beginning of the method.
class Example {
	/**
	 * @since 1.2.3
	 * @deprecated 2.0.0
	 */
	public function not_used_method() {

		_deprecated_function( __METHOD__, '2.0.0 of the {Plugin Name} plugin' );

		// method code.
	}

	/**
	 * @since 1.2.3
	 * @deprecated 2.0.0
	 */
	public static function not_used_static_method() {

		_deprecated_function( __METHOD__, '2.0.0 of the {Plugin Name} plugin' );

		// method code.
	}
}

Deprecate Hooks

Hooks are specific to the WordPress mechanism but are the most crucial. You have to carefully deprecate hooks before removing them.

  1. Keep previous code working
  2. Let customers or developers know about future changes
  3. Suggest a replacement
  4. Remove the code in the next major version

To deprecate a hook:
1. Replace the hook with do_action_deprecated, apply_filters_deprecated, or _deprecated_hook

2. Add the @deprecated PHPDoc tag.

For instance, we have the next action and filter:

/**
 * @since 1.2.3
 */
do_action( 'wppunk_some_action_with_bad_name', $arg1, $arg2, $arg3, $arg4 );

/**
 * @since 1.2.3
 */
$arg1 = apply_filters( 'wppunk_some_filter_with_bad_name', $arg1, $arg2, $arg3, $arg4 );

We should replace it with the following:

/**
 * @since 1.2.3
 * @deprecated 2.0.0
 */
do_action_deprecated( 'wppunk_some_action_with_bad_name', [ $arg1, $arg2, $arg3, $arg4 ], '2.0.0 of the {Plugin Name} plugin', 'wppunk_some_action_with_good_name' );

/**
 * @since 2.0.0
 */
do_action( 'wppunk_some_action_with_good_name', $arg1, $arg2, $arg3, $arg4 );

/**
 * @since 1.2.3
 * @deprecated 2.0.0
 */
$arg1 = apply_filters_deprecated( 'wppunk_some_filter_with_bad_name', [ $arg1, $arg2, $arg3, $arg4 ], '2.0.0 of the {Plugin Name} plugin', 'wppunk_some_filter_with_good_name' );

/**
 * @since 2.0.0
 */
$arg1 = apply_filters( 'wppunk_some_filter_with_good_name', $arg1, $arg2, $arg3, $arg4 );
If the content was useful, share it on social networks

Leave a Reply

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

Subscribe to news. I promise not to spam :)
Follow me, don't be shy