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

/**
* @deprecated tag:v6.5.0 - Use NewFunction() instead
*/

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

/**
* @feature-deprecated (flag:FEATURE_NEXT_11111)
*/

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

/**
* @major-deprecated (flag:FEATURE_NEXT_22222)
*/

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

/**
* @internal (flag:FEATURE_NEXT_11111)
*/

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.

Symfony Backward Compatibility Promise

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.

Remove a protected property, constant or method.

🔴 NO

Annotate it as deprecated and remove it in the next major release.

Remove a private property, constant, or method.

✅ YES

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

/** Before */
class MailService implements MailServiceInterface
/** After */
class MailService extends AbstractMailService
class AbstractMailService implements MailServiceInterface

Add an argument

/**
* @feature-deprecated tag:v6.5.0 (flag:FEATURE_NEXT_22222)
* Parameter $precision will be mandatory in future implementation
*/
public function calculate(ProductEntity $product, Context $context /*, int $precision */): Product
{
if (Feature::isActive('FEATURE_NEXT_22222')) {
if (\func_num_args() === 3) {
$precision = func_get_arg(2);
// Do new calculation
} else {
throw new InvalidArgumentException('Argument 3 $precision is required with feature FEATURE_NEXT_22222');
}
} else {
// Do old calculation
}
}

Add a public function

/** Before */
abstract class AbstractProductRoute
{
abstract public function getDecorated(): AbstractProductRoute;
abstract public function load();
}
/** After */
abstract class AbstractProductRoute
{
abstract public function getDecorated(): AbstractProductRoute;
abstract public function load();
/**
* @deprecated tag:v6.5.0 - Will be abstract
*/
public function loadV2()
{
return $this->getDecorated()->loadV2();
}
}

TWIG & HTML

Deprecate TWIG block

Storefront: Use the deprecated tag from TWIG including a comment with the normal annotation.

{% block the_block_name %}
{% deprecated '@deprecated tag:v6.5.0 - Block will be removed completely including the content' %}
<div>Content</div>
{% endblock %}

Administration: Use normal TWIG comments for the annotation, as the other syntax is not supported.

{% block the_block_name %}
{# @deprecated tag:v6.5.0 - Block will be removed completely including the content #}
<div>Content</div>
{% endblock %}

Rename TWIG block

{% block new_block_name %}
{% block old_block_name %}
{% deprecated '@deprecated tag:v6.5.0 - Use `new_block_name` instead' %}
<div>Content</div>
{% endblock %}
{% endblock %}

Deprecate CSS selectors

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

Deprecate Vue Slot

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

JavaScript

Add new public function

// route.service.js
export default class RouteService {
/**
* @deprecated tag:v6.5.0 - Use getRouteConfig() instead
*/
getRoute(symfonyRoute) {
// Returns string 'foo/bar'
return this._someMagic(symfonyRoute);
}
getRouteConfig() {
// Returns object { name: 'foo/bar', params: [] }
return {
url: this._someMagic(symfonyRoute).url,
params: this._someMagic(symfonyRoute).params
}
}
}

Rename a method

/**
* @deprecated tag:v6.5.0 - Use onItemClick() instead
*/
onClick(event) {
return onItemClick(event);
},
onItemClick(event) {
// ...
}

Deprecate admin components

/**
* @deprecated tag:v6.5.0 - Use sw-new instead
* @status deprecated
*/
Shopware.Component.register('sw-old', {
deprecated: '6.5.0'
});

Deprecate admin component properties

{
name: 'example-component',
props: {
/** @deprecated tag:v6.5.0 - Insert additional information in comments */
exampleProp: {
type: String,
required: false,
default: 'Default value',
deprecated: {
version: '6.5.0',
comment: 'Insert additional information in comments'
}
}
}
}

Adding required properties to components

{
createdComponent() {
/** @deprecated tag:v6.5.0 - Warning will be removed when prop is requirerd */
if (!this.newProp) {
debug.warn(
'sw-example-component',
'"newProp" will be requirerd in tag:v6.5.0'
);
}
}
}

Renaming or removing JS services

// http-client.service.js
/**
* @deprecated tag:v6.5.0 - Use NewHttpClient instead (new-http-client.service.js)
*/
export default class HttpClient {
// ...
}
// new-http-client.service.js
export default class NewHttpClient {
// ...
}

Renaming or removing JS plugins

// buy-button.plugin.js
/**
* @deprecated tag:v6.5.0 - Use NewBuyButtonPlugin instead (new-buy-button.plugin.js)
*/
export default class BuyButtonPlugin extends Plugin {
// ...
}
// new-buy-button.plugin.js
export default class NewBuyButtonPlugin extends Plugin {
// ...
}