Backward Compatibility

Introduction

Shopware is a standard eCommerce solution which is used by many customers around the world and is the technical foundation of their online business. As developers of Shopware it is our highest goal to guarantee the reliability of the software for our customers. Always keep in mind that every change you make can have a big impact on the life of our customers, either in a good way, but also in a bad way.
For the release strategy Shopware uses a semantic versioning and therefore has to be always backward compatible for minor and patch updates. This brings additional challenges, when changing the code of Shopware. The following guide aims to provide you with the necessary workflows and techniques to do your changes in a backward compatible way and what to do, if it is not possible.

Annotations

During the development different cases occur, where you want to replace old code with new one or even remove some obsolete code. As Shopware must always be backward compatible on minor and patch updates, old code should only be removed with a major release. Until the next major release you want to mark the code with a corresponding annotation, to inform other developers of the planned change. This overview shows the most important annotations and in which situation they have to be used.

@deprecated

1
/**
2
* @deprecated tag:v6.5.0 - Use NewFunction() instead
3
*/
Copied!
The @deprecated annotation is used for obsolete public code, which will be removed with the next major release. The annotation always needs the specific major version tag, in which the code should be removed. Always add a meaningful comment with information about the corresponding replacement and how the deprecated code can be removed.

@feature-deprecated

1
/**
2
* @feature-deprecated (flag:FEATURE_NEXT_11111)
3
*/
Copied!
The @feature-deprecated annotation is used for obsolete code during the development of a feature, when the code is still hidden behind a feature flag. This is important, so that code, that is not public, will not trigger deprecation warnings. This annotation has to be changed to the correct @deprecated annotation, when the feature is released and the corresponding feature flag is removed. Always add the name of the corresponding feature flag to the annotation, so that it will not be forgotten, when the flag is removed.

@major-deprecated

1
/**
2
* @major-deprecated (flag:FEATURE_NEXT_22222)
3
*/
Copied!
The @major-deprecated annotation is used for breaking code which has to stay behind a specific major feature flag until the next major release. Always add the name of the corresponding feature flag to the annotation, so that it will not be forgotten, when the flag is removed.

@internal

1
/**
2
* @internal (flag:FEATURE_NEXT_11111)
3
*/
Copied!
In combination with a feature flag, like shown above, the @internal annotation is used for newly introduced code, which is not yet released. This ensures, that it will not be treated as public API until the corresponding feature is released and makes it possible to change the code in any way until the final release. Always add the name of the corresponding feature flag to the annotation, so that it will not be forgotten, when the corresponding feature is released.

Workflows

Backward Compatible Features

When developing new features, the goal should always be, to do this in a backward compatible way. This ensures that the feature can be shipped with a minor release, to provide value for customers as soon as possible. The following table should help you to use the correct approach for each type of change.
Case
During development
On feature release
Next major release
🚩 Feature Flag
Hide code behind normal feature flag.
Remove feature flag.
​
βž• New code
Add @internal annotation for new public API.
Remove @internal annotation.
​
βšͺ Obsolete code
Add @feature-deprecated annotation.
Replace @feature-deprecated with normal @deprecated annotation.
Remove old code.
πŸ”΄ Breaking change
Add @major-deprecated annotation. Hide breaking code behind additional major feature flag! Also create a separate changelog for the change with the major flag.
​
Remove old code. Remove major feature flag.
πŸ” Tests
Add new tests behind feature flag.
Remove feature flags from new tests. Declare old tests as legacy.
Remove legacy tests.
You can also find more detailed information and code examples in the corresponding ADR for the deprecation strategy.

Breaking Changes / Features

The first goal should always be to make your changes backward compatible. But there might be some special case, where it isn't possible in any way. In this case, the change can only be released with a major version. As we develop all changes in the same code base, the trunk branch, the changes have to stay behind a special feature flag, which is especially marked as a major feature flag.
Case
During development
Next major release (feature release)
🚩 Feature Flag
Hide code behind major feature flag.
Remove major feature flag.
βž• New code
Add @internal annotation for new public API.
Remove @internal annotation.
βšͺ Obsolete code
Add @major-deprecated annotation.
Remove old code.
πŸ”΄ Breaking change
Add @major-deprecated annotation.
Remove old code.
πŸ” Tests
Add new tests behind major feature flag. Declare old tests as legacy.
Remove legacy tests.

Compatibility Sheet

To ensure the backward compatibility it is important to know what you are allowed to do and what not. The following sheet should give you an orientation on common changes and how they could affect the backward compatibility. Although a lot of effort went into this list, it is not guaranteed to be a 100% complete. Always keep the persona of third-party developers in mind and challenge your changes against external needs.

PHP

