rfc:traits-with-interfaces
Differences
This shows you the differences between two versions of the page.
Next revision | Previous revision | ||
rfc:traits-with-interfaces [2016/02/17 18:58] – created kevingessner | rfc:traits-with-interfaces [2017/09/22 13:28] (current) – external edit 127.0.0.1 | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== PHP RFC: Your Title Here ====== | + | ====== PHP RFC: Traits with interfaces |
- | * Version: 0.1 | + | * Version: 0.2 |
- | * Date: 2016-02-12 | + | * Date: 2016-02-12 |
* Author: Kevin Gessner, kgessner@etsy.com | * Author: Kevin Gessner, kgessner@etsy.com | ||
- | * Status: | + | * Status: |
* First Published at: http:// | * First Published at: http:// | ||
Line 10: | Line 10: | ||
Allow traits to implement interfaces. | Allow traits to implement interfaces. | ||
- | ===== Proposal | + | ===== Proposals |
+ | |||
+ | This RFC proposes two language changes to PHP’s traits. | ||
+ | |||
+ | ==== Proposal 1: Traits implement interfaces ==== | ||
Traits provide horizontal reuse of methods: a class that uses a trait mixes in the implementation of a set of methods. | Traits provide horizontal reuse of methods: a class that uses a trait mixes in the implementation of a set of methods. | ||
- | These concepts fit together well. The set of methods provided by a trait may match the set of methods guaranteed by an interface. | + | These concepts fit together well. The set of methods provided by a trait may match the set of methods guaranteed by an interface. |
- | Having the trait declare that it implements an interface makes the relationship between the interface (specification) and trait (implementation) explicit. | + | This first proposal is that a trait be permitted to declare that it implements an interface. |
The trait must implement each of the methods from all the interfaces it implements. | The trait must implement each of the methods from all the interfaces it implements. | ||
- | Concretely, | + | Concretely, |
- | '' | + | < |
+ | |||
+ | interface I { | ||
+ | function foo(); | ||
+ | } | ||
+ | |||
+ | trait T implements I { | ||
+ | function foo() { | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | See “Examples” below for additional sample code. | ||
+ | |||
+ | If a class inserts a trait that implements an interface, the class may or may not declare that interface. | ||
+ | |||
+ | This pattern -- where a trait provides a standard implementation of an interface -- exists in the wild. See “References” for links. | ||
+ | |||
+ | This change does not introduce any new keywords. | ||
+ | |||
+ | ==== Proposal 2: Propagating interfaces from traits to classes ==== | ||
+ | |||
+ | //This proposal depends on Proposal 1, above.// | ||
+ | |||
+ | A trait that implements an interface provides methods that fulfill the interface’s contract. | ||
+ | |||
+ | Concretely, Proposal 2 makes this code be valid and functional: | ||
+ | |||
+ | <PHP><?php | ||
interface I { | interface I { | ||
Line 40: | Line 73: | ||
// ( | // ( | ||
// [I => I] | // [I => I] | ||
- | // )'' | + | // )</ |
- | Compared to an abstract class, using a trait plus interface provides looser coupling. | + | Classes that insert the trait may override any members (as with existing traits) and continue to implement the interface. |
- | This pattern -- where a trait provides a standard implementation of an interface -- exists in the wild. See “References” for links. | + | This change reduces the overhead of implementing |
- | Other languages that implement languages features like PHP’s traits allow for interface specifications like this RFC. | + | Other languages that implement languages features like PHP’s traits allow for interface specifications like this RFC. |
- | + | ||
- | This change does not introduce any new keywords. | + | |
===== Examples ===== | ===== Examples ===== | ||
- | ==== Example #1: Trait implementing interface and additional methods ==== | + | ==== Example #1: Trait implementing interface, and providing |
- | <?php | + | //Relies on Proposal 1// |
- | + | ||
- | interface Logger { | + | |
- | function error($message); | + | |
- | function info($message); | + | |
- | } | + | |
- | + | ||
- | trait FileLogger implements Logger { | + | |
- | abstract function logToFile($message); | + | |
- | + | ||
- | function error($message) { | + | |
- | $this-> | + | |
- | } | + | |
- | + | ||
- | function info($message) { | + | |
- | $this-> | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | class Widget { | + | |
- | use FileLogger; | + | |
- | + | ||
- | function logToFile($message) { | + | |
- | | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | | + | |
- | print_r(class_implements(Widget:: | + | |
+ | <PHP> | ||
+ | <?php | ||
+ | |||
+ | interface Logger { | ||
+ | function error($message); | ||
+ | function info($message); | ||
+ | } | ||
+ | |||
+ | trait FileLogger implements Logger { | ||
+ | abstract function logToFile($message); | ||
+ | |||
+ | function error($message) { | ||
+ | $this-> | ||
+ | } | ||
+ | |||
+ | function info($message) { | ||
+ | $this-> | ||
+ | } | ||
+ | } | ||
+ | </ | ||
==== Example #2: Trait that does not implement all required methods ==== | ==== Example #2: Trait that does not implement all required methods ==== | ||
- | | + | //Relies on Proposal 1// |
- | + | ||
- | interface Logger { | + | < |
- | function error($message); | + | <?php |
- | function info($message); | + | |
- | } | + | interface Logger { |
- | + | function error($message); | |
- | // Fatal error: Trait ErrorLogger contains 1 abstract method and must implement the remaining methods (Logger:: | + | function info($message); |
- | trait ErrorLogger implements Logger { | + | } |
- | function error($message) { | + | |
- | print $message; | + | // Fatal error: Trait ErrorLogger contains 1 abstract method and must implement the remaining methods (Logger:: |
- | } | + | trait ErrorLogger implements Logger { |
- | } | + | function error($message) { |
+ | print $message; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
==== Example #3: Trait that implements part of the interface via an abstract method ==== | ==== Example #3: Trait that implements part of the interface via an abstract method ==== | ||
- | <?php | + | //Relies on Proposal 1// |
- | + | ||
- | interface Logger { | + | |
- | function error($message); | + | |
- | function info($message); | + | |
- | } | + | |
- | + | ||
- | trait ErrorLogger implements Logger { | + | |
- | function error($message) { | + | |
- | print $message; | + | |
- | } | + | |
- | + | ||
- | abstract function info($message); | + | |
- | } | + | |
- | + | ||
- | class Widget { | + | |
- | use ErrorLogger; | + | |
- | + | ||
- | function info($message) { | + | |
- | | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | | + | |
- | print_r(class_implements(Widget:: | + | |
+ | <PHP> | ||
+ | <?php | ||
- | ==== Example #4: Method from trait is renamed, so interface | + | interface |
+ | function error($message); | ||
+ | function info($message); | ||
+ | } | ||
- | <?php | + | trait ErrorLogger |
- | + | function | |
- | | + | |
- | | + | } |
- | | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | // Fatal error: Access level to VarsToJson:: | + | |
- | class Widget { | + | |
- | use VarsToJson { | + | |
- | jsonSerialize as private; | + | |
- | } | + | |
- | | + | |
+ | abstract function info($message); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== Example #4: Class implementing interface via trait ==== | ||
+ | |||
+ | //Relies on Proposals 1 and 2// | ||
+ | |||
+ | <PHP> | ||
+ | <?php | ||
+ | |||
+ | interface Logger { | ||
+ | function error($message); | ||
+ | function info($message); | ||
+ | } | ||
+ | |||
+ | trait FileLogger implements Logger { | ||
+ | abstract function logToFile($message); | ||
+ | |||
+ | function error($message) { | ||
+ | $this-> | ||
+ | } | ||
+ | |||
+ | function info($message) { | ||
+ | $this-> | ||
+ | } | ||
+ | } | ||
+ | |||
+ | class Widget { | ||
+ | use FileLogger; | ||
+ | |||
+ | function logToFile($message) { | ||
+ | // ... | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Prints Array( [Logger] => Logger ) | ||
+ | print_r(class_implements(Widget:: | ||
+ | </ | ||
+ | |||
+ | ==== Example #5: Method from trait is renamed, so interface is no longer satisfied ==== | ||
+ | |||
+ | //Relies on Proposals 1 and 2// | ||
+ | |||
+ | See “Open Issues” below for a discussion of this example. | ||
+ | |||
+ | <PHP> | ||
+ | <?php | ||
+ | |||
+ | trait VarsToJson implements JsonSerializable { | ||
+ | public function jsonSerialize() { | ||
+ | return get_object_vars($this); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Fatal error: Access level to VarsToJson:: | ||
+ | class Widget { | ||
+ | use VarsToJson { | ||
+ | jsonSerialize as private; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
===== Backward Incompatible Changes ===== | ===== Backward Incompatible Changes ===== | ||
Line 179: | Line 239: | ||
===== Open Issues ===== | ===== Open Issues ===== | ||
- | None yet! | + | ==== Proposal 2 ==== |
+ | |||
+ | Given a trait that implements an interface, what happens when a class that uses that trait renames one of the methods required by the interface? | ||
+ | |||
+ | This is not the only valid behavior in this case. Alternatively, | ||
+ | |||
+ | This issue does not apply to Proposal 1, as it only affects the case where interface declarations propagate from trait to class. | ||
===== Unaffected PHP Functionality ===== | ===== Unaffected PHP Functionality ===== | ||
Line 195: | Line 261: | ||
===== Proposed Voting Choices ===== | ===== Proposed Voting Choices ===== | ||
- | Vote date TBD. This change | + | The two proposals will be voted separately and concurrently. |
+ | |||
+ | If Proposal 1 fails to pass, Proposal 2 is moot and also fails. | ||
===== Patches and Tests ===== | ===== Patches and Tests ===== | ||
- | Language spec patch: https:// | + | Language spec patch, covering Proposal 1: https:// |
- | Other patches TBD. | + | php-src patch implementing Proposal 1: https:// |
===== Implementation ===== | ===== Implementation ===== |
rfc/traits-with-interfaces.txt · Last modified: 2017/09/22 13:28 by 127.0.0.1