Friday's adventure with Backward Compatibility

2 mins
112

If you work with open source, in the environment world, these are public plugins, and you have a lot of users, but backward compatibility is tedious.

But today, there was a hilarious case when it was not an obvious situation. Let’s deep into it.

The problem

I have a class that I’ve decided to mark as deprecated and write a new code that more readable (I think in such a way, but it’s a lie) and used Composer’s autoload.

The Really_Old class located at the root of the plugin at the class-really-old.php file. I’ve prepared a simplified example:

class Really_Old {

	public $some_property;
	public $some_property_2;

	public function __construct() {
		add_action( 'some_action', [ $this, 'some_method' ] );
		add_action( 'some_action_2', [ $this, 'some_method_2' ] );
		// ...
	}
	
	public function some_method() {
		// ...
	}

	public function some_method_2() {
		// ...
	}
}

new Really_Old();

Not the worst code that I’ve ever seen but with a few problems. Executable construct, a lot of public methods and properties, and created instance directly at the file. This is the way.

This class running into the bootstrap file really-awesome-plugin.php:

// ...
require __DIR__ . '/old-class.php';
// ...

Deprecate it

I added the deprecation function to the each public and protected methods:

_deprecated_function( __CLASS__ . '->' . __METHOD__ . '( ... )', '2.0.0' );

Also, added the code for the public and protected properties via magic methods __get() and __set():

class Really_Old {
	//...
	public function __get( $name ) {
		if ( in_array( $name, [ 'some_property', 'some_property_2'], true ) ) {
			trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
				sprintf( 'Property %1$s was deprecated', esc_attr( $name ) ),
				E_USER_DEPRECATED
			);
		}

		return $this->$name;
	}
	public function __set( $name, $value ) {

		if ( in_array( $name, [ 'some_property', 'some_property_2'], true ) ) {
			trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
				sprintf( 'Property %1$s was deprecated', esc_attr( $name ) ),
				E_USER_DEPRECATED
			);
		}

		return $this->$name = $value;
	}
	// ...
}

What the next?

Let’s think about backward compatibility and how it would be involved to the customers:

  • I mustn’t move this file because this file is executable, which means if anybody included this file, this class would run.
  • The file name and class name are contrary to PSR standards, which means I can’t use Composer’s autoloading by PSR.
  • This file located directly at the root directory, which means I can’t use Composer’s classmap autoload.
  • I can’t include this file directly or using Composers’ files autoload because this file is executable, and after including the customer will see a lot of deprecated messages.

File connection solution

We can use composer autoloading directly at the PHP file. Let’s update our really-awesome-plugin.php file:

...
$loader = require_once __DIR__ . '/vendor/autoload.php';
$loader->addClassMap(
	[
		'Really_Old' => __DIR__ . '/class-really-old.php',
	]
);
...

Actually, the backward compatibility problem was solved. When customers try to work with the legacy code, they will see the deprecated messages. Otherwise, they will not know about our changes. What is backward compatibility to you? Pain in the ass or God’s gift?

Leave a Reply

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