As Shopware is based on the PHP framework Symfony, we also have to make sure to use the rules, which the framework follows. Besides the list below, always keep in mind the backward compatibility promise and implement your changes in a way the promise is kept.
Use Case
Allowed?
Notes / Alternatives
Change the typehint of a class, interface or trait.
πŸ”΄ NO
Add the new typehint as an abstract class. Code Example: Extend class with abstract class​
Change the constructor of a service.
βœ… YES
Services have to be instantiated over the container, so the changes should not break anything.
Change the constructor of a class, that is not a service. (Instantiated with new Class())
βšͺ PARTIAL
Only optional arguments are allowed to be added and this should be made via func_get_args(). Code Example: Add an argument​
Change the arguments of a public method.
βšͺ PARTIAL
Only optional arguments are allowed to be added and this should be made via func_get_args(). Code Example: Add an argument​
Change the arguments of a protected method.
βšͺ PARTIAL
Only optional arguments are allowed to be added and this should be made via func_get_args(). Code Example: Add an argument​
Change the arguments of a private method.
βœ… YES
​
Change the return the type of a method.
πŸ”΄ NO
Create a new method and deprecate the old one.
Change the value of a public constant.
πŸ”΄ NO
You should add a new constant. Annotate the old constant as deprecated and remove it in the next major version.
Change the value of a private constant.
βœ… YES
Check all potential usages of the constant. Maybe it is used somewhere to be stored in the database. In that case, you must write a migration for it which ensures every use of the constant in a db-value is updated as well.
Change a class or method to final.
πŸ”΄ NO
You will have to deprecate the class or method and add an annotation that it will be final in the next major version.
Change the visibility of a class, method or property from public to private/protected or protected to private
πŸ”΄ NO
Annotate it as deprecated and change the visibility in the next major version.
Change the namespace of a class.
πŸ”΄ NO
Duplicate the class and mark the old one as deprecated.
Change static state (remove static or delete static keyword).
πŸ”΄ NO
Annotate it as deprecated and add or remove the static keyword in the next major version.
Add parameter to interface or abstract class function.
βšͺ PARTIAL
Only optional arguments are allowed to be added and this should be made via func_get_args(). Code Example: Add an argument​
Add new public function to interface.
πŸ”΄ NO
​
Add new public function to abstract class.
βšͺ PARTIAL
Only possible if the abstract class already contains the getDecorated call. Code Example: Add a public function​
Add an event or event dispatch.
βœ… YES
​
Add a constant.
βœ… YES
​
Remove an event or event dispatch.
πŸ”΄ NO
​
Remove a public property, constant or method.
πŸ”΄ NO
Annotate it as deprecated and remove it in the next major release.

Storefront

TWIG Templates

Use Case
Allowed?
Notes / Alternatives
Removing TWIG blocks.
πŸ”΄ NO
Use the deprecation workflow. Code Example: Deprecate TWIG block​
Renaming TWIG blocks.
πŸ”΄ NO
Use the deprecation workflow. Create a new surrounding block with the new name and deprecate the old one. All variables which are defined in the scope of the old block, must be moved to the new surrounding block scope. Code Example: Rename TWIG block​
Moving TWIG blocks within the same file.
βšͺ PARTIAL
Only within the same scope / parent block.
Removing TWIG variables.
πŸ”΄ NO
​
Renaming TWIG variables.
πŸ”΄ NO
Create a new variable within the same scope and deprecate the old one.
Changing the value of TWIG variables
βšͺ PARTIAL
The data type has to stay the same. Otherwise use the deprecation workflow.
Moving TWIG variable definitions to other TWIG blocks.
βšͺ PARTIAL
Only when they are being moved higher up in the block scope.
Adding TWIG blocks which affect the scope of variable definitions.
πŸ”΄ NO
​
Moving template files to other directories.
πŸ”΄ NO
​

HTML

Use Case
Allowed?
Notes / Alternatives
Removing HTML sections.
πŸ”΄ NO
​
Moving HTML sections within the same file.
βšͺ PARTIAL
Only within the same TWIG Block.
Renaming of removing CSS selectors.
πŸ”΄ NO
Use the deprecation workflow. Code Example: Deprecate CSS selectors​

JavaScript

Use Case
Allowed?
Notes / Alternatives
Renaming or removing JS services.
πŸ”΄ NO
Use the deprecation workflow. Code Example: Renaming or removing JS services​
Renaming or removing of JS plugins.
πŸ”΄ NO
Use the deprecation workflow. Code Example: Renaming or removing JS plugins​
Changing the public API of a JS plugin or service.
πŸ”΄ NO
Use the deprecation workflow. Code Example: Add new public function​
Renaming methods of JS plugins or services.
πŸ”΄ NO
Use the deprecation workflow. Code Example: Rename a method​
Renaming or removing of JS events.
πŸ”΄ NO
​
Changing the parameters of JS events.
πŸ”΄ NO
​

Styling / CSS

