Backwards compatibility

From Mothership
Jump to: navigation, search

Backwards compatibility is very important to maintain to ensure that Mothership is able to update without breaking installations.

Semantic versioning

Mothership uses Composer to manage the various different dependencies, consisting of both Mothership components, and external open source PHP libraries. In order for Composer to work effectively, package managers need to ensure that they follow the standards of semantic versioning. This means that each release is tagged with a version number that determines what type of release it is. A version number follows the format of x.y.z (for example, 3.2.1).

  • The x represents a major version. A major version can break backwards compatibility. Ideally, the breaks wouldn't be too plentiful to ensure that upgrading installations is not too painful, but the important thing to note is that Composer will not upgrade packages to a new major version (i.e. from version 1.x.x to 2.x.x) without the appropriate composer.json declaring that it is safe.
  • The y represents a minor version or feature. This is a version that does not break backwards compatibility, but does add new features that other components or the site itself might rely upon.
  • The z represents a hotfix. This is a version that contains bug fixes, refactors, security improvements, and anything else that improves the code without changing the functionality.

What counts as a backwards compatibility break?

At times it may be difficult to ascertain what exactly counts as a break to backwards compatibility.

The following will almost always be considered a BC break:

  • Removing a class that is able to function without causing a fatal error
  • Renaming a class
  • Renaming a method from an interface
  • Adding a method to an interface
  • Removing a public or protected method that is able to function without causing a fatal error
  • Renaming a public or protected method
  • Adding required parameters to a public or protected method
  • Changing the order of required parameters on a method
  • Adding required parameters of the __construct() method on a class that does not exist in the service container
  • Changing the order of required parameters on a method on a class that does not exist in the service container
  • Changing the fundamental behaviour of a method in a way that does not fix a bug
  • Making a class no longer implement a certain interface
  • Making a class no longer extend a certain other class
  • Removing a service from the service container
  • Renaming a service in the service container

The following may be considered a BC break:

  • Changing the required parameters of a __construct() method on a class that exists in the service container. This will be judged on a case by case basis. Generally one of the benefits of the service container is to eliminate the need to worry about which dependencies the class needs, and so not as much care needs to go into the maintaining the consistency of the constructor. However, if it is likely that the class has been extended, this will still be considered a BC break.
  • Adding assets that may conflict with those in the installation. An example of this would be moving a JavaScript file from the base installation into a module. If the base installation does not work with the JavaScript file existing in both the installation and the Mothership core codebase, this should be considered a BC break.
  • Changing a variable sent to the view from a scalar type to a non-scalar type or vice versa. This will usually be considered a BC break unless, for example, what was once a string is now an object with a __toString() method.
  • Changing the fundamental behaviour of a method or class will usually be considered a BC break, especially if the method or class is low level. An exception to this would be if the original behaviour could be considered as a bug.

How to avoid BC breaks

Not breaking backwards compatibility can be difficult, but there are ways to avoid it:

Make new parameters optional

Generally a safe way to add parameters to a method is to make them optional. They need to not only be optional in the method declaration, but the method needs to account for the possibility that it may not be set without throwing an exception.

Deprecate methods and classes instead of removing or renaming them

Instead of removing or renaming a method or class, tag it as deprecated, leaving information in the docblock of which method should be used instead. Most IDEs will be able to interpret this and alert the developer that should no longer use the method or class, e.g.:

/**
 * @deprecated This class is deprecated as of version 3.2.0, use NewClass instead
 */
class OldClass
{}

You could also make it extend the replacement class to ensure that the new functionality is still used:

/**
 * @deprecated This class is deprecated as of version 3.2.0, use NewClass instead
 */
class OldClass extends NewClass
{}

Alias renamed services instead of removing them

If you wish to rename or remove a service, keep the old one intact, and simply return the new service:

$services['new_service'] = function ($c) {
    return NewClass();
};

$services['old_service'] = function ($c) {
    return $c['new_service';]
};