From 981bd4dfce479f051483c73fc7732ccfa33c94ee Mon Sep 17 00:00:00 2001 From: Niels Braczek Date: Sun, 21 Nov 2021 00:24:47 +0100 Subject: [PATCH 01/12] Initial commit of first draft --- index.md | 2 + proposed/release-versioning-meta.md | 101 ++++++++++++++++++++++++ proposed/release-versioning.md | 116 ++++++++++++++++++++++++++++ 3 files changed, 219 insertions(+) create mode 100644 proposed/release-versioning-meta.md create mode 100644 proposed/release-versioning.md diff --git a/index.md b/index.md index 9083c0e..4dfd9dd 100644 --- a/index.md +++ b/index.md @@ -42,6 +42,7 @@ The sponsor is responsible for managing the review stage and votes. | N/A | [Simplify Admin Views*][simplify-admin] | Elisa Foltyn | N/A | | N/A | [Simplify Admin Views*][simplify-admin2] | Astrid Günther | N/A | | N/A | [Composer support][composer] | Astrid Günther | N/A | +| N/A | [Release Versioning][release-versioning] | Niels Braczek | Harald Leithner | *) Two different proposals on the same target, need to be merged @@ -68,3 +69,4 @@ _**Legend:** A = Accepted | D = Draft | P = Pre-Draft | R = Review | X = Depreca [simplify-admin2]: https://github.com/joomla-x/joomla-standards/pull/7 [composer]: https://github.com/joomla-x/joomla-standards/pull/8 [rfc-procedure]: https://github.com/joomla-x/joomla-standards/blob/master/accepted/RFC-0-rfc-meta.md +[release-versioning]: https://github.com/joomla-x/joomla-Specifications/tree/master/proposed diff --git a/proposed/release-versioning-meta.md b/proposed/release-versioning-meta.md new file mode 100644 index 0000000..1471b5f --- /dev/null +++ b/proposed/release-versioning-meta.md @@ -0,0 +1,101 @@ +# Release Versioning Meta Document + +## 1. Summary + +_This Specification describes how versions are numbered in Joomla projects._ + +This specification aims to help developers, code reviewers and release leads to determine, whether a new piece or a +change of code belongs to the next patch, minor or major release. It will also cover documentation requirements that are +related to versioning. + +## 2. Why Bother? + +Sometimes it is hard to tell bug fixes and features apart. The Release Leads want a comprehensive set of rules to be +able to make good and fast decisions about the target release of a contribution. + +## 3. Scope + +The specification applies to all Joomla projects, e.g., the CMS, the Framework, core extensions, ... + +## 4. Considerations + +### 4.1 Semantic Versioning + +Joomla started to follow [Semantic Versioning](Semver) with release 3.3 in April 2014. The rules were not always +followed strictly ([Issue#16874](16874), [Issue#17583](17583), [Issue#24728](24728) to name a few). This need to change, +because the Joomla ecosystem is heavily depending on reliable information about compatibility. This is done in section 1 +of the specification. + +[Semver]: https://github.com/semver/semver/blob/master/semver.md + +[24728]: https://issues.joomla.org/tracker/joomla-cms/24728 + +[17583]: https://github.com/joomla/joomla-cms/issues/17583 + +[16874]: https://github.com/joomla/joomla-cms/issues/16874 + +### 4.2 API + +The main problem with Semantic Versioning seems to be to categorise code contributions as bug fixes or features. This +should be easy enough, as the Semantic Versioning specification is quite clear in that regard. Strongly simplified, it +says: Changes that do not change signatures of functions or methods of the public API and do not add new ones can go +into a patch release. + +Unfortunately, Joomla has no official definition of "public API". Section 2 of the specification makes up for the +omission. It describes, how public and internal API are told apart using the `@api` and `@internal` annotations as +proposed by the draft [PSR19][]. It is recommended to add these annotations to existing code. For new code they are +required. + +[PSR19]: https://github.com/php-fig/fig-standards/blob/master/proposed/phpdoc-tags.md + +### 4.3 Deprecations + +The core contributors have not been very cautious about deprecations in the past. +`JObject`, for example, has +been [deprecated for Joomla 3.4](https://github.com/joomla/joomla-cms/issues/6125#issuecomment-75035212), but made it +into Joomla 4 (although renamed to CMSObject) with the deprecation annotation still in place. Section 3 describes how to +handle and document deprecations. Among other things, it stipulates that a replacement must be offered and used, if the +deprecation is not phasing out a feature. The documentation requirements make sure that legacy code can easily be +adopted to the new implementation. + +### 4.4 User Interface + +From a code point of view, improvements of the user interface do not touch the API and thus are not features in the +sense of Semantic Versioning. Template files, however, work like functions, since they rely on injected variables to +produce the output. This interface between a View and a template is a contract and as such underlies the rules of +Semantic Versioning. Section 4 provides the rules for dealing with user interface changes. + +### 4.5 External Dependencies + +Section 5 deals with external dependencies, a.k.a. libraries. The implementation details of external dependencies are not under the control of the Joomla project. Therefore, the code +of these dependencies is basically internal and does not fall under Semantic Versioning. As a result, these dependencies +can be updated or even exchanged without breaking the compatibility promise. As soon as an external API officially becomes part of the +public API, as in the case of Bootstrap, the rules of Semantic Versioning do apply. + +## 5. People + +### 5.1 Editor(s) + +* Niels Braczek + +### 5.2 Sponsors + +* Harald Leithner + +### 5.3 Contributors + +* N/A + +## 6. Votes + +* **Entrance Vote:** 2021-11-16 Production Team Leaders +* **Acceptance Vote:** _(not yet taken)_ + +## 7. Relevant Links + +_**Note:** Order descending chronologically._ +* [Semantic Versioning Specification](Semver) + +## 8. Errata + +... diff --git a/proposed/release-versioning.md b/proposed/release-versioning.md new file mode 100644 index 0000000..3e3b5b9 --- /dev/null +++ b/proposed/release-versioning.md @@ -0,0 +1,116 @@ +# Release Versioning + +This specification describes how versions are numbered in Joomla projects. It also defines documentation requirements +related to versioning. + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", +"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119][]. + +[RFC 2119]: https://www.rfc-editor.org/rfc/rfc2119.html + +### Definitions + +* "Structural Element" is a collection of Programming Constructs which MAY be preceded by a DocBlock. The collection + contains the following constructs: + * class + * interface + * trait + * function (including methods) + * property + * constant + * variables, both local and global scope. + +### References + +- [RFC 2119][]: Key words for use in RFCs to Indicate Requirement Levels + +## Specification + +### 1 Semantic Versioning + +1. All releases in all Joomla projects MUST strictly follow the [Semantic Versioning](Semver) specification in its + current version. + +[Semver]: https://github.com/semver/semver/blob/master/semver.md + +### 2 API + +Because Semantic Versioning applies to public API only, a distinction between public and internal API is needed. + +#### 2.1 Public API + +"Public API" describes the sum of structural elements that MAY be used by extension developers. + +1. Changes to the public API are subject to the rules of semantic versioning. +2. In order to distinguish these elements from the internal elements, the following applies: + 1. Existing public structural elements SHOULD be annotated with `@api` in the corresponding DocBlock. + 1. Newly introduced public structural elements MUST be annotated with `@api` in the corresponding DocBlock. + 2. Public structural elements MUST be included in the official documentation. + +#### 2.2 Internal API + +The "Internal API" comprises the structural elements that are supportive for the core and SHOULD NOT be used by +extension developers. + +1. Changes to the internal API are not subject to the rules of semantic versioning. +2. In order to distinguish these elements from the public elements, the following applies: + 1. Existing internal structural elements SHOULD be annotated with `@internal` in the corresponding DocBlock. + 1. Newly introduced structural elements MUST be annotated with `@internal` in the corresponding DocBlock. + 2. Structural elements with the access modifier `private` and structural elements in final classes with the access + modifier `protected` MUST be treated as internal. + 3. Internal structural elements MUST NOT be included in the official documentation. All documentation needed by core + developers SHOULD be available in the corresponding DocBlock. + +### 3 Deprecations + +Deprecating existing functionality is a normal part of software development and is often required to make forward +progress. There are two reasons to deprecate code: (1) architectural changes or (2) removal of features. This section +applies to public API only. + +#### 3.1 Architectural Changes + +1. The official documentation MUST be updated to let users know about the change. + 1. The DocBlock MUST be annotated to document the replacement for the deprecated element. The deprecation annotation + SHOULD be supplemented by a usage example for the replacement. Example: + ```php + /** + * ... + * @deprecated X.Y Will be removed in X+1.0. Use instead. + * Before (version < X.Y): + * + * After (version >= X.Y): + * + */ + ``` + with X.Y being the minor version introducing the deprecation. +2. A new minor release MUST be issued with the deprecation in place. That release MUST NOT use the deprecated code + anywhere. +3. The deprecated code MUST be removed in the next major release. + +#### 3.2 Phasing out Features + +1. The official documentation MUST be updated to let users know about the change. + 1. The DocBlock MUST be annotated to document the replacement for the deprecated element. The deprecation annotation + SHOULD be supplemented by a recommendation for an alternative. Example: + ```php + /** + * ... + * @deprecated X.Y Will be removed in X+1.0 without replacement. + * Please consider using or instead. + */ + ``` + with X.Y being the minor version introducing the deprecation. +2. A new minor release MUST be issued with the deprecation in place. That release MUST NOT use the deprecated code + anywhere. +3. The deprecated code MUST be removed in the next major release. + +### 4 User Interface + +1. Changes to the user interface are considered compatible if they do not affect the public API. +2. If variables passed to or required by template files change, it MUST be treated as a change of the public API. + +### 5 External Dependencies + +1. Updating external dependencies is considered compatible since it does not affect the public API. +2. The API of the dependencies is not part of the project's API, unless explicitly stated in the official documentation. +3. Extension developers MUST NOT rely on the presence of dependencies that are not covered in the official documentation. From 0f4a64887c6ade48dcb9c4d14fca0ed096d3551f Mon Sep 17 00:00:00 2001 From: Niels Braczek Date: Sun, 21 Nov 2021 23:27:06 +0100 Subject: [PATCH 02/12] Elaborate Section 4.3 of the meta document to answer https://github.com/joomla/rfc/pull/29#discussion_r753728936 --- proposed/release-versioning-meta.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/proposed/release-versioning-meta.md b/proposed/release-versioning-meta.md index 1471b5f..0dcbe16 100644 --- a/proposed/release-versioning-meta.md +++ b/proposed/release-versioning-meta.md @@ -55,8 +55,15 @@ The core contributors have not been very cautious about deprecations in the past been [deprecated for Joomla 3.4](https://github.com/joomla/joomla-cms/issues/6125#issuecomment-75035212), but made it into Joomla 4 (although renamed to CMSObject) with the deprecation annotation still in place. Section 3 describes how to handle and document deprecations. Among other things, it stipulates that a replacement must be offered and used, if the -deprecation is not phasing out a feature. The documentation requirements make sure that legacy code can easily be -adopted to the new implementation. +deprecation is not phasing out a feature. This requirement serves several purposes: + +- It proves that the new solution actually works where it will end up being used. +- It shows developers, who consider the code as authoritative documentation, how the new solution works and should be + used. +- It prevents the release lead of the following major version from being surprised by the "leftovers" and maybe + therefore not being able to meet the schedule. + +The documentation requirements make sure that legacy code can easily be adopted to the new implementation. ### 4.4 User Interface @@ -67,10 +74,11 @@ Semantic Versioning. Section 4 provides the rules for dealing with user interfac ### 4.5 External Dependencies -Section 5 deals with external dependencies, a.k.a. libraries. The implementation details of external dependencies are not under the control of the Joomla project. Therefore, the code -of these dependencies is basically internal and does not fall under Semantic Versioning. As a result, these dependencies -can be updated or even exchanged without breaking the compatibility promise. As soon as an external API officially becomes part of the -public API, as in the case of Bootstrap, the rules of Semantic Versioning do apply. +Section 5 deals with external dependencies, a.k.a. libraries. The implementation details of external dependencies are +not under the control of the Joomla project. Therefore, the code of these dependencies is basically internal and does +not fall under Semantic Versioning. As a result, these dependencies can be updated or even exchanged without breaking +the compatibility promise. As soon as an external API officially becomes part of the public API, as in the case of +Bootstrap, the rules of Semantic Versioning do apply. ## 5. People @@ -94,6 +102,7 @@ public API, as in the case of Bootstrap, the rules of Semantic Versioning do app ## 7. Relevant Links _**Note:** Order descending chronologically._ + * [Semantic Versioning Specification](Semver) ## 8. Errata From b061e4836f3befeba16716e8421513944502cc7a Mon Sep 17 00:00:00 2001 From: Niels Braczek Date: Tue, 23 Nov 2021 16:14:15 +0100 Subject: [PATCH 03/12] Add examples based on @HLeithner's contribution --- proposed/release-versioning-meta.md | 390 +++++++++++++++++++++++++++- 1 file changed, 382 insertions(+), 8 deletions(-) diff --git a/proposed/release-versioning-meta.md b/proposed/release-versioning-meta.md index 0dcbe16..f11269d 100644 --- a/proposed/release-versioning-meta.md +++ b/proposed/release-versioning-meta.md @@ -80,31 +80,405 @@ not fall under Semantic Versioning. As a result, these dependencies can be updat the compatibility promise. As soon as an external API officially becomes part of the public API, as in the case of Bootstrap, the rules of Semantic Versioning do apply. -## 5. People - -### 5.1 Editor(s) +## 5. Examples + +To illustrate the rules of the specification, here are a few examples. The starting point is this class, which prepares +a fresh Earl Grey tea: + +```php +// Old version +class CupOfTea +{ + /** + * Brew a fresh cup of Earl Grey tea + * + * @param bool $addSugar Set to true, if you want sugar in the tea. + * @param bool $addMilk Set to true, if you want milk in the tea. + * + * @return Cup + */ + public function makeTea($addSugar = false, $addMilk = false) + { + $boiler = new Boiler(); + $boiler->addWater(0.5); + $boiler->brew(97.7778); // heat up to 97.7778 °C + + $tea = new Tea('earl grey'); + + $cup = new Cup(); + $cup->addIngredient($tea); + $cup->addIngredient($boiler->getWater()); + + if ($addSugar) { + $sugar = new Sugar(4); // get 4 gram sugar + $cup->addIngredient($sugar); + } + + if ($addMilk) { + $milk = new Milk(100); // get 100 ml milk + $cup->addIngredient($milk); + } + + return $cup; + } +} +``` + +### 5.1 Scenario 1 - Using New PHP Features + +Let's assume that PHP 8.1 introduces a native function `php_make_caffeine_drink()` to make all kinds of caffeinated hot +drinks. This function does a lot of work for us, so we can greatly simplify `CupOfTea::makeTea()`. However, we still +need to support PHP versions before 8.1. This is where a so-called feature switch comes into play. + +```php +// PHP 8.1 version using native php_make_caffeine_drink() function +class CupOfTea +{ + /** + * Brew a fresh cup of Earl Grey tea + * + * @api + * @param bool $addSugar Set to true, if you want sugar in the tea. + * @param bool $addMilk Set to true, if you want milk in the tea. + * + * @return Cup + */ + public function makeTea($addSugar = false, $addMilk = false) + { + // Feature switch + if (compare_version(PHP_VERSION, '8.1', '>=')) { + $tea = php_make_caffeine_drink(type: 'earl grey', amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 100 : 0); + $cup = new Cup(); + $cup->addIngredient($tea); + return $cup; + } + + // Fall back to old code for PHP < 8.1 + $boiler = new Boiler(); + $boiler->addWater(0.5); + $boiler->brew(97.7778); // heat up to 97.7778 °C + + $tea = new Tea('earl grey'); + + $cup = new Cup(); + $cup->addIngredient($tea); + $cup->addIngredient($boiler->getWater()); + + if ($addSugar) { + $sugar = new Sugar(4); // get 4 gram sugar + $cup->addIngredient($sugar); + } + + if ($addMilk) { + $milk = new Milk(100); // get 100 ml milk + $cup->addIngredient($milk); + } + + return $cup; + } +} +``` + +Since this change does not affect existing code that uses the `CupOfTea::makeTea()` method in any way (nothing needs to +be adjusted there to continue working, after all), it can be released with the next patch release. As soon as official +support for PHP versions < 8.1 ceases, the old code can (must) be removed: + +```php +// PHP 8.1 version using native php_make_caffeine_drink() function +class CupOfTea +{ + /** + * Brew a fresh cup of Earl Grey tea + * + * @api + * @param bool $addSugar Set to true, if you want sugar in the tea. + * @param bool $addMilk Set to true, if you want milk in the tea. + * + * @return Cup + */ + public function makeTea($addSugar = false, $addMilk = false) + { + $tea = php_make_caffeine_drink(type: 'earl grey', amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 100 : 0); + $cup = new Cup(); + $cup->addIngredient($tea); + return $cup; + } +} +``` + +### 5.2 Scenario 2 - Using New PHP Features Adding Functionality + +The PHP function `php_make_caffeine_drink()` in scenario 1 is a function for all caffeinated hot drinks, not just Earl +Grey tea. This opens up the chance for us to also offer other tea varieties such as Darjeeling. We can even go as far as +allowing coffee. + +Here we have to make a fundamental decision: Do we want to build a separate class for coffee? Or would we rather build a +more general version that brews caffeinated hot drinks? + +#### a. Two Separate Classes + +In the first case, the class `CupOfTea` will be enabled to produce different types of tea. Unfortunately, we can't just +change the signature of `makeTea()` - there might be developers who have derived from this class because it wasn't +declared as `final`, and PHP throws an `E_WARNING` if the signatures of parent class and child class don't match. It can +make sense to accept these warnings, but this should be thoroughly considered, justified and documented. Here we choose +the way to replace the `makeTea()` function. + +```php +// PHP 8.1 version using native php_make_caffeine_drink() function +class CupOfTea +{ + /** + * Supported types of tea + * @api + */ + public const EARL_GREY = 'earl grey'; + public const DARJEELING = 'darjeeling'; + + /** + * Brew a fresh cup of Earl Grey tea + * + * @api + * @param bool $addSugar Set to true, if you want sugar in the tea. + * @param bool $addMilk Set to true, if you want milk in the tea. + * + * @return Cup + * + * @deprecated 4.2 Will be removed in 5.0. Use CupOfTea::getTea() instead. + * Before (version < 4.2): + * $teaMaker = new CupOfTea(); + * $tea = $teaMaker->makeTea($addSugar, $addMilk); + * After (version >= 4.2): + * $teaMaker = new CupOfTea(); + * $tea = $teaMaker->getTea(self::EARL_GREY, $addSugar, $addMilk); + */ + public function makeTea($addSugar = false, $addMilk = false) + { + trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED); + + return $this->getTea(self::EARL_GREY, $addSugar, $addMilk); + } + + /** + * Brew a fresh cup of tea + * + * @api + * @param string $type The type of tea, one of the CupOfTea::* constants. + * @param bool $addSugar Set to true, if you want sugar in the tea. + * @param bool $addMilk Set to true, if you want milk in the tea. + * + * @return Cup A cup of tea + * @since 4.2 + */ + public function getTea($type, $addSugar = false, $addMilk = false) + { + if (compare_version(PHP_VERSION, '8.1', '>=')) { + $drink = php_make_caffeine_drink(type: $type, amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 100 : 0); + $cup = new Cup(); + $cup->addIngredient($drink); + return $cup; + } + + // Fall back to old code for PHP < 8.1 + $boiler = new Boiler(); + $boiler->addWater(0.5); + $boiler->brew(97.7778); // heat up to 97.7778 °C + + $tea = new Tea($type); + + $cup = new Cup(); + $cup->addIngredient($tea); + $cup->addIngredient($boiler->getWater()); + + if ($addSugar) { + $sugar = new Sugar(4); // get 4 gram sugar + $cup->addIngredient($sugar); + } + + if ($addMilk) { + $milk = new Milk(100); // get 100 ml milk + $cup->addIngredient($milk); + } + + return $cup; + } +} + +class CupOfCoffee +{ + /** + * Supported types of coffee + * @api + */ + public const ROBUSTA = 'robusta'; + public const ARABICA = 'arabica'; + + /** + * Constructor. + * + * This constructor can be removed, once the PHP minimum requirement is 8.1 or above. + * + * @api + * @since 4.2 + */ + public function __construct() + { + if (compare_version(PHP_VERSION, '8.1', '<')) { + throw new \RuntimeException('This class requires PHP >= 8.1'); + } + } + + /** + * Brew a fresh cup of coffee + * + * @api + * @param string $type The type of coffee, one of the CupOfCoffee::* constants. + * @param bool $addSugar Set to true, if you want sugar in the coffee. + * @param bool $addMilk Set to true, if you want milk in the coffee. + * + * @return Cup + * @since 4.2 + */ + public function getCoffee($type, $addSugar = false, $addMilk = false) + { + $tea = php_make_caffeine_drink(type: $type, amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 50 : 0); + $cup = new Cup(); + $cup->addIngredient($tea); + return $cup; + } +} +``` + +Because you can now make teas other than Earl Grey, and even coffee, the change is considered a feature and therefore +belongs in a minor release. + +#### b. One General Class + +Since the methods `CupOfTea::getTea()` and `CupOfCoffee::getCoffee()` are very similar in end effect, it probably makes +sense in this case to provide a more general solution: a class that makes hot drinks. With the introduction of this new +class, `CupOfTea` becomes redundant and therefore deprecated. + +```php +class CupOfHotDrink +{ + /** + * Supported types of hot drinks + * @api + */ + public const EARL_GREY = 'earl grey'; + public const DARJEELING = 'darjeeling'; + public const ROBUSTA = 'robusta'; + public const ARABICA = 'arabica'; + + /** + * Brew a fresh hot drink + * + * @api + * @param string $type The type of hot drink, one of the CupOfHotDrink::* constants. + * @param bool $addSugar Set to true, if you want sugar in the hot drink. + * @param bool $addMilk Set to true, if you want milk in the hot drink. + * + * @return Cup + * @since 4.2 + */ + public function getHotDrink($type, $addSugar = false, $addMilk = false) + { + if (compare_version(PHP_VERSION, '8.1', '<')) { + $tea = php_make_caffeine_drink(type: $type, amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 50 : 0); + $cup = new Cup(); + $cup->addIngredient($tea); + return $cup; + } + + if ($type !== self::EARL_GREY) { + throw new \RuntimeException('To brew hot drinks other than Earl Gray tea you need PHP 8.1 or above.'); + } + + // Fall back to old code for PHP < 8.1 + $boiler = new Boiler(); + $boiler->addWater(0.5); + $boiler->brew(97.7778); // heat up to 97.7778 °C + + $tea = new Tea('earl grey'); + + $cup = new Cup(); + $cup->addIngredient($tea); + $cup->addIngredient($boiler->getWater()); + + if ($addSugar) { + $sugar = new Sugar(4); // get 4 gram sugar + $cup->addIngredient($sugar); + } + + if ($addMilk) { + $milk = new Milk(100); // get 100 ml milk + $cup->addIngredient($milk); + } + + return $cup; + } +} + +/** + * @deprecated 4.2 Will be removed in 5.0. Use CupOfHotDrink instead. + */ +class CupOfTea +{ + /** + * Brew a fresh cup of Earl Grey tea + * + * @api + * @param bool $addSugar Set to true, if you want sugar in the tea. + * @param bool $addMilk Set to true, if you want milk in the tea. + * + * @return Cup + * + * @deprecated 4.2 Will be removed in 5.0. Use CupOfHotDrink::getHotDrink() instead. + * Before (version < 4.2): + * $brewer = new CupOfTea(); + * $tea = $brewer->makeTea($addSugar, $addMilk); + * After (version >= 4.2): + * $brewer = new CupOfHotDrink(); + * $tea = $brewer->getHotDrink(CupOfHotDrink::EARL_GREY, $addSugar, $addMilk); + */ + public function makeTea($addSugar = false, $addMilk = false) + { + trigger_error('Method ' . __METHOD__ . ' is deprecated. Use CupOfHotDrink::getHotDrink() instead.', E_USER_DEPRECATED); + + $brewer = new CupOfHotDrink(); + return $brewer->getHotDrink(CupOfHotDrink::EARL_GREY, $addSugar, $addMilk); + } +} +``` + +This solution will maintain the functionality `CupOfTea` until the next major version. From the next minor release that +includes this change, users in a modern environment will be able to use the new possibilities immediately. For those in +older environments, no functionality will be lost. + +## 6. People + +### 6.1 Editor(s) * Niels Braczek -### 5.2 Sponsors +### 6.2 Sponsors * Harald Leithner -### 5.3 Contributors +### 6.3 Contributors * N/A -## 6. Votes +## 7. Votes * **Entrance Vote:** 2021-11-16 Production Team Leaders * **Acceptance Vote:** _(not yet taken)_ -## 7. Relevant Links +## 8. Relevant Links _**Note:** Order descending chronologically._ * [Semantic Versioning Specification](Semver) -## 8. Errata +## 9. Errata ... From 39ee97a355fd65144bf67b2048b8efe331b17c39 Mon Sep 17 00:00:00 2001 From: Niels Braczek Date: Tue, 23 Nov 2021 16:17:52 +0100 Subject: [PATCH 04/12] Reverted rushed replacements --- proposed/release-versioning-meta.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proposed/release-versioning-meta.md b/proposed/release-versioning-meta.md index f11269d..5755fc2 100644 --- a/proposed/release-versioning-meta.md +++ b/proposed/release-versioning-meta.md @@ -101,7 +101,7 @@ class CupOfTea { $boiler = new Boiler(); $boiler->addWater(0.5); - $boiler->brew(97.7778); // heat up to 97.7778 °C + $boiler->cook(97.7778); // heat up to 97.7778 °C $tea = new Tea('earl grey'); @@ -156,7 +156,7 @@ class CupOfTea // Fall back to old code for PHP < 8.1 $boiler = new Boiler(); $boiler->addWater(0.5); - $boiler->brew(97.7778); // heat up to 97.7778 °C + $boiler->cook(97.7778); // heat up to 97.7778 °C $tea = new Tea('earl grey'); @@ -281,7 +281,7 @@ class CupOfTea // Fall back to old code for PHP < 8.1 $boiler = new Boiler(); $boiler->addWater(0.5); - $boiler->brew(97.7778); // heat up to 97.7778 °C + $boiler->cook(97.7778); // heat up to 97.7778 °C $tea = new Tea($type); @@ -396,7 +396,7 @@ class CupOfHotDrink // Fall back to old code for PHP < 8.1 $boiler = new Boiler(); $boiler->addWater(0.5); - $boiler->brew(97.7778); // heat up to 97.7778 °C + $boiler->cook(97.7778); // heat up to 97.7778 °C $tea = new Tea('earl grey'); From 35cb62d5bd4fcbe051692775b8eb5c236ce76894 Mon Sep 17 00:00:00 2001 From: Niels Braczek Date: Tue, 23 Nov 2021 19:01:08 +0100 Subject: [PATCH 05/12] Small editorial changes in the example code's comments --- proposed/release-versioning-meta.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proposed/release-versioning-meta.md b/proposed/release-versioning-meta.md index 5755fc2..7c97755 100644 --- a/proposed/release-versioning-meta.md +++ b/proposed/release-versioning-meta.md @@ -245,11 +245,11 @@ class CupOfTea * * @deprecated 4.2 Will be removed in 5.0. Use CupOfTea::getTea() instead. * Before (version < 4.2): - * $teaMaker = new CupOfTea(); - * $tea = $teaMaker->makeTea($addSugar, $addMilk); + * $brewer = new CupOfTea(); + * $tea = $brewer->makeTea($addSugar, $addMilk); * After (version >= 4.2): - * $teaMaker = new CupOfTea(); - * $tea = $teaMaker->getTea(self::EARL_GREY, $addSugar, $addMilk); + * $brewer = new CupOfTea(); + * $tea = $brewer->getTea(CupOfTea::EARL_GREY, $addSugar, $addMilk); */ public function makeTea($addSugar = false, $addMilk = false) { From 86327ae88fa5a032281a6564deefabcfe98556e1 Mon Sep 17 00:00:00 2001 From: Niels Braczek Date: Fri, 26 Nov 2021 14:35:22 +0100 Subject: [PATCH 06/12] Extracted examples into its own document --- proposed/release-versioning-examples.md | 387 +++++++++++++++++++++++ proposed/release-versioning-meta.md | 390 +----------------------- 2 files changed, 395 insertions(+), 382 deletions(-) create mode 100644 proposed/release-versioning-examples.md diff --git a/proposed/release-versioning-examples.md b/proposed/release-versioning-examples.md new file mode 100644 index 0000000..32fa580 --- /dev/null +++ b/proposed/release-versioning-examples.md @@ -0,0 +1,387 @@ +# Release Versioning Examples + +This document contains a collection of examples to illustrate the rules of the Release Versioning specification. + +* [Case: Utilising a New PHP Feature](#case-1) + * [Scenario 1 - Using the New PHP Feature to Replace Code](#case-1-1) + * [Scenario 2 - Using the New PHP Feature to Extend Functionality](#case-1-2) + +More cases may be added over time. + + + +## Case: Utilising a New PHP Feature + +The starting point is this class, which prepares a fresh Earl Grey tea: + +```php +// Old version +class CupOfTea +{ + /** + * Brew a fresh cup of Earl Grey tea + * + * @param bool $addSugar Set to true, if you want sugar in the tea. + * @param bool $addMilk Set to true, if you want milk in the tea. + * + * @return Cup + */ + public function makeTea($addSugar = false, $addMilk = false) + { + $boiler = new Boiler(); + $boiler->addWater(0.5); + $boiler->cook(97.7778); // heat up to 97.7778 °C + + $tea = new Tea('earl grey'); + + $cup = new Cup(); + $cup->addIngredient($tea); + $cup->addIngredient($boiler->getWater()); + + if ($addSugar) { + $sugar = new Sugar(4); // get 4 gram sugar + $cup->addIngredient($sugar); + } + + if ($addMilk) { + $milk = new Milk(100); // get 100 ml milk + $cup->addIngredient($milk); + } + + return $cup; + } +} +``` + + + +### Scenario 1 - Using the New PHP Feature to Replace Code + +Let's assume that PHP 8.1 introduces a native function `php_make_caffeine_drink()` to make all kinds of caffeinated hot +drinks. This function does a lot of work for us, so we can greatly simplify `CupOfTea::makeTea()`. However, we still +need to support PHP versions before 8.1. This is where a so-called feature switch comes into play. + +```php +// PHP 8.1 version using native php_make_caffeine_drink() function +class CupOfTea +{ + /** + * Brew a fresh cup of Earl Grey tea + * + * @api + * @param bool $addSugar Set to true, if you want sugar in the tea. + * @param bool $addMilk Set to true, if you want milk in the tea. + * + * @return Cup + */ + public function makeTea($addSugar = false, $addMilk = false) + { + // Feature switch + if (compare_version(PHP_VERSION, '8.1', '>=')) { + $tea = php_make_caffeine_drink(type: 'earl grey', amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 100 : 0); + $cup = new Cup(); + $cup->addIngredient($tea); + return $cup; + } + + // Fall back to old code for PHP < 8.1 + $boiler = new Boiler(); + $boiler->addWater(0.5); + $boiler->cook(97.7778); // heat up to 97.7778 °C + + $tea = new Tea('earl grey'); + + $cup = new Cup(); + $cup->addIngredient($tea); + $cup->addIngredient($boiler->getWater()); + + if ($addSugar) { + $sugar = new Sugar(4); // get 4 gram sugar + $cup->addIngredient($sugar); + } + + if ($addMilk) { + $milk = new Milk(100); // get 100 ml milk + $cup->addIngredient($milk); + } + + return $cup; + } +} +``` + +Since this change does not affect existing code that uses the `CupOfTea::makeTea()` method in any way (nothing needs to +be adjusted there to continue working, after all), it can be released with the next patch release. As soon as official +support for PHP versions < 8.1 ceases, the old code can (must) be removed: + +```php +class CupOfTea +{ + /** + * Brew a fresh cup of Earl Grey tea + * + * @api + * @param bool $addSugar Set to true, if you want sugar in the tea. + * @param bool $addMilk Set to true, if you want milk in the tea. + * + * @return Cup + */ + public function makeTea($addSugar = false, $addMilk = false) + { + $tea = php_make_caffeine_drink(type: 'earl grey', amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 100 : 0); + $cup = new Cup(); + $cup->addIngredient($tea); + return $cup; + } +} +``` + + + +## Scenario 2 - Using the New PHP Feature to Extend Functionality + +The PHP function `php_make_caffeine_drink()` in scenario 1 is a function for all caffeinated hot drinks, not just Earl +Grey tea. This opens up the chance for us to also offer other tea varieties such as Darjeeling. We can even go as far as +allowing coffee. + +Here we have to make a fundamental decision: Do we want to build a separate class for coffee? Or would we rather build a +more general version that brews caffeinated hot drinks? + +### a. Two Separate Classes + +In the first case, the class `CupOfTea` will be enabled to produce different types of tea. Unfortunately, we can't just +change the signature of `makeTea()` - there might be developers who have derived from this class because it wasn't +declared as `final`, and PHP throws an `E_WARNING` if the signatures of parent class and child class don't match. It can +make sense to accept these warnings, but this should be thoroughly considered, justified and documented. Here we choose +the way to replace the `makeTea()` function. + +```php +// PHP 8.1 version using native php_make_caffeine_drink() function +class CupOfTea +{ + /** + * Supported types of tea + * @api + */ + public const EARL_GREY = 'earl grey'; + public const DARJEELING = 'darjeeling'; + + /** + * Brew a fresh cup of Earl Grey tea + * + * @api + * @param bool $addSugar Set to true, if you want sugar in the tea. + * @param bool $addMilk Set to true, if you want milk in the tea. + * + * @return Cup + * + * @deprecated 4.2 Will be removed in 5.0. Use CupOfTea::getTea() instead. + * Before (version < 4.2): + * $brewer = new CupOfTea(); + * $tea = $brewer->makeTea($addSugar, $addMilk); + * After (version >= 4.2): + * $brewer = new CupOfTea(); + * $tea = $brewer->getTea(CupOfTea::EARL_GREY, $addSugar, $addMilk); + */ + public function makeTea($addSugar = false, $addMilk = false) + { + trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED); + + return $this->getTea(self::EARL_GREY, $addSugar, $addMilk); + } + + /** + * Brew a fresh cup of tea + * + * @api + * @param string $type The type of tea, one of the CupOfTea::* constants. + * @param bool $addSugar Set to true, if you want sugar in the tea. + * @param bool $addMilk Set to true, if you want milk in the tea. + * + * @return Cup A cup of tea + * @since 4.2 + */ + public function getTea($type, $addSugar = false, $addMilk = false) + { + if (compare_version(PHP_VERSION, '8.1', '>=')) { + $drink = php_make_caffeine_drink(type: $type, amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 100 : 0); + $cup = new Cup(); + $cup->addIngredient($drink); + return $cup; + } + + // Fall back to old code for PHP < 8.1 + $boiler = new Boiler(); + $boiler->addWater(0.5); + $boiler->cook(97.7778); // heat up to 97.7778 °C + + $tea = new Tea($type); + + $cup = new Cup(); + $cup->addIngredient($tea); + $cup->addIngredient($boiler->getWater()); + + if ($addSugar) { + $sugar = new Sugar(4); // get 4 gram sugar + $cup->addIngredient($sugar); + } + + if ($addMilk) { + $milk = new Milk(100); // get 100 ml milk + $cup->addIngredient($milk); + } + + return $cup; + } +} + +class CupOfCoffee +{ + /** + * Supported types of coffee + * @api + */ + public const ROBUSTA = 'robusta'; + public const ARABICA = 'arabica'; + + /** + * Constructor. + * + * This constructor can be removed, once the PHP minimum requirement is 8.1 or above. + * + * @api + * @since 4.2 + */ + public function __construct() + { + if (compare_version(PHP_VERSION, '8.1', '<')) { + throw new \RuntimeException('This class requires PHP >= 8.1'); + } + } + + /** + * Brew a fresh cup of coffee + * + * @api + * @param string $type The type of coffee, one of the CupOfCoffee::* constants. + * @param bool $addSugar Set to true, if you want sugar in the coffee. + * @param bool $addMilk Set to true, if you want milk in the coffee. + * + * @return Cup + * @since 4.2 + */ + public function getCoffee($type, $addSugar = false, $addMilk = false) + { + $tea = php_make_caffeine_drink(type: $type, amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 50 : 0); + $cup = new Cup(); + $cup->addIngredient($tea); + return $cup; + } +} +``` + +Because you can now make teas other than Earl Grey, and even coffee, the change is considered a feature and therefore +belongs in a minor release. + +### b. One General Class + +Since the methods `CupOfTea::getTea()` and `CupOfCoffee::getCoffee()` are very similar in end effect, it probably makes +sense in this case to provide a more general solution: a class that makes hot drinks. With the introduction of this new +class, `CupOfTea` becomes redundant and therefore deprecated. + +```php +class CupOfHotDrink +{ + /** + * Supported types of hot drinks + * @api + */ + public const EARL_GREY = 'earl grey'; + public const DARJEELING = 'darjeeling'; + public const ROBUSTA = 'robusta'; + public const ARABICA = 'arabica'; + + /** + * Brew a fresh hot drink + * + * @api + * @param string $type The type of hot drink, one of the CupOfHotDrink::* constants. + * @param bool $addSugar Set to true, if you want sugar in the hot drink. + * @param bool $addMilk Set to true, if you want milk in the hot drink. + * + * @return Cup + * @since 4.2 + */ + public function getHotDrink($type, $addSugar = false, $addMilk = false) + { + if (compare_version(PHP_VERSION, '8.1', '<')) { + $tea = php_make_caffeine_drink(type: $type, amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 50 : 0); + $cup = new Cup(); + $cup->addIngredient($tea); + return $cup; + } + + if ($type !== self::EARL_GREY) { + throw new \RuntimeException('To brew hot drinks other than Earl Gray tea you need PHP 8.1 or above.'); + } + + // Fall back to old code for PHP < 8.1 + $boiler = new Boiler(); + $boiler->addWater(0.5); + $boiler->cook(97.7778); // heat up to 97.7778 °C + + $tea = new Tea('earl grey'); + + $cup = new Cup(); + $cup->addIngredient($tea); + $cup->addIngredient($boiler->getWater()); + + if ($addSugar) { + $sugar = new Sugar(4); // get 4 gram sugar + $cup->addIngredient($sugar); + } + + if ($addMilk) { + $milk = new Milk(100); // get 100 ml milk + $cup->addIngredient($milk); + } + + return $cup; + } +} + +/** + * @deprecated 4.2 Will be removed in 5.0. Use CupOfHotDrink instead. + */ +class CupOfTea +{ + /** + * Brew a fresh cup of Earl Grey tea + * + * @api + * @param bool $addSugar Set to true, if you want sugar in the tea. + * @param bool $addMilk Set to true, if you want milk in the tea. + * + * @return Cup + * + * @deprecated 4.2 Will be removed in 5.0. Use CupOfHotDrink::getHotDrink() instead. + * Before (version < 4.2): + * $brewer = new CupOfTea(); + * $tea = $brewer->makeTea($addSugar, $addMilk); + * After (version >= 4.2): + * $brewer = new CupOfHotDrink(); + * $tea = $brewer->getHotDrink(CupOfHotDrink::EARL_GREY, $addSugar, $addMilk); + */ + public function makeTea($addSugar = false, $addMilk = false) + { + trigger_error('Method ' . __METHOD__ . ' is deprecated. Use CupOfHotDrink::getHotDrink() instead.', E_USER_DEPRECATED); + + $brewer = new CupOfHotDrink(); + return $brewer->getHotDrink(CupOfHotDrink::EARL_GREY, $addSugar, $addMilk); + } +} +``` + +This solution will maintain the functionality `CupOfTea` until the next major version. From the next minor release that +includes this change, users in a modern environment will be able to use the new possibilities immediately. For those in +older environments, no functionality will be lost. diff --git a/proposed/release-versioning-meta.md b/proposed/release-versioning-meta.md index 7c97755..0dcbe16 100644 --- a/proposed/release-versioning-meta.md +++ b/proposed/release-versioning-meta.md @@ -80,405 +80,31 @@ not fall under Semantic Versioning. As a result, these dependencies can be updat the compatibility promise. As soon as an external API officially becomes part of the public API, as in the case of Bootstrap, the rules of Semantic Versioning do apply. -## 5. Examples - -To illustrate the rules of the specification, here are a few examples. The starting point is this class, which prepares -a fresh Earl Grey tea: - -```php -// Old version -class CupOfTea -{ - /** - * Brew a fresh cup of Earl Grey tea - * - * @param bool $addSugar Set to true, if you want sugar in the tea. - * @param bool $addMilk Set to true, if you want milk in the tea. - * - * @return Cup - */ - public function makeTea($addSugar = false, $addMilk = false) - { - $boiler = new Boiler(); - $boiler->addWater(0.5); - $boiler->cook(97.7778); // heat up to 97.7778 °C - - $tea = new Tea('earl grey'); - - $cup = new Cup(); - $cup->addIngredient($tea); - $cup->addIngredient($boiler->getWater()); - - if ($addSugar) { - $sugar = new Sugar(4); // get 4 gram sugar - $cup->addIngredient($sugar); - } - - if ($addMilk) { - $milk = new Milk(100); // get 100 ml milk - $cup->addIngredient($milk); - } - - return $cup; - } -} -``` - -### 5.1 Scenario 1 - Using New PHP Features - -Let's assume that PHP 8.1 introduces a native function `php_make_caffeine_drink()` to make all kinds of caffeinated hot -drinks. This function does a lot of work for us, so we can greatly simplify `CupOfTea::makeTea()`. However, we still -need to support PHP versions before 8.1. This is where a so-called feature switch comes into play. - -```php -// PHP 8.1 version using native php_make_caffeine_drink() function -class CupOfTea -{ - /** - * Brew a fresh cup of Earl Grey tea - * - * @api - * @param bool $addSugar Set to true, if you want sugar in the tea. - * @param bool $addMilk Set to true, if you want milk in the tea. - * - * @return Cup - */ - public function makeTea($addSugar = false, $addMilk = false) - { - // Feature switch - if (compare_version(PHP_VERSION, '8.1', '>=')) { - $tea = php_make_caffeine_drink(type: 'earl grey', amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 100 : 0); - $cup = new Cup(); - $cup->addIngredient($tea); - return $cup; - } - - // Fall back to old code for PHP < 8.1 - $boiler = new Boiler(); - $boiler->addWater(0.5); - $boiler->cook(97.7778); // heat up to 97.7778 °C - - $tea = new Tea('earl grey'); - - $cup = new Cup(); - $cup->addIngredient($tea); - $cup->addIngredient($boiler->getWater()); - - if ($addSugar) { - $sugar = new Sugar(4); // get 4 gram sugar - $cup->addIngredient($sugar); - } - - if ($addMilk) { - $milk = new Milk(100); // get 100 ml milk - $cup->addIngredient($milk); - } - - return $cup; - } -} -``` - -Since this change does not affect existing code that uses the `CupOfTea::makeTea()` method in any way (nothing needs to -be adjusted there to continue working, after all), it can be released with the next patch release. As soon as official -support for PHP versions < 8.1 ceases, the old code can (must) be removed: - -```php -// PHP 8.1 version using native php_make_caffeine_drink() function -class CupOfTea -{ - /** - * Brew a fresh cup of Earl Grey tea - * - * @api - * @param bool $addSugar Set to true, if you want sugar in the tea. - * @param bool $addMilk Set to true, if you want milk in the tea. - * - * @return Cup - */ - public function makeTea($addSugar = false, $addMilk = false) - { - $tea = php_make_caffeine_drink(type: 'earl grey', amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 100 : 0); - $cup = new Cup(); - $cup->addIngredient($tea); - return $cup; - } -} -``` - -### 5.2 Scenario 2 - Using New PHP Features Adding Functionality - -The PHP function `php_make_caffeine_drink()` in scenario 1 is a function for all caffeinated hot drinks, not just Earl -Grey tea. This opens up the chance for us to also offer other tea varieties such as Darjeeling. We can even go as far as -allowing coffee. - -Here we have to make a fundamental decision: Do we want to build a separate class for coffee? Or would we rather build a -more general version that brews caffeinated hot drinks? - -#### a. Two Separate Classes - -In the first case, the class `CupOfTea` will be enabled to produce different types of tea. Unfortunately, we can't just -change the signature of `makeTea()` - there might be developers who have derived from this class because it wasn't -declared as `final`, and PHP throws an `E_WARNING` if the signatures of parent class and child class don't match. It can -make sense to accept these warnings, but this should be thoroughly considered, justified and documented. Here we choose -the way to replace the `makeTea()` function. - -```php -// PHP 8.1 version using native php_make_caffeine_drink() function -class CupOfTea -{ - /** - * Supported types of tea - * @api - */ - public const EARL_GREY = 'earl grey'; - public const DARJEELING = 'darjeeling'; - - /** - * Brew a fresh cup of Earl Grey tea - * - * @api - * @param bool $addSugar Set to true, if you want sugar in the tea. - * @param bool $addMilk Set to true, if you want milk in the tea. - * - * @return Cup - * - * @deprecated 4.2 Will be removed in 5.0. Use CupOfTea::getTea() instead. - * Before (version < 4.2): - * $brewer = new CupOfTea(); - * $tea = $brewer->makeTea($addSugar, $addMilk); - * After (version >= 4.2): - * $brewer = new CupOfTea(); - * $tea = $brewer->getTea(CupOfTea::EARL_GREY, $addSugar, $addMilk); - */ - public function makeTea($addSugar = false, $addMilk = false) - { - trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED); - - return $this->getTea(self::EARL_GREY, $addSugar, $addMilk); - } - - /** - * Brew a fresh cup of tea - * - * @api - * @param string $type The type of tea, one of the CupOfTea::* constants. - * @param bool $addSugar Set to true, if you want sugar in the tea. - * @param bool $addMilk Set to true, if you want milk in the tea. - * - * @return Cup A cup of tea - * @since 4.2 - */ - public function getTea($type, $addSugar = false, $addMilk = false) - { - if (compare_version(PHP_VERSION, '8.1', '>=')) { - $drink = php_make_caffeine_drink(type: $type, amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 100 : 0); - $cup = new Cup(); - $cup->addIngredient($drink); - return $cup; - } - - // Fall back to old code for PHP < 8.1 - $boiler = new Boiler(); - $boiler->addWater(0.5); - $boiler->cook(97.7778); // heat up to 97.7778 °C - - $tea = new Tea($type); - - $cup = new Cup(); - $cup->addIngredient($tea); - $cup->addIngredient($boiler->getWater()); - - if ($addSugar) { - $sugar = new Sugar(4); // get 4 gram sugar - $cup->addIngredient($sugar); - } - - if ($addMilk) { - $milk = new Milk(100); // get 100 ml milk - $cup->addIngredient($milk); - } - - return $cup; - } -} - -class CupOfCoffee -{ - /** - * Supported types of coffee - * @api - */ - public const ROBUSTA = 'robusta'; - public const ARABICA = 'arabica'; - - /** - * Constructor. - * - * This constructor can be removed, once the PHP minimum requirement is 8.1 or above. - * - * @api - * @since 4.2 - */ - public function __construct() - { - if (compare_version(PHP_VERSION, '8.1', '<')) { - throw new \RuntimeException('This class requires PHP >= 8.1'); - } - } - - /** - * Brew a fresh cup of coffee - * - * @api - * @param string $type The type of coffee, one of the CupOfCoffee::* constants. - * @param bool $addSugar Set to true, if you want sugar in the coffee. - * @param bool $addMilk Set to true, if you want milk in the coffee. - * - * @return Cup - * @since 4.2 - */ - public function getCoffee($type, $addSugar = false, $addMilk = false) - { - $tea = php_make_caffeine_drink(type: $type, amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 50 : 0); - $cup = new Cup(); - $cup->addIngredient($tea); - return $cup; - } -} -``` - -Because you can now make teas other than Earl Grey, and even coffee, the change is considered a feature and therefore -belongs in a minor release. - -#### b. One General Class - -Since the methods `CupOfTea::getTea()` and `CupOfCoffee::getCoffee()` are very similar in end effect, it probably makes -sense in this case to provide a more general solution: a class that makes hot drinks. With the introduction of this new -class, `CupOfTea` becomes redundant and therefore deprecated. - -```php -class CupOfHotDrink -{ - /** - * Supported types of hot drinks - * @api - */ - public const EARL_GREY = 'earl grey'; - public const DARJEELING = 'darjeeling'; - public const ROBUSTA = 'robusta'; - public const ARABICA = 'arabica'; - - /** - * Brew a fresh hot drink - * - * @api - * @param string $type The type of hot drink, one of the CupOfHotDrink::* constants. - * @param bool $addSugar Set to true, if you want sugar in the hot drink. - * @param bool $addMilk Set to true, if you want milk in the hot drink. - * - * @return Cup - * @since 4.2 - */ - public function getHotDrink($type, $addSugar = false, $addMilk = false) - { - if (compare_version(PHP_VERSION, '8.1', '<')) { - $tea = php_make_caffeine_drink(type: $type, amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 50 : 0); - $cup = new Cup(); - $cup->addIngredient($tea); - return $cup; - } - - if ($type !== self::EARL_GREY) { - throw new \RuntimeException('To brew hot drinks other than Earl Gray tea you need PHP 8.1 or above.'); - } - - // Fall back to old code for PHP < 8.1 - $boiler = new Boiler(); - $boiler->addWater(0.5); - $boiler->cook(97.7778); // heat up to 97.7778 °C - - $tea = new Tea('earl grey'); - - $cup = new Cup(); - $cup->addIngredient($tea); - $cup->addIngredient($boiler->getWater()); - - if ($addSugar) { - $sugar = new Sugar(4); // get 4 gram sugar - $cup->addIngredient($sugar); - } - - if ($addMilk) { - $milk = new Milk(100); // get 100 ml milk - $cup->addIngredient($milk); - } - - return $cup; - } -} - -/** - * @deprecated 4.2 Will be removed in 5.0. Use CupOfHotDrink instead. - */ -class CupOfTea -{ - /** - * Brew a fresh cup of Earl Grey tea - * - * @api - * @param bool $addSugar Set to true, if you want sugar in the tea. - * @param bool $addMilk Set to true, if you want milk in the tea. - * - * @return Cup - * - * @deprecated 4.2 Will be removed in 5.0. Use CupOfHotDrink::getHotDrink() instead. - * Before (version < 4.2): - * $brewer = new CupOfTea(); - * $tea = $brewer->makeTea($addSugar, $addMilk); - * After (version >= 4.2): - * $brewer = new CupOfHotDrink(); - * $tea = $brewer->getHotDrink(CupOfHotDrink::EARL_GREY, $addSugar, $addMilk); - */ - public function makeTea($addSugar = false, $addMilk = false) - { - trigger_error('Method ' . __METHOD__ . ' is deprecated. Use CupOfHotDrink::getHotDrink() instead.', E_USER_DEPRECATED); - - $brewer = new CupOfHotDrink(); - return $brewer->getHotDrink(CupOfHotDrink::EARL_GREY, $addSugar, $addMilk); - } -} -``` - -This solution will maintain the functionality `CupOfTea` until the next major version. From the next minor release that -includes this change, users in a modern environment will be able to use the new possibilities immediately. For those in -older environments, no functionality will be lost. - -## 6. People - -### 6.1 Editor(s) +## 5. People + +### 5.1 Editor(s) * Niels Braczek -### 6.2 Sponsors +### 5.2 Sponsors * Harald Leithner -### 6.3 Contributors +### 5.3 Contributors * N/A -## 7. Votes +## 6. Votes * **Entrance Vote:** 2021-11-16 Production Team Leaders * **Acceptance Vote:** _(not yet taken)_ -## 8. Relevant Links +## 7. Relevant Links _**Note:** Order descending chronologically._ * [Semantic Versioning Specification](Semver) -## 9. Errata +## 8. Errata ... From 8abf92585e6f80855f2041878e6f262057e965c3 Mon Sep 17 00:00:00 2001 From: Niels Braczek Date: Fri, 26 Nov 2021 16:10:25 +0100 Subject: [PATCH 07/12] Fix incorrect phrasing in section 3.2 --- proposed/release-versioning.md | 49 +++++++++++++++++----------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/proposed/release-versioning.md b/proposed/release-versioning.md index 3e3b5b9..5dfeff9 100644 --- a/proposed/release-versioning.md +++ b/proposed/release-versioning.md @@ -70,19 +70,19 @@ applies to public API only. #### 3.1 Architectural Changes 1. The official documentation MUST be updated to let users know about the change. - 1. The DocBlock MUST be annotated to document the replacement for the deprecated element. The deprecation annotation - SHOULD be supplemented by a usage example for the replacement. Example: - ```php - /** - * ... - * @deprecated X.Y Will be removed in X+1.0. Use instead. - * Before (version < X.Y): - * - * After (version >= X.Y): - * - */ - ``` - with X.Y being the minor version introducing the deprecation. +2. The DocBlock MUST be annotated to document the replacement for the deprecated element. The deprecation annotation + SHOULD be supplemented by a usage example for the replacement. Example: + ```php + /** + * ... + * @deprecated X.Y Will be removed in X+1.0. Use instead. + * Before (version < X.Y): + * + * After (version >= X.Y): + * + */ + ``` + with X.Y being the minor version introducing the deprecation. 2. A new minor release MUST be issued with the deprecation in place. That release MUST NOT use the deprecated code anywhere. 3. The deprecated code MUST be removed in the next major release. @@ -90,16 +90,16 @@ applies to public API only. #### 3.2 Phasing out Features 1. The official documentation MUST be updated to let users know about the change. - 1. The DocBlock MUST be annotated to document the replacement for the deprecated element. The deprecation annotation - SHOULD be supplemented by a recommendation for an alternative. Example: - ```php - /** - * ... - * @deprecated X.Y Will be removed in X+1.0 without replacement. - * Please consider using or instead. - */ - ``` - with X.Y being the minor version introducing the deprecation. +1. The DocBlock MUST be annotated to document the future removal of the deprecated element. The deprecation annotation + SHOULD be supplemented by a recommendation for an alternative. Example: + ```php + /** + * ... + * @deprecated X.Y Will be removed in X+1.0 without replacement. + * Please consider using or instead. + */ + ``` + with X.Y being the minor version introducing the deprecation. 2. A new minor release MUST be issued with the deprecation in place. That release MUST NOT use the deprecated code anywhere. 3. The deprecated code MUST be removed in the next major release. @@ -113,4 +113,5 @@ applies to public API only. 1. Updating external dependencies is considered compatible since it does not affect the public API. 2. The API of the dependencies is not part of the project's API, unless explicitly stated in the official documentation. -3. Extension developers MUST NOT rely on the presence of dependencies that are not covered in the official documentation. +3. Extension developers MUST NOT rely on the presence of dependencies that are not covered in the official + documentation. From 8d3e35940c41c14ae5ebdd3eac97f769cc07c7d2 Mon Sep 17 00:00:00 2001 From: Niels Braczek Date: Fri, 26 Nov 2021 16:12:14 +0100 Subject: [PATCH 08/12] Fix wrong function name --- proposed/release-versioning-examples.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proposed/release-versioning-examples.md b/proposed/release-versioning-examples.md index 32fa580..bd1117c 100644 --- a/proposed/release-versioning-examples.md +++ b/proposed/release-versioning-examples.md @@ -77,7 +77,7 @@ class CupOfTea public function makeTea($addSugar = false, $addMilk = false) { // Feature switch - if (compare_version(PHP_VERSION, '8.1', '>=')) { + if (version_compare(PHP_VERSION, '8.1', '>=')) { $tea = php_make_caffeine_drink(type: 'earl grey', amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 100 : 0); $cup = new Cup(); $cup->addIngredient($tea); @@ -203,7 +203,7 @@ class CupOfTea */ public function getTea($type, $addSugar = false, $addMilk = false) { - if (compare_version(PHP_VERSION, '8.1', '>=')) { + if (version_compare(PHP_VERSION, '8.1', '>=')) { $drink = php_make_caffeine_drink(type: $type, amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 100 : 0); $cup = new Cup(); $cup->addIngredient($drink); @@ -254,7 +254,7 @@ class CupOfCoffee */ public function __construct() { - if (compare_version(PHP_VERSION, '8.1', '<')) { + if (version_compare(PHP_VERSION, '8.1', '<')) { throw new \RuntimeException('This class requires PHP >= 8.1'); } } @@ -314,7 +314,7 @@ class CupOfHotDrink */ public function getHotDrink($type, $addSugar = false, $addMilk = false) { - if (compare_version(PHP_VERSION, '8.1', '<')) { + if (version_compare(PHP_VERSION, '8.1', '<')) { $tea = php_make_caffeine_drink(type: $type, amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 50 : 0); $cup = new Cup(); $cup->addIngredient($tea); From f3e60d9b5c43a4517babdb62bf91a4952e0e6dea Mon Sep 17 00:00:00 2001 From: Niels Braczek Date: Mon, 17 Jan 2022 13:52:28 +0100 Subject: [PATCH 09/12] Add @see annotation --- proposed/release-versioning.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/proposed/release-versioning.md b/proposed/release-versioning.md index 5dfeff9..05ff70d 100644 --- a/proposed/release-versioning.md +++ b/proposed/release-versioning.md @@ -75,7 +75,7 @@ applies to public API only. ```php /** * ... - * @deprecated X.Y Will be removed in X+1.0. Use instead. + * @deprecated X.Y Will be removed in X+1.0. Use {@see } instead. * Before (version < X.Y): * * After (version >= X.Y): @@ -83,14 +83,14 @@ applies to public API only. */ ``` with X.Y being the minor version introducing the deprecation. -2. A new minor release MUST be issued with the deprecation in place. That release MUST NOT use the deprecated code +3. A new minor release MUST be issued with the deprecation in place. That release MUST NOT use the deprecated code anywhere. -3. The deprecated code MUST be removed in the next major release. +4. The deprecated code MUST be removed in the next major release. #### 3.2 Phasing out Features 1. The official documentation MUST be updated to let users know about the change. -1. The DocBlock MUST be annotated to document the future removal of the deprecated element. The deprecation annotation +2. The DocBlock MUST be annotated to document the future removal of the deprecated element. The deprecation annotation SHOULD be supplemented by a recommendation for an alternative. Example: ```php /** @@ -100,9 +100,9 @@ applies to public API only. */ ``` with X.Y being the minor version introducing the deprecation. -2. A new minor release MUST be issued with the deprecation in place. That release MUST NOT use the deprecated code +3. A new minor release MUST be issued with the deprecation in place. That release MUST NOT use the deprecated code anywhere. -3. The deprecated code MUST be removed in the next major release. +4. The deprecated code MUST be removed in the next major release. ### 4 User Interface From bd5cd9631daa19f5e81b785bb8f42c6abd32ee01 Mon Sep 17 00:00:00 2001 From: Niels Braczek Date: Wed, 24 Aug 2022 03:43:47 +0200 Subject: [PATCH 10/12] Change removal of deprecated code to x+2 Use "release managers" instead of "release leads" --- proposed/release-versioning-examples.md | 6 +++--- proposed/release-versioning-meta.md | 4 ++-- proposed/release-versioning.md | 14 +++++++------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/proposed/release-versioning-examples.md b/proposed/release-versioning-examples.md index bd1117c..b68f64d 100644 --- a/proposed/release-versioning-examples.md +++ b/proposed/release-versioning-examples.md @@ -175,7 +175,7 @@ class CupOfTea * * @return Cup * - * @deprecated 4.2 Will be removed in 5.0. Use CupOfTea::getTea() instead. + * @deprecated 4.2 Will be removed in 6.0. Use CupOfTea::getTea() instead. * Before (version < 4.2): * $brewer = new CupOfTea(); * $tea = $brewer->makeTea($addSugar, $addMilk); @@ -351,7 +351,7 @@ class CupOfHotDrink } /** - * @deprecated 4.2 Will be removed in 5.0. Use CupOfHotDrink instead. + * @deprecated 4.2 Will be removed in 6.0. Use CupOfHotDrink instead. */ class CupOfTea { @@ -364,7 +364,7 @@ class CupOfTea * * @return Cup * - * @deprecated 4.2 Will be removed in 5.0. Use CupOfHotDrink::getHotDrink() instead. + * @deprecated 4.2 Will be removed in 6.0. Use CupOfHotDrink::getHotDrink() instead. * Before (version < 4.2): * $brewer = new CupOfTea(); * $tea = $brewer->makeTea($addSugar, $addMilk); diff --git a/proposed/release-versioning-meta.md b/proposed/release-versioning-meta.md index 0dcbe16..e6dc8c4 100644 --- a/proposed/release-versioning-meta.md +++ b/proposed/release-versioning-meta.md @@ -4,7 +4,7 @@ _This Specification describes how versions are numbered in Joomla projects._ -This specification aims to help developers, code reviewers and release leads to determine, whether a new piece or a +This specification aims to help developers, code reviewers and release managers to determine, whether a new piece or a change of code belongs to the next patch, minor or major release. It will also cover documentation requirements that are related to versioning. @@ -60,7 +60,7 @@ deprecation is not phasing out a feature. This requirement serves several purpos - It proves that the new solution actually works where it will end up being used. - It shows developers, who consider the code as authoritative documentation, how the new solution works and should be used. -- It prevents the release lead of the following major version from being surprised by the "leftovers" and maybe +- It prevents the release managers of the following major version from being surprised by the "leftovers" and maybe therefore not being able to meet the schedule. The documentation requirements make sure that legacy code can easily be adopted to the new implementation. diff --git a/proposed/release-versioning.md b/proposed/release-versioning.md index 05ff70d..fd8d314 100644 --- a/proposed/release-versioning.md +++ b/proposed/release-versioning.md @@ -44,8 +44,8 @@ Because Semantic Versioning applies to public API only, a distinction between pu 1. Changes to the public API are subject to the rules of semantic versioning. 2. In order to distinguish these elements from the internal elements, the following applies: 1. Existing public structural elements SHOULD be annotated with `@api` in the corresponding DocBlock. - 1. Newly introduced public structural elements MUST be annotated with `@api` in the corresponding DocBlock. - 2. Public structural elements MUST be included in the official documentation. + 2. Newly introduced public structural elements MUST be annotated with `@api` in the corresponding DocBlock. + 3. Public structural elements MUST be included in the official documentation. #### 2.2 Internal API @@ -55,10 +55,10 @@ extension developers. 1. Changes to the internal API are not subject to the rules of semantic versioning. 2. In order to distinguish these elements from the public elements, the following applies: 1. Existing internal structural elements SHOULD be annotated with `@internal` in the corresponding DocBlock. - 1. Newly introduced structural elements MUST be annotated with `@internal` in the corresponding DocBlock. - 2. Structural elements with the access modifier `private` and structural elements in final classes with the access + 2. Newly introduced structural elements MUST be annotated with `@internal` in the corresponding DocBlock. + 3. Structural elements with the access modifier `private` and structural elements in final classes with the access modifier `protected` MUST be treated as internal. - 3. Internal structural elements MUST NOT be included in the official documentation. All documentation needed by core + 4. Internal structural elements MUST NOT be included in the official documentation. All documentation needed by core developers SHOULD be available in the corresponding DocBlock. ### 3 Deprecations @@ -75,7 +75,7 @@ applies to public API only. ```php /** * ... - * @deprecated X.Y Will be removed in X+1.0. Use {@see } instead. + * @deprecated X.Y Will be removed in X+2.0. Use {@see } instead. * Before (version < X.Y): * * After (version >= X.Y): @@ -95,7 +95,7 @@ applies to public API only. ```php /** * ... - * @deprecated X.Y Will be removed in X+1.0 without replacement. + * @deprecated X.Y Will be removed in X+2.0 without replacement. * Please consider using or instead. */ ``` From 7a13f05cf31ed4a26e74ead752d69b760c2f2811 Mon Sep 17 00:00:00 2001 From: Niels Braczek Date: Wed, 24 Aug 2022 12:22:57 +0200 Subject: [PATCH 11/12] Fix bug in example code --- proposed/release-versioning-examples.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposed/release-versioning-examples.md b/proposed/release-versioning-examples.md index b68f64d..2ef9c2a 100644 --- a/proposed/release-versioning-examples.md +++ b/proposed/release-versioning-examples.md @@ -314,7 +314,7 @@ class CupOfHotDrink */ public function getHotDrink($type, $addSugar = false, $addMilk = false) { - if (version_compare(PHP_VERSION, '8.1', '<')) { + if (version_compare(PHP_VERSION, '8.1', '>=')) { $tea = php_make_caffeine_drink(type: $type, amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 50 : 0); $cup = new Cup(); $cup->addIngredient($tea); From a03d12d74ab9a7b610d021a19c54867a263d648a Mon Sep 17 00:00:00 2001 From: Niels Braczek Date: Fri, 2 Sep 2022 23:22:34 +0200 Subject: [PATCH 12/12] Editorial changes --- proposed/release-versioning-meta.md | 3 +++ proposed/release-versioning.md | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/proposed/release-versioning-meta.md b/proposed/release-versioning-meta.md index e6dc8c4..4bd105d 100644 --- a/proposed/release-versioning-meta.md +++ b/proposed/release-versioning-meta.md @@ -64,6 +64,9 @@ deprecation is not phasing out a feature. This requirement serves several purpos therefore not being able to meet the schedule. The documentation requirements make sure that legacy code can easily be adopted to the new implementation. +A comparison of old and new code shall make it easier for developers to adapt their code to the new conditions and +provide sample code for documentation. It is important that the representation of the old and new code is both human- +and machine-readable. ### 4.4 User Interface diff --git a/proposed/release-versioning.md b/proposed/release-versioning.md index fd8d314..80a5f88 100644 --- a/proposed/release-versioning.md +++ b/proposed/release-versioning.md @@ -71,7 +71,7 @@ applies to public API only. 1. The official documentation MUST be updated to let users know about the change. 2. The DocBlock MUST be annotated to document the replacement for the deprecated element. The deprecation annotation - SHOULD be supplemented by a usage example for the replacement. Example: + SHALL be supplemented by a usage example for the replacement. Example: ```php /** * ... @@ -91,7 +91,7 @@ applies to public API only. 1. The official documentation MUST be updated to let users know about the change. 2. The DocBlock MUST be annotated to document the future removal of the deprecated element. The deprecation annotation - SHOULD be supplemented by a recommendation for an alternative. Example: + SHALL be supplemented by a recommendation for an alternative. Example: ```php /** * ...