Backward compatible development

This page describes rules and best practices for backward compatible development.

Backward Сompatibility Policy

See the versioning documentation for the definitions of MAJOR and MINOR changes and how it impacts extension developers.

The core Magento team and contributing developers work in two release types

  1. New and significant release (product’s MINOR release)
    • Necessary MAJOR and MINOR changes are allowed, but the Magento architecture team ultimately decides what is allowed.
  2. New patch release (product’s PATCH release)
    • PATCH changes are allowed, but MAJOR and MINOR changes are not allowed.

Backward Сompatibility Policy is not applied to Plugins, Observers and Setup Scripts.

Prohibited code changes

The following code modifications are forbidden for all code (both @api and non @api) without approval of a Magento architect.

The rules listed do not apply to customization code (e.g. Plugins, Observers, JS Mixins, etc.).

PHP

The following is a list of prohibited PHP code changes and possible alternative implementations.

Interface/class removal

Mark the class with the @deprecated tag instead of removing it, and mark all of its methods as deprecated so an IDE can highlight them as deprecated.

Public and protected method removal 

Mark the method with the @deprecated tag instead of removing it.

Continue returning the same results from the method if possible, so the old functionality is preserved.

Introduction of a method to a class or interface

Create a new interface with a new method instead of introducing a method to a class or interface.

The new interface may take over existing methods from the class if it makes sense to group them together. In this case, you must deprecate corresponding methods in the old interface/class with the @see annotation. The old methods should proxy the calls to the new interface instead of duplicating the logic.

For an example of an interface with an extracted method see the Magento\Catalog\Api\CategoryListInterface. This interface is responsible for the getList() method, but Magento\Catalog\Api\CategoryRepositoryInterface does not have that method.

  • For a PATCH product release, do NOT mark the new interface with @api.
  • For a MINOR product release, an architect marks, or approves, the new interface with @api if applicable.

Removing static functions

Do not remove static functions.

Adding parameters in public methods 

Deprecate the method and add a new method with the new parameter(s) instead of adding them to a public method.

Follow the alternative implementation described earlier for introducing a new method to a class or interface.

Reference the new method in a @see tag as a recommended replacement. Explain the reasons for replacing the old method with the new one (e.g., there is a bug in the old method). 

Adding parameters in protected methods 

Instead of adding parameters to protected methods, Create a new method with a new signature and deprecate the old method without changing it.

Declare the new method as private if possible.

Example Code
/**
 * @deprecated This method is not intended for usage in child classes
 * @see updateScopedPrice($price, $storeId)
 */
protected function updatePrice($price)
{
    $this->updateScopedPrice($price);
} 

private function updateScopedPrice($price, $storeId)
{
    // Updated logic that takes into account $storeId
} 

Modifying the default values of optional arguments in public and protected methods 

This is forbidden because the default argument values of public or protected methods are part of the API of the class/interface.

As an alternative, Create a new method with new interface following the alternative implementation for creating a new method for a class or interface.

Create multiple methods to cover all use cases to avoid using optional parameters.

Modifying the method argument type

Do not modify a method argument type.

Modifying the types of thrown exceptions

Do not modify the types of thrown exceptions unless a new exception is a sub-type of the old one.

Adding a constructor parameter

Add a new optional parameter to the constructor at the end of the arguments list instead of modifying the constructor.

In the constructor body, if the new dependency is not provided (i.e. the value of the introduced argument is null), fetch the dependency using Magento\Framework\App\ObjectManager::getInstance().

Example Code
class ExistingClass
{
    /** @var \New\Dependency\Interface */
    private $newDependency;

    public function __construct(
        \Old\Dependency\Intreface $oldDependency,
        $oldRequiredConstructorParameter,
        $oldOptinalConstructorParameter = null,
        \New\Dependency\Interface $newDependency = null
    ) {
        ...
        $this->newDependency = $newDependency ?: \Magento\Framework\App\ObjectManager::getInstance()->get(\New\Dependency\Interface::class);
    }

