我们的向后兼容性承诺
确保您的项目平稳升级是我们的首要任务。因此,我们承诺所有 Symfony 次要版本都具有向后兼容性 (BC)。您可能将此策略识别为 语义化版本控制。简而言之,语义化版本控制意味着只有主要版本(例如 5.0、6.0 等)才允许破坏向后兼容性。次要版本(例如 5.1、5.2 等)可能会引入新功能,但必须在不破坏该发行分支(前例中的 5.x)的现有 API 的情况下进行。
我们还在代码库中提供弃用消息,以帮助您完成跨主要版本的迁移过程。
然而,向后兼容性有许多不同的形式。事实上,我们对框架所做的几乎每一项更改都可能破坏应用程序。例如,如果我们在类中添加一个新方法,这将破坏扩展此类并添加了相同方法但具有不同方法签名的应用程序。
此外,并非每个 BC 破坏对应用程序代码的影响都相同。虽然某些 BC 破坏需要您对类或架构进行重大更改,但另一些则可以通过更改方法名称来修复。
这就是我们为您创建此页面的原因。“使用 Symfony 代码”部分将告诉您如何确保您的应用程序在升级到同一主要发行分支的较新版本时不会完全崩溃。
第二部分“开发 Symfony 代码”是针对 Symfony 贡献者的。本节列出了每位贡献者需要遵循的详细规则,以确保我们的用户可以平稳升级。
使用 Symfony 代码
如果您在项目中使用 Symfony,以下准则将帮助您确保平稳升级到 Symfony 版本的未来所有次要版本。
使用我们的接口
Symfony 附带的所有接口都可以在类型提示中使用。您还可以调用它们声明的任何方法。我们保证我们不会破坏遵守这些规则的代码。
警告
此规则的例外是标有 @internal
的接口。不应使用或实现此类接口。
如果您实现了接口,我们承诺我们永远不会破坏您的代码。
下表详细说明了我们的向后兼容性承诺涵盖哪些用例
用例 | 向后兼容性 |
---|---|
如果您... | 那么我们保证 BC... |
针对接口进行类型提示 | 是 |
调用方法 | 是 [10] |
如果您实现该接口并且... | 那么我们保证 BC... |
实现方法 | 是 |
向已实现的方法添加参数 | 是 |
为参数添加默认值 | 是 |
为已实现的方法添加返回类型 | 是 |
使用我们的类
Symfony 提供的所有类都可以通过其公共方法和属性进行实例化和访问。
警告
带有 @internal
标签的类、属性和方法,以及位于各种 *\Tests\
命名空间中的类是此规则的例外。它们仅供内部使用,不应由您自己的代码访问。
为了安全起见,请查看下表以了解我们的向后兼容性承诺涵盖哪些用例
使用我们的特性
Symfony 提供的所有特性都可以在您的类中使用。
警告
此规则的例外是标有 @internal
的特性。不应使用此类特性。
为了安全起见,请查看下表以了解我们的向后兼容性承诺涵盖哪些用例
用例 | 向后兼容性 |
---|---|
如果您... | 那么我们保证 BC... |
使用特性 | 是 |
如果您使用该特性并且... | 那么我们保证 BC... |
使用它来实现接口 | 是 |
使用它来实现抽象方法 | 是 |
使用它来扩展父类 | 是 |
使用它来定义抽象类 | 是 |
使用公共、受保护或私有属性 | 是 |
使用公共、受保护或私有方法 | 是 |
开发 Symfony 代码
您想帮助我们改进 Symfony 吗?那太好了!但是,请遵守下面列出的规则,以确保我们的用户可以平稳升级。
更改接口
下表告诉您在开发 Symfony 的接口时允许进行哪些更改
更改类
下表告诉您在开发 Symfony 的类时允许进行哪些更改
更改类型 | 允许更改 | 注释 |
---|---|---|
完全删除 | 否 | |
设为 final | 否 | [6] |
设为 abstract | 否 | |
更改名称或命名空间 | 否 | |
更改父类 | 是 | [4] |
添加接口 | 是 | |
删除接口 | 否 | |
公共属性 | ||
添加公共属性 | 是 | |
删除公共属性 | 否 | |
降低可见性 | 否 | |
移动到父类 | 是 | |
受保护的属性 | ||
添加受保护的属性 | 是 | |
删除受保护的属性 | 否 | [7] |
降低可见性 | 否 | [7] |
设为公共 | 否 | [7] |
移动到父类 | 是 | |
私有属性 | ||
添加私有属性 | 是 | |
设为公共或受保护 | 是 | |
删除私有属性 | 是 | |
构造函数 | ||
添加不带强制参数的构造函数 | 是 | [1] |
添加不带默认值的参数 | 否 | |
添加带有默认值的参数 | 是 | [11] |
删除参数 | 否 | [3] |
为参数添加默认值 | 是 | |
删除参数的默认值 | 否 | |
为参数添加类型提示 | 否 | |
删除参数的类型提示 | 是 | |
更改参数类型 | 否 | |
删除构造函数 | 否 | |
降低公共构造函数的可见性 | 否 | |
降低受保护的构造函数的可见性 | 否 | [7] |
移动到父类 | 是 | |
析构函数 | ||
添加析构函数 | 是 | |
删除析构函数 | 否 | |
移动到父类 | 是 | |
公共方法 | ||
添加公共方法 | 是 | |
删除公共方法 | 否 | |
更改名称 | 否 | |
降低可见性 | 否 | |
设为 final | 否 | [6] |
移动到父类 | 是 | |
添加不带默认值的参数 | 否 | |
添加带有默认值的参数 | 否 | [7] [8] |
删除参数 | 否 | [3] |
为参数添加默认值 | 否 | [7] [8] |
删除参数的默认值 | 否 | |
为参数添加类型提示 | 否 | [7] [8] |
删除参数的类型提示 | 否 | [7] [8] |
更改参数类型 | 否 | [7] [8] |
添加返回类型 | 否 | [7] [8] |
删除返回类型 | 否 | [7] [8] [9] |
更改返回类型 | 否 | [7] [8] |
受保护的方法 | ||
添加受保护的方法 | 是 | |
删除受保护的方法 | 否 | [7] |
更改名称 | 否 | [7] |
降低可见性 | 否 | [7] |
设为 final | 否 | [6] |
设为公共 | 否 | [7] [8] |
移动到父类 | 是 | |
添加不带默认值的参数 | 否 | |
添加带有默认值的参数 | 否 | [7] [8] |
删除参数 | 否 | [3] |
为参数添加默认值 | 否 | [7] [8] |
删除参数的默认值 | 否 | [7] |
为参数添加类型提示 | 否 | [7] [8] |
删除参数的类型提示 | 否 | [7] [8] |
更改参数类型 | 否 | [7] [8] |
添加返回类型 | 否 | [7] [8] |
删除返回类型 | 否 | [7] [8] [9] |
更改返回类型 | 否 | [7] [8] |
私有方法 | ||
添加私有方法 | 是 | |
删除私有方法 | 是 | |
更改名称 | 是 | |
设为公共或受保护 | 是 | |
添加不带默认值的参数 | 是 | |
添加带有默认值的参数 | 是 | |
删除参数 | 是 | |
为参数添加默认值 | 是 | |
删除参数的默认值 | 是 | |
为参数添加类型提示 | 是 | |
删除参数的类型提示 | 是 | |
更改参数类型 | 是 | |
添加返回类型 | 是 | |
删除返回类型 | 是 | |
更改返回类型 | 是 | |
静态方法和属性 | ||
将非静态方法更改为静态方法 | 否 | [7] [8] |
将静态方法更改为非静态方法 | 否 | |
常量 | ||
添加常量 | 是 | |
删除常量 | 否 | |
更改常量的值 | 是 | [1] [5] |
更改特性
下表告诉您在开发 Symfony 的特性时允许进行哪些更改
更改类型 | 允许更改 | 注释 |
---|---|---|
完全删除 | 否 | |
更改名称或命名空间 | 否 | |
使用另一个特性 | 是 | |
公共属性 | ||
添加公共属性 | 是 | |
删除公共属性 | 否 | |
降低可见性 | 否 | |
移动到已使用的特性 | 是 | |
受保护的属性 | ||
添加受保护的属性 | 是 | |
删除受保护的属性 | 否 | |
降低可见性 | 否 | |
设为公共 | 否 | |
移动到已使用的特性 | 是 | |
私有属性 | ||
添加私有属性 | 是 | |
删除私有属性 | 否 | |
设为公共或受保护 | 是 | |
移动到已使用的特性 | 是 | |
构造函数和析构函数 | ||
具有构造函数或析构函数 | 否 | |
公共方法 | ||
添加公共方法 | 是 | |
删除公共方法 | 否 | |
更改名称 | 否 | |
降低可见性 | 否 | |
设为 final | 否 | [6] |
移动到已使用的特性 | 是 | |
添加不带默认值的参数 | 否 | |
添加带有默认值的参数 | 否 | |
删除参数 | 否 | |
为参数添加默认值 | 否 | |
删除参数的默认值 | 否 | |
为参数添加类型提示 | 否 | |
删除参数的类型提示 | 否 | |
更改参数类型 | 否 | |
更改返回类型 | 否 | |
受保护的方法 | ||
添加受保护的方法 | 是 | |
删除受保护的方法 | 否 | |
更改名称 | 否 | |
降低可见性 | 否 | |
设为 final | 否 | [6] |
设为公共 | 否 | [8] |
移动到已使用的特性 | 是 | |
添加不带默认值的参数 | 否 | |
添加带有默认值的参数 | 否 | |
删除参数 | 否 | |
为参数添加默认值 | 否 | |
删除参数的默认值 | 否 | |
为参数添加类型提示 | 否 | |
删除参数的类型提示 | 否 | |
更改参数类型 | 否 | |
更改返回类型 | 否 | |
私有方法 | ||
添加私有方法 | 是 | |
删除私有方法 | 否 | |
更改名称 | 否 | |
设为公共或受保护 | 是 | |
移动到已使用的特性 | 是 | |
添加不带默认值的参数 | 否 | |
添加带有默认值的参数 | 否 | |
删除参数 | 否 | |
为参数添加默认值 | 否 | |
删除参数的默认值 | 否 | |
为参数添加类型提示 | 否 | |
删除参数的类型提示 | 否 | |
更改参数类型 | 否 | |
添加返回类型 | 否 | |
删除返回类型 | 否 | |
更改返回类型 | 否 | |
静态方法和属性 | ||
将非静态方法更改为静态方法 | 否 | |
将静态方法更改为非静态方法 | 否 |
注释
[1] 应避免。如果完成,此更改必须在 UPGRADE 文件中记录。
[2] 添加的父接口不得引入接口中已不存在的任何新方法。
[3] 只能删除方法的最后一个可选参数,因为 PHP 不关心您传递给方法的其他参数。
[4] 更改父类时,原始父类必须仍然是该类的祖先。
[5] 仅当常量未在配置(例如 Yaml 和 XML 文件)中使用时,才可以更改常量的值,因为这些配置不支持常量,并且必须硬编码该值。例如,事件名称常量不能更改值而不引入 BC 破坏。此外,如果常量很可能在序列化的对象中使用,则不应更改常量的值。
[6] 允许使用 @final
注解。
[7] 如果类是 final 类,则允许。在首次发布后收到 @final
注解的类在其下一个主要版本中被视为 final 类。只有使用父类型才有可能更改参数类型。只有使用子类型才有可能更改返回类型。
[8] 如果方法是 final 方法,则允许。在首次发布后收到 @final
注解的方法在其下一个主要版本中被视为 final 方法。只有使用父类型才有可能更改参数类型。只有使用子类型才有可能更改返回类型。
[9] 允许用于 void
返回类型。
[10] 参数名称仅在 Attribute 类的构造函数的兼容性承诺范围内。当升级到较新的 Symfony 版本时,使用 PHP 命名参数可能会破坏您的代码。
[11] 只能在构造函数的最后一个位置添加可选参数。
以向后兼容的方式进行代码更改
正如您在上面阅读到的,许多更改是不允许的,因为它们会代表向后兼容性破坏。但是,我们希望能够随着时间的推移改进代码及其功能,这可以通过一些策略来完成,这些策略允许仍然分几个步骤进行一些不允许的更改,以确保向后兼容性和平稳的升级路径。其中一些将在接下来的章节中描述。
向公共方法添加参数
只有当新参数是方法的最后一个参数时,才有可能向公共方法添加新参数。
如果是这种情况,以下是如何在次要版本中正确执行此操作的方法
将参数作为注释添加到签名中
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 { }
在 PHPDoc 中记录新参数
1 2 3
/** * @param bool $stripWhitespace */
使用
func_num_args
和func_get_arg
在方法中检索参数1
$stripWhitespace = 2 <= \func_num_args() ? func_get_arg(1) : false;
请注意,默认值为
false
以保持当前行为。如果参数具有将更改当前行为的默认值,请警告用户
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.');
如果参数没有默认值,请警告用户,该参数将在下一个主要版本中成为必需参数
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); }
- 在下一个主要版本 (
X.0
) 中,取消注释参数,如果不需要描述,则删除 PHPDoc,并删除func_get_arg
代码和警告(如果有)。