WordPress plugins/themes and backwards compatibility

Pippin Williamson of pippinsplugins fame did a presentation on backwards compatibility and WordPress plugins at LoopConf. In the presentation he showed a couple of examples of where developers have a tendency to break backwards compatibility. In this article I will give my thoughts on what he said and also give some ideas on how you can prevent breaking things in the first place.

Hide how things are stored

In the presentation Pippin gives an example of get_post_meta and changing the key used for storage or how its stored all together like a taxonomy or separate table.

1

$meta_values = get_post_meta( $post_id, $key, $single );

This is not information that should be available to the developer. The developer should not know how things are stored or even have to care. It is not something they in most cases need to know. The value should be accessed using a wrapper function or be a property of a class. Some developers make the case that developers should not know about the classes themselves and argue that only basic structures should be used to communicate data, such as stdClass, arrays and so on. Instead of returning a class with properties you return an array representation of the information. A minimal class without functionality and with public properties can also be used. This has the advantage over arrays and stdClass of providing autocompletion when using code editors. They are also easier to document.

Don’t encourage direct inclusion of files

If it's possible, try to use autoloading of your classes. You can also add a wrapper function that includes a file containing functions since functions cannot be autoloaded. The files should preferably be included by default on load. Doing require_once on all the plugin files is not a recommended approach and its not something developers should do on your plugin files. If you have to use require_once to include files you have a bad structure in your plugin or theme. Having a wrapper function to include files and also discourage use of require_once? Yes, well I like paradoxes. Run tortoise run.

Don’t hard code any HTML code in your plugin

Pippin says to be careful with HTML structure in plugins since you can break extensions later on if you change the tags and so forth. However there are ways to minimize this risk. One way is to provide filters for wrapping tags etc if you are for example showing a list.

1

2

3

4

5

6

7

8

9

$wrapper = apply_filters('list-wrapper',['parent'=>'ul', 'tag'=>'li'], $context)

$outline=strip_tags($wrapper['parent']);

$tag=strip_tags($wrapper['tag']);

echo "<$outline>";

foreach ($items as $item)

  echo "<$tag>", 'asdfasdf', "</$tag>";

 

do_action('list', $wrapper, $context);

echo "</$outline>";

Tag inclusion can be used in conjunction with actions as well.

1

2

3

4

5

6

7

8

9

10

<ul>

<?php foreach ($items as $item) ?>

<li>somestuff</li>

<?php ?>

<?php do_action('list-item', $context,

  '<li class="'.implode(' ',apply_filters('li-classes',array(),$context)).' %s">',

  '</li>);

//do_action('list-item', $context, '<li class="%s">%s</li>');

 ?>

</ul>

Of course the above method does not work if you go from one column of data to multiple columns of data. Such as going from a list with a single value to a table with a two column layout with more data displayed. One way to handle variable columns is to use a placeholder system. This works as long as the objects to display all adhere to the same contract. I personally loath plugins that print hard coded HTML. It's my data. I should decide how it's displayed.

Don’t hard code HTML etc in JavaScript files

JavaScript is usually a must in all modern sites. However, how you structure your JavaScript can have huge effects on both extendability and maintaining compatibility. This point is in the same vein as the previous one. Usually WordPress themes and plugins make use of jQuery however they use it badly. Or rather they make use of JavaScript badly. In order to make it easier for you and your eventual end users try to separate out all DOM manipulation from your events. So if you have for example a button “Add to list” which stores an item on a list on the server, do not include any DOM manipulation in that event. Separate the responsibilities.

1

2

3

4

5

6

//<ul class='somelist'></ul>

$('.add').on('click', func(e)

  var data = retrieve_data('list-entry-form');

  button.disable();

  $.post(url, data).success(func() $('.somelist').append('<li>asdasd</li>').done(func()button.enable(););

);

If a theme, plugin or you have changed the tag for that list to a div now you have breaking HTML. Instead try to make use of events so you can better separate the various parts of your code to separate files. It also enables any themes or plugins to remove your default DOM manipulating code and replace it with their own. Of course this is a two-edged sword. To separate responsibilities you can make use of jQuery trigger() and on() functions.

1

2

3

4

5

6

7

8

9

10

11

12

13

//my-actions.js handling posting data and triggering events

$('.add').on('click', function(e)

  var data = retrieve_data('list-entry-form');

  var button = this;

  $(document).trigger('list-item-adding', [button,data]);

  $.post(url, data).success(function()

    $(document).trigger('list-item-added', [button,data]);

  ).done(function() $(document).trigger('list-item-done', [button,data]););

);

//my-subscribers.js subscribing to events and manipulating DOM

$(document).on('list-item-adding', func(button, data) $(button).disable());

$(document).on('list-item-added', func(button, data) $('somelist').append('<li>somestuff</li>');

$(document).on('list-item-done', func(button, data) $(button).enable());

If you want you can even make the class .add customizable using a filter and making use of the wp_localize_script function to print the class ID to use for the on Click event. Separating the responsibilities is also possible if you want to make use of translatable JavaScript strings.

Write unit and functional tests

The fastest way to find out if things have changed and might affect end users and other developers is breaking tests. If you have written tests for your code and they fail when you make changes you really need to think about the changes you are making. This is good both for your JavaScript code and for your PHP code. Tests can be a lifesaver and decrease the amount of support requests you get.

Write acceptance tests

To make sure things are working when it comes to JavaScript, classnames, structure write acceptance tests. This is useful both for the website and for the WordPress admin. You need to make sure your settings page behaves correctly as well. There are numerous frameworks for this type of testing. You can use PHPUnit with Selenium, Codeception or why not use a JavaScript framework like CasperJS. When it comes to the frontend tests they can be made available to other developers as well so they can test their plugins of your plugin or theme.

Let the developers know

Write good documentation of your code and how its meant to be used. If certain class properties are meant to be read only use @property-read. If a class or function is meant only to be used by the plugin itself mark it as internal. If developers disregards the instructions that is not your concern. Provide the developers with a best practice extension so they can see how things are meant to work.

Incremental changes

If you foresee breaking changes you can do as Pippins says in the talk and introduce the changes version by version. It all depends on what kind of changes you are making. This of course relies on the end users actually updating the plugin to each version in the first place.

When backwards compatibility can’t be kept

Depending on how big the changes are you might be better off forking your own plugin or theme if the changes are really big. If you do not want to do that and still want to make bigger changes don’t rely on the default update functionality in WordPress. Hook into it and give users a step by step form to complete inorder for the upgrade to be made. You know what version they are upgrading from and can adjust accordingly. You can also present a screen with the changes before upgrading that they need to accept. This of course relies on you having added these capabilities to your plugin beforehand.

In conclusion

The main thing is to write tests for your code and try not to hardcode things. Remaining backwards capable and having a good structure in your code goes, from my perspective, hand in hand. If you have good structure you can also make bigger changes if the need arises. Writing more complex functionality sometimes requires you to do some thinking before you get going. Especially if you write code that you think people will expand upon with new functionality.

If you have comments or other ideas on the subject of backwards capability and code structure please leave a comment I would love to learn more about it. If you think my thoughts on the subject are completely bonkers I don’t mind hearing about that as well. Just try to do the shredding as politely as possible.