    public function existingFunction()
    {
        // Existing functionality
        ...
        ...

        // Use $this->newDependency wherever the new dependency is needed
        ...
        ...
    }
}

// Sample unit test code snippet follows
class ExistingClassTest extends \PHPUnit_Framework_TestCase
{
    private $existingClassObject;

    protected function setUp()
    {
        ...
        // Create dependency mocks with $this->getMock() or $this->getMockBuilder()
        $newDependencyMock = $this->getMock(\New\Dependency\Interface::class);

        $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
        $this->existingClassObject = $objectManager->getObject(
            ExistingClass::class,
            [
                'oldDependency' => $oldDependencyMock,
                'oldRequiredConstructorParameter' => 'foo',
                'oldOptinalConstructorParameter' => 'bar',
                'newDependency' => $newDependencyMock,
            ]
        );
    }

    public function testExistingFunction()
    {
        ...
        ...
    }
}

Removing or renaming public and protected properties

Mark properties with the @deprecated tag instead of removing or renaming them. Continue storing the value in the property to preserve the old functionality.

Removing or renaming constants

Do not remove or rename constants.

Removing, renaming, or changing the type of event arguments

Do not remove or rename event arguments. Do not change argument types. Instead of changing argument name or type, introduce new event argument with new name or type and deprecate the old argument by adding @deprecated annotation before dispatching the event.

JS

The following is a list of prohibited JS code changes:

  • Removing or renaming an interface or class
  • Removing or renaming public or protected methods
  • Introducing a method to an interface
  • Introducing an abstract method to a class
  • Removing or renaming static functions
  • Adding non-optional arguments in public and protected methods
  • Modifying the default value for optional arguments in public and protected methods
  • Removing or renaming public or protected properties
  • Removing or renaming constants

XML Schema

The following is a list of prohibited XML Schema changes:

  • Adding an obligatory node
  • Adding an obligatory attribute
  • Removing or renaming an attribute or node type
  • Removing or renaming a configuration file

DB Schema

The following is a list of prohibited DB Schema changes:

  • Modifying field type, default value, or property
  • Removing or renaming a table
  • Introducing a required field

CSS/LESS

The following is a list of prohibited CSS/LESS changes:

  • Removing or renaming a class
  • Removing or renaming a mix-in
  • Removing or renaming a variable

Magento APIs

The following is a list of prohibited Magento API changes:

  • Removing or renaming an event
  • Removing or renaming a layout handle
  • Removing or renaming a store configuration path
  • Modifying the directory structure
  • Removing an @api annotation
  • Modifying the Magento tool command argument list
  • Modifying or removing the Magento tool command

Translatable phrases

Do not modify any translatable phrase.

Magento functional and integration tests

The following is a list of prohibited changes to Magento functional and integration tests:

  • Changing a fixture format
  • Changing a fixture content (except changes forced by new functionality)

Allowed Code Changes

PHP

Changing the value of a constant 

Changing the value of a constant is itself a backward compatible change.

Even if client code saves the value in permanent storage or use it as input or output of a method, it is the responsibility of that code to ensure that it is a reliable implementation.

The client code should have enough control over the constant’s value. Do not rely on a value of a constant from another module or another vendor.

Stop setting a value to the Registry

Setting a value to the Registry is backward compatible. However, Magento discourages usage of the Registry, so third-party extensions should not depend on values in the Registry.

Adding an argument to an event

Adding an argument to an event is allowed.

Version changing rules for module setup

  1. The module data/schema version must not increase in a patch version release if the next minor version is already released.

    For example, the module data/schema version for all patch releases of Magento 2.0 can change prior to the release of Magento 2.1. After 2.1 releases, the version cannot change for 2.0 patch releases, but it can change for 2.1 patch releases until Magento 2.2.

  2. Deliver fixes that bump the module setup/data version in the current, unpublished version before delivering it to previous minor versions. In cases where an urgent fix was delivered in a previous minor version, treat the fix for the current unpublished version as a high priority task.

    For example, issue fixes that change the setup/upgrade version in the unreleased develop branch are delivered first before being ported into the released branches. If the fix was made for a released branch, a pull request for porting it into the develop branch must be created with a high priority and delivered as soon as possible.

  3. The setup version of a module must be higher than previous releases of the same module.

    For example, the setup version for a fix for the Magento_Catalog module is higher in the develop branch (2.1.3) than previous branch versions (2.0.2 and 2.1.2 for versions 2.0 and 2.1).

