Our Backward Compatibility Promise
Ensuring smooth upgrades of your projects is our first priority. That's why we promise you backward compatibility (BC) for all minor Symfony releases. You probably recognize this strategy as Semantic Versioning. In short, Semantic Versioning means that only major releases (such as 5.0, 6.0 etc.) are allowed to break backward compatibility. Minor releases (such as 5.1, 5.2 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (5.x in the previous example).
We also provide deprecation message triggered in the code base to help you with the migration process across major releases.
However, backward compatibility comes in many different flavors. In fact, almost every change that we make to the framework can potentially break an application. For example, if we add a new method to a class, this will break an application which extended this class and added the same method, but with a different method signature.
Also, not every BC break has the same impact on application code. While some BC breaks require you to make significant changes to your classes or your architecture, others are fixed by changing the name of a method.
That's why we created this page for you. The section "Using Symfony Code" will tell you how you can ensure that your application won't break completely when upgrading to a newer version of the same major release branch.
The second section, "Working on Symfony Code", is targeted at Symfony contributors. This section lists detailed rules that every contributor needs to follow to ensure smooth upgrades for our users.
Caution
Experimental Features and code
marked with the @internal
tags are excluded from our Backward
Compatibility promise.
Also note that backward compatibility breaks are tolerated if they are required to fix a security issue.
Using Symfony Code
If you are using Symfony in your projects, the following guidelines will help you to ensure smooth upgrades to all future minor releases of your Symfony version.
Using our Interfaces
All interfaces shipped with Symfony can be used in type hints. You can also call any of the methods that they declare. We guarantee that we won't break code that sticks to these rules.
Caution
The exception to this rule are interfaces tagged with @internal
. Such
interfaces should not be used or implemented.
If you implement an interface, we promise that we won't ever break your code.
The following table explains in detail which use cases are covered by our backward compatibility promise:
Use Case | Backward Compatibility |
---|---|
If you... | Then we guarantee BC... |
Type hint against the interface | Yes |
Call a method | Yes [10] |
If you implement the interface and... | Then we guarantee BC... |
Implement a method | Yes |
Add an argument to an implemented method | Yes |
Add a default value to an argument | Yes |
Add a return type to an implemented method | Yes |
Using our Classes
All classes provided by Symfony may be instantiated and accessed through their public methods and properties.
Caution
Classes, properties and methods that bear the tag @internal
as well as
the classes located in the various *\Tests\
namespaces are an
exception to this rule. They are meant for internal use only and should
not be accessed by your own code.
To be on the safe side, check the following table to know which use cases are covered by our backward compatibility promise:
Use Case | Backward Compatibility |
---|---|
If you... | Then we guarantee BC... |
Type hint against the class | Yes |
Create a new instance | Yes |
Extend the class | Yes |
Access a public property | Yes |
Call a public method | Yes [10] |
If you extend the class and... | Then we guarantee BC... |
Access a protected property | Yes |
Call a protected method | Yes [10] |
Override a public property | Yes |
Override a protected property | Yes |
Override a public method | Yes |
Override a protected method | Yes |
Add a new property | No |
Add a new method | No |
Add an argument to an overridden method | Yes |
Add a default value to an argument | Yes |
Call a private method (via Reflection) | No |
Access a private property (via Reflection) | No |
Using our Traits
All traits provided by Symfony may be used in your classes.
Caution
The exception to this rule are traits tagged with @internal
. Such
traits should not be used.
To be on the safe side, check the following table to know which use cases are covered by our backward compatibility promise:
Use Case | Backward Compatibility |
---|---|
If you... | Then we guarantee BC... |
Use a trait | Yes |
If you use the trait and... | Then we guarantee BC... |
Use it to implement an interface | Yes |
Use it to implement an abstract method | Yes |
Use it to extend a parent class | Yes |
Use it to define an abstract class | Yes |
Use a public, protected or private property | Yes |
Use a public, protected or private method | Yes |
Working on Symfony Code
Do you want to help us improve Symfony? That's great! However, please stick to the rules listed below in order to ensure smooth upgrades for our users.
Changing Interfaces
This table tells you which changes you are allowed to do when working on Symfony's interfaces:
Type of Change | Change Allowed | Notes |
---|---|---|
Remove entirely | No | |
Change name or namespace | No | |
Add parent interface | Yes | [2] |
Remove parent interface | No | |
Methods | ||
Add method | No | |
Remove method | No | |
Change name | No | |
Move to parent interface | Yes | |
Add argument without a default value | No | |
Add argument with a default value | No | |
Remove argument | No | [3] |
Add default value to an argument | No | |
Remove default value of an argument | No | |
Add type hint to an argument | No | |
Remove type hint of an argument | No | |
Change argument type | No | |
Add return type | No | |
Remove return type | No | [9] |
Change return type | No | |
Static Methods | ||
Turn non static into static | No | |
Turn static into non static | No | |
Constants | ||
Add constant | Yes | |
Remove constant | No | |
Change value of a constant | Yes | [1] [5] |
Changing Classes
This table tells you which changes you are allowed to do when working on Symfony's classes:
Type of Change | Change Allowed | Notes |
---|---|---|
Remove entirely | No | |
Make final | No | [6] |
Make abstract | No | |
Change name or namespace | No | |
Change parent class | Yes | [4] |
Add interface | Yes | |
Remove interface | No | |
Public Properties | ||
Add public property | Yes | |
Remove public property | No | |
Reduce visibility | No | |
Move to parent class | Yes | |
Protected Properties | ||
Add protected property | Yes | |
Remove protected property | No | [7] |
Reduce visibility | No | [7] |
Make public | No | [7] |
Move to parent class | Yes | |
Private Properties | ||
Add private property | Yes | |
Make public or protected | Yes | |
Remove private property | Yes | |
Constructors | ||
Add constructor without mandatory arguments | Yes | [1] |
Add argument without a default value | No | |
Add argument with a default value | Yes | [11] |
Remove argument | No | [3] |
Add default value to an argument | Yes | |
Remove default value of an argument | No | |
Add type hint to an argument | No | |
Remove type hint of an argument | Yes | |
Change argument type | No | |
Remove constructor | No | |
Reduce visibility of a public constructor | No | |
Reduce visibility of a protected constructor | No | [7] |
Move to parent class | Yes | |
Destructors | ||
Add destructor | Yes | |
Remove destructor | No | |
Move to parent class | Yes | |
Public Methods | ||
Add public method | Yes | |
Remove public method | No | |
Change name | No | |
Reduce visibility | No | |
Make final | No | [6] |
Move to parent class | Yes | |
Add argument without a default value | No | |
Add argument with a default value | No | [7] [8] |
Remove argument | No | [3] |
Add default value to an argument | No | [7] [8] |
Remove default value of an argument | No | |
Add type hint to an argument | No | [7] [8] |
Remove type hint of an argument | No | [7] [8] |
Change argument type | No | [7] [8] |
Add return type | No | [7] [8] |
Remove return type | No | [7] [8] [9] |
Change return type | No | [7] [8] |
Protected Methods | ||
Add protected method | Yes | |
Remove protected method | No | [7] |
Change name | No | [7] |
Reduce visibility | No | [7] |
Make final | No | [6] |
Make public | No | [7] [8] |
Move to parent class | Yes | |
Add argument without a default value | No | |
Add argument with a default value | No | [7] [8] |
Remove argument | No | [3] |
Add default value to an argument | No | [7] [8] |
Remove default value of an argument | No | [7] |
Add type hint to an argument | No | [7] [8] |
Remove type hint of an argument | No | [7] [8] |
Change argument type | No | [7] [8] |
Add return type | No | [7] [8] |
Remove return type | No | [7] [8] [9] |
Change return type | No | [7] [8] |
Private Methods | ||
Add private method | Yes | |
Remove private method | Yes | |
Change name | Yes | |
Make public or protected | Yes | |
Add argument without a default value | Yes | |
Add argument with a default value | Yes | |
Remove argument | Yes | |
Add default value to an argument | Yes | |
Remove default value of an argument | Yes | |
Add type hint to an argument | Yes | |
Remove type hint of an argument | Yes | |
Change argument type | Yes | |
Add return type | Yes | |
Remove return type | Yes | |
Change return type | Yes | |
Static Methods and Properties | ||
Turn non static into static | No | [7] [8] |
Turn static into non static | No | |
Constants | ||
Add constant | Yes | |
Remove constant | No | |
Change value of a constant | Yes | [1] [5] |
Changing Traits
This table tells you which changes you are allowed to do when working on Symfony's traits:
Type of Change | Change Allowed | Notes |
---|---|---|
Remove entirely | No | |
Change name or namespace | No | |
Use another trait | Yes | |
Public Properties | ||
Add public property | Yes | |
Remove public property | No | |
Reduce visibility | No | |
Move to a used trait | Yes | |
Protected Properties | ||
Add protected property | Yes | |
Remove protected property | No | |
Reduce visibility | No | |
Make public | No | |
Move to a used trait | Yes | |
Private Properties | ||
Add private property | Yes | |
Remove private property | No | |
Make public or protected | Yes | |
Move to a used trait | Yes | |
Constructors and destructors | ||
Have constructor or destructor | No | |
Public Methods | ||
Add public method | Yes | |
Remove public method | No | |
Change name | No | |
Reduce visibility | No | |
Make final | No | [6] |
Move to used trait | Yes | |
Add argument without a default value | No | |
Add argument with a default value | No | |
Remove argument | No | |
Add default value to an argument | No | |
Remove default value of an argument | No | |
Add type hint to an argument | No | |
Remove type hint of an argument | No | |
Change argument type | No | |
Change return type | No | |
Protected Methods | ||
Add protected method | Yes | |
Remove protected method | No | |
Change name | No | |
Reduce visibility | No | |
Make final | No | [6] |
Make public | No | [8] |
Move to used trait | Yes | |
Add argument without a default value | No | |
Add argument with a default value | No | |
Remove argument | No | |
Add default value to an argument | No | |
Remove default value of an argument | No | |
Add type hint to an argument | No | |
Remove type hint of an argument | No | |
Change argument type | No | |
Change return type | No | |
Private Methods | ||
Add private method | Yes | |
Remove private method | No | |
Change name | No | |
Make public or protected | Yes | |
Move to used trait | Yes | |
Add argument without a default value | No | |
Add argument with a default value | No | |
Remove argument | No | |
Add default value to an argument | No | |
Remove default value of an argument | No | |
Add type hint to an argument | No | |
Remove type hint of an argument | No | |
Change argument type | No | |
Add return type | No | |
Remove return type | No | |
Change return type | No | |
Static Methods and Properties | ||
Turn non static into static | No | |
Turn static into non static | No |
Notes
[1] Should be avoided. When done, this change must be documented in the UPGRADE file.
[2] The added parent interface must not introduce any new methods that don't exist in the interface already.
[3] Only the last optional argument(s) of a method may be removed, as PHP does not care about additional arguments that you pass to a method.
[4] When changing the parent class, the original parent class must remain an ancestor of the class.
[5] The value of a constant may only be changed when the constants aren't used in configuration (e.g. Yaml and XML files), as these do not support constants and have to hardcode the value. For instance, event name constants can't change the value without introducing a BC break. Additionally, if a constant will likely be used in objects that are serialized, the value of a constant should not be changed.
[6] Allowed using the @final
annotation.
[7] Allowed if the class is final. Classes that received the @final
annotation after their first release are considered final in their next major
version. Changing an argument type is only possible with a parent type. Changing
a return type is only possible with a child type.
[8] Allowed if the method is final. Methods that received the @final
annotation after their first release are considered final in their next major
version. Changing an argument type is only possible with a parent type. Changing
a return type is only possible with a child type.
[9] Allowed for the void
return type.
[10] Parameter names are only covered by the compatibility promise for constructors of Attribute classes. Using PHP named arguments might break your code when upgrading to newer Symfony versions.
[11] Only optional argument(s) of a constructor at last position may be added.
Making Code Changes in a Backward Compatible Way
As you read above, many changes are not allowed because they would represent a backward compatibility break. However, we want to be able to improve the code and its features over time and that can be done thanks to some strategies that allow to still do some unallowed changes in several steps that ensure backward compatibility and a smooth upgrade path. Some of them are described in the next sections.
Adding an Argument to a Public Method
Adding a new argument to a public method is possible only if this is the last argument of the method.
If that's the case, here is how to do it properly in a minor version:
Add the argument as a comment in the signature:
1 2 3 4 5 6 7 8 9
// the new argument can be optional public function say(string $text, /* bool $stripWhitespace = true */): void { } // or required public function say(string $text, /* bool $stripWhitespace */): void { }
Document the new argument in a PHPDoc:
1 2 3
/** * @param bool $stripWhitespace */
Use
func_num_args
andfunc_get_arg
to retrieve the argument in the method:1
$stripWhitespace = 2 <= \func_num_args() ? func_get_arg(1) : false;
Note that the default value is
false
to keep the current behavior.If the argument has a default value that will change the current behavior, warn the user:
1
trigger_deprecation('symfony/COMPONENT', 'X.Y', 'Not passing the "bool $stripWhitespace" argument explicitly is deprecated, its default value will change to X in Z.0.');
If the argument has no default value, warn the user that is going to be required in the next major version:
1 2 3 4 5 6 7
if (\func_num_args() < 2) { trigger_deprecation('symfony/COMPONENT', 'X.Y', 'The "%s()" method will have a new "bool $stripWhitespace" argument in version Z.0, not defining it is deprecated.', __METHOD__); $stripWhitespace = false; } else { $stripWhitespace = func_get_arg(1); }
- In the next major version (
X.0
), uncomment the argument, remove the PHPDoc if there is no need for a description, and remove thefunc_get_arg
code and the warning if any.