Use Case
Allowed?
Notes / Alternatives
Renaming or removing CSS definitions.
βšͺ PARTIAL
Only CSS properties which have a visual effect, but no structure or functional CSS properties. Not allowed are: display, position, visibility, z-index, pointer-events, overflow, transform
Changing generic selectors of the Bootstrap framework.
βšͺ PARTIAL
Be aware of what you are doing. Fixing a small styling issue might be ok. Changing structural properties might have a big impact on the layout and the functionality.
Changing the CSS properties of generic Bootstrap classes.
βšͺ PARTIAL
Be aware of what you are doing. Fixing a small styling issue might be ok. Changing structural properties might have a big impact on the layout and the functionality.
Renaming or removing SASS variables or mixins.
πŸ”΄ NO
​
Renaming or removing of standard theme variables.
πŸ”΄ NO
​

Administration

Component Templates

Use Case
Allowed?
Notes / Alternatives
Renaming or removing TWIG blocks.
πŸ”΄ NO
Use the deprecation workflow. Code Example: Deprecate TWIG block​
Moving TWIG blocks within the same file.
βšͺ PARTIAL
Only within the same scope / parent block.
Changing the "ref" attribute of elements.
πŸ”΄ NO
​
Changing VueJS specific template functions, like v-if.
πŸ”΄ NO
​
Changing VueJS data functions, like v-model, or v-bind.
πŸ”΄ NO
​
Renaming or removing VueJS slots.
πŸ”΄ NO
Use the deprecation workflow. Code Example: Deprecate Vue Slot​
Using new functionality of the VueJS framework, which has a breaking behaviour.
πŸ”΄ NO
​
Renaming or removing of global available VueJS template functions.
πŸ”΄ NO
​

JavaScript Modules & Components

Use Case
Allowed?
Notes / Alternatives
Renaming or removing of base components.
πŸ”΄ NO
Use the deprecation workflow. Code Example: Deprecate admin components​
Renaming or removing of module components.
πŸ”΄ NO
Use the deprecation workflow. Code Example: Deprecate admin components​
Renaming or removing methods
πŸ”΄ NO
Use the deprecation workflow. Code Example: Rename a method​
Changing the return value of a method
πŸ”΄ NO
Use the deprecation workflow. Code Example: Add new public function​
Changing the parameters of a method
πŸ”΄ NO
Only with new optional parameters which have a default value or if the method uses a single object as parameter via destructering. Otherwise use the deprecation workflow. Code Example: Add new public function​
Renaming or removing of required props
πŸ”΄ NO
Use the deprecation workflow. Code Example: Deprecate properties​
Renaming or removing of vue events
πŸ”΄ NO
Use the deprecation workflow. Add a deprecation annotation to the event which needs to be renamed or removed and offer an alternative inside the deprecation comment when possible
Changing the parameters of a vue event
βšͺ PARTIAL
Only with new optional parameters which have a default value. Only when the method receives an object as parameter and when working with destructuring
Adding required properties to components.
πŸ”΄ NO
Add the property as optional property and show a warning if the property is empty. This could be done on component creation or with a property validator. Code Example: Adding required properties to components​
Renaming or removing data which is used in the data binding.
πŸ”΄ NO
​
Renaming or removing the routes of a module.
πŸ”΄ NO
​
Changing the parameters of a route.
πŸ”΄ NO
​
Adding required parameters to a route.
πŸ”΄ NO
​
Changing the public API of the global "Shopware" object.
πŸ”΄ NO
Use the deprecation workflow. Use same workflow as for other methods. Code Example: Rename a method​
Changing the public API of state stores. (VueX)
πŸ”΄ NO
​
Renaming, removing or not-using of assets or other imports.
πŸ”΄ NO
​

Component Styling

Use Case
Allowed?
Notes / Alternatives
Renaming or removing CSS definitions.
βšͺ PARTIAL
Only CSS properties which have a visual effect, but no structure or functional CSS properties. Not allowed are: display, position, visibility, z-index, pointer-events, overflow, transform
Renaming or removing of functional selectors, like is--*.
πŸ”΄ NO
​
Renaming or removing root CSS selectors.
πŸ”΄ NO
​

Code Examples

PHP

Extend class with abstract class

1
/** Before */
2
class MailService implements MailServiceInterface
3
4
/** After */
5
class MailService extends AbstractMailService
6
class AbstractMailService implements MailServiceInterface
Copied!

Add an argument

1
/**
2
* @feature-deprecated tag:v6.5.0 (flag:FEATURE_NEXT_22222)
3
* Parameter $precision will be mandatory in future implementation
4
*/
5
public function calculate(ProductEntity $product, Context $context /*, int $precision */): Product
6
{
7
if (Feature::isActive('FEATURE_NEXT_22222')) {
8
if (\func_num_args() === 3) {
9
$precision = func_get_arg(2);
10
// Do new calculation
11
} else {
12
throw new InvalidArgumentException('Argument 3 $precision is required with feature FEATURE_NEXT_22222');
13
}
14
} else {
15
// Do old calculation
16
}
17
}
Copied!