Backport fixes with breaking changes to patch branches

Backward compatibility is more important than niceness and implementation effort, but a Magento architect must be involved in making a decision.

Potential drawbacks:

  • It is double the work when it is necessary to implement different solutions for the develop branch (upcoming minor release) and patch release branches.
  • Inability to refactor code in patch releases
  • Effort for implementing fixes in patch releases may be higher due to necessary implementation workarounds.

Refactoring classes that reach limit of coupling between objects

Poorly designed classes with too many responsibilities and dependencies should be refactored to prevent them from reaching the limit of coupling between objects, but removing excessing dependencies and/or breaking the class down into smaller classes is a backward incompatible implementation.

Preserve public and protected class interfaces to maintain backward compatibility. Review and refactor the class such that parts of the logic go into smaller specialized classes without breaking backward compatibility.

  • Turn the existing class into a facade to prevent existing usages of the refactored methods from breaking.
  • The old public/protected methods should be marked as deprecated with the @see tag to suggest the new implementation for new usages.
  • Remove all unused private properties/methods.
  • Mark as deprecated unused protected properties. Remove the variable type indicated in the DocBlock to remove the dependency.
  • To preserve the constructor signature:
    • Remove type hinting for unused parameters to remove dependency on their type.
    • Add @SuppressWarnings(PHPMD.UnusedFormalParameter) for unused parameters.

Deprecation

Magento 2 must not have alternative APIs. Whenever you introduce a new implementation of some behavior, mark the old implementation as deprecated and specify the reason.

PHP, JS and XML

Use the @deprecated tag to mark methods as deprecated and follow it up with an explanation.

Use the  @see tag to recommend the new API to use instead of the old one.

Deprecated tag in PHP

/**
 * @deprecated because new api was introduced
 * @see \New\Api
 */

Deprecated tag in XML/HTML

<!--
@deprecated because new api was introduced
@see NewApi
-->

WebAPI

When replacing a WebAPI method with a new implementation that has a different signature, make sure it remains accessible on the same resource but with the next sequential version.

Deprecation testing

Every piece of code that is deprecated MUST be covered by a static test that will fail if some code uses the deprecated piece of code.

Removal of deprecated code

Deprecated code is preserved for the following time frames:

  • @api code: Until the next major version of the component
  • non-@api code: The next 2 minor releases or until a major release

Documentation of Backward Incompatible Changes

Backward incompatible changes must be approved by an architect and documented in the scope of the task that introduces those changes.

Examples of these tasks include:

  • Changing the input/output values format of a method
  • Changing a value format in the DB
  • Changing XML files (layouts, configuration files, etc.)

Some changes are detected and documented by an automated tool. These backward incompatible changes do not need manual documentation:

  • Adding/removing a class/interface
  • Adding/removing a method
  • Modifying a method signature
  • Adding/removing a class/interface constant
  • Adding removing a class property

Auto-generated Magento Open Source changes

Auto-generated Magento Commerce changes

Where to document

In the DevDocs repository, manually add backward incompatible changes to the following file:

https://github.com/magento/devdocs/blob/develop/guides/v<version>/release-notes/backward-incompatible-changes.md

Where: <version> is the MINOR version of the product (2.1, 2.2, 2.3, etc).

Example: https://github.com/magento/devdocs/blob/develop/guides/v2.2/release-notes/backward-incompatible-changes.md.

Update the page for the next MINOR product release when working in the develop branch of Magento.

For example, when 2.2 is released, a new backward-incompatible-changes.md for 2.3 becomes available for editing.

In order to update the page, create a PR to the DevDocs repository with your changes.