Backward compatible development
This page describes rules and best practices for backward compatible development.
Backward Compatibility 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
- 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.
- New patch release (product’s PATCH release)
- PATCH changes are allowed, but MAJOR and MINOR changes are not allowed.
Backward Compatibility Policy is not applied to Plugins, Observers and Setup Scripts.
Prohibited code changes
The following modifications are forbidden for @api
code without the approval of the Magento architect, Product Manager and Engineering Manager assigned to the component.
The rules listed do not apply to customization code (e.g. Plugins, Observers, JS Mixins, etc.).
Composer
Introducing a new dependency from an existing module
Introducing a new dependency from an existing module is a backward incompatible change because we cannot guarantee when Magento will enable the required module. As a result, we cannot satisfy the dependency in this way.
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
- @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.
Throwing a new type of exception from an existing method
You SHOULD NOT throw a new type of exception from an existing method. The existing clients of the method may not expect this change and consequently fail to properly handle the exception.
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()
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class ExistingClass
{
/** @var \New\Dependency\Interface */
private $newDependency;
public function __construct(
\Old\Dependency\Interface $oldDependency,
$oldRequiredConstructorParameter,
$oldOptionalConstructorParameter = 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',
'oldOptionalConstructorParameter' => '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.
Example code:
1
2
3
4
5
6
7
8
9
$transportObject = new DataObject($transport);
/**
* Event argument `transport` is @deprecated. Use `transport_object` instead.
*/
$this->eventManager->dispatch(
'email_invoice_set_template_vars_before',
['sender' => $this, 'transport' => $transportObject->getData(), 'transport_object' => $transportObject]
);
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
- Topic names for message queue, except automatically generated names (e.g. topic names generated by Asynchronous Web API. Automatically generated topic names should be treated as private implementation and may be changed at any time if needed.
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
-
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.
-
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 thedevelop
branch must be created with a high priority and delivered as soon as possible. -
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).
Backporting 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 excessive 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.
Preserve @api
tag when deprecating @api
-marked code.
Deprecating in PHP and JS
Comment:
1
2
3
4
/**
* @deprecated because newAPI was introduced
* @see \New\Api
*/
Trigger a deprecation message in deprecated functions/classes to notify extensions/customizations that use them.
PHP:
1
trigger_error('Class is deprecated', E_USER_DEPRECATED);
JS:
1
console.warn('Function is deprecated');
Deprecating in XML/HTML
1
2
3
4
<!--
@deprecated because newAPI 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/index.md
Where: <version>
is the MINOR version of the product (2.1, 2.2, 2.3, etc).
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.