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:
- All error messages have the same formatting.
- WordPress translates error messages if possible, making the error message user-friendly for non-English speakers.
- 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:
- Keep previous code working
- Let customers or developers know about future changes
- Suggest a replacement
- 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:
- Add the
@deprecation
tag to PHPDoc. - Trigger and deprecation error at the beginning of the function.
- 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:
- Add a message you deprecated the argument to the
@since
tag. - Rename the deprecated argument to
$deprecated
in the function body and PHPDoc. - Add the
null
default value for the deprecated argument. - 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:
- Add the
@deprecate
PHPDoc tag. - Deprecate every property, constant, and method.
- 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:
- Make the property dynamic by removing property declaration (
public $property1
at the top of the class) - 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:
- Add the
@deprecate
tag to PHPDoc - 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.
- Keep previous code working
- Let customers or developers know about future changes
- Suggest a replacement
- 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 );