Add a public function

1
/** Before */
2
abstract class AbstractProductRoute
3
{
4
abstract public function getDecorated(): AbstractProductRoute;
5
6
abstract public function load();
7
}
8
9
10
/** After */
11
abstract class AbstractProductRoute
12
{
13
abstract public function getDecorated(): AbstractProductRoute;
14
15
abstract public function load();
16
17
/**
18
* @deprecated tag:v6.5.0 - Will be abstract
19
*/
20
public function loadV2()
21
{
22
return $this->getDecorated()->loadV2();
23
}
24
}
Copied!

TWIG & HTML

Deprecate TWIG block

Storefront: Use the deprecated tag from TWIG including a comment with the normal annotation.
1
{% block the_block_name %}
2
{% deprecated '@deprecated tag:v6.5.0 - Block will be removed completely including the content' %}
3
<div>Content</div>
4
{% endblock %}
Copied!
Administration: Use normal TWIG comments for the annotation, as the other syntax is not supported.
1
{% block the_block_name %}
2
{# @deprecated tag:v6.5.0 - Block will be removed completely including the content #}
3
<div>Content</div>
4
{% endblock %}
Copied!

Rename TWIG block

1
{% block new_block_name %}
2
{% block old_block_name %}
3
{% deprecated '@deprecated tag:v6.5.0 - Use `new_block_name` instead' %}
4
<div>Content</div>
5
{% endblock %}
6
{% endblock %}
Copied!

Deprecate CSS selectors

1
{# @deprecated tag:v6.5.0 - CSS class "card-primary" is deprecated, use "card-major" instead #}
2
<div class="card card-major card-primary">
3
...
4
</div>
Copied!

Deprecate Vue Slot

1
{# @deprecated tag:v6.5.0 - Use slot "main-content" instead #}
2
<slot name="content"></slot>
3
<slot name="main-content"></slot>
Copied!

JavaScript

Add new public function

1
// route.service.js
2
export default class RouteService {
3
/**
4
* @deprecated tag:v6.5.0 - Use getRouteConfig() instead
5
*/
6
getRoute(symfonyRoute) {
7
// Returns string 'foo/bar'
8
return this._someMagic(symfonyRoute);
9
}
10
11
getRouteConfig() {
12
// Returns object { name: 'foo/bar', params: [] }
13
return {
14
url: this._someMagic(symfonyRoute).url,
15
params: this._someMagic(symfonyRoute).params
16
}
17
}
18
}
Copied!

Rename a method

1
/**
2
* @deprecated tag:v6.5.0 - Use onItemClick() instead
3
*/
4
onClick(event) {
5
return onItemClick(event);
6
},
7
8
onItemClick(event) {
9
// ...
10
}
Copied!

Deprecate admin components

1
/**
2
* @deprecated tag:v6.5.0 - Use sw-new instead
3
* @status deprecated
4
*/
5
Shopware.Component.register('sw-old', {
6
deprecated: '6.5.0'
7
});
Copied!

Deprecate admin component properties

1
{
2
name: 'example-component',
3
props: {
4
/** @deprecated tag:v6.5.0 - Insert additional information in comments */
5
exampleProp: {
6
type: String,
7
required: false,
8
default: 'Default value',
9
deprecated: {
10
version: '6.5.0',
11
comment: 'Insert additional information in comments'
12
}
13
}
14
}
15
}
Copied!

Adding required properties to components

1
{
2
createdComponent() {
3
/** @deprecated tag:v6.5.0 - Warning will be removed when prop is requirerd */
4
if (!this.newProp) {
5
debug.warn(
6
'sw-example-component',
7
'"newProp" will be requirerd in tag:v6.5.0'
8
);
9
}
10
}
11
}
Copied!

Renaming or removing JS services

1
// http-client.service.js
2
/**
3
* @deprecated tag:v6.5.0 - Use NewHttpClient instead (new-http-client.service.js)
4
*/
5
export default class HttpClient {
6
// ...
7
}
8
9
// new-http-client.service.js
10
export default class NewHttpClient {
11
// ...
12
}
Copied!

Renaming or removing JS plugins

1
// buy-button.plugin.js
2
/**
3
* @deprecated tag:v6.5.0 - Use NewBuyButtonPlugin instead (new-buy-button.plugin.js)
4
*/
5
export default class BuyButtonPlugin extends Plugin {
6
// ...
7
}
8
9
// new-buy-button.plugin.js
10
export default class NewBuyButtonPlugin extends Plugin {
11
// ...
12
}
Copied!