rfc:traits-with-interfaces

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
rfc:traits-with-interfaces [2016/02/17 19:15] – Fix PHP block formatting levimrfc:traits-with-interfaces [2017/09/22 13:28] (current) – external edit 127.0.0.1
Line 1: Line 1:
 ====== PHP RFC: Traits with interfaces ====== ====== PHP RFC: Traits with interfaces ======
-  * Version: 0.1 +  * Version: 0.2 
-  * Date: 2016-02-12+  * Date: 2016-02-12 (last update 2016-02-18)
   * Author: Kevin Gessner, kgessner@etsy.com   * Author: Kevin Gessner, kgessner@etsy.com
-  * Status: Draft+  * Status: Under Discussion
   * First Published at: http://wiki.php.net/rfc/traits-with-interfaces   * First Published at: http://wiki.php.net/rfc/traits-with-interfaces
  
Line 10: Line 10:
 Allow traits to implement interfaces.  Classes that insert the trait would then implement the interface, as though it was declared on the class. Allow traits to implement interfaces.  Classes that insert the trait would then implement the interface, as though it was declared on the class.
  
-===== Proposal =====+===== Proposals ===== 
 + 
 +This RFC proposes two language changes to PHP’s traits.  The second proposal requires the first. 
 + 
 +==== 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.  Interfaces provide a promise of a class's implementation: a class that implements an interface is guaranteed to provide 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.  Interfaces provide a promise of a class's implementation: a class that implements an interface is guaranteed to provide 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.  When a class inserts that trait, the class now fulfills the interface.  PHP currently requires that the class explicitly specify that it implements the interface.  This RFC proposes that the trait be permitted to implement the interface.  Any class that inserts that trait would then implement the interface.+These concepts fit together well.  The set of methods provided by a trait may match the set of methods guaranteed by an interface.  While the programmer’s intends that the trait provide an implementation of the complete interface, PHP cannot enforce this intention in code.
  
-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.  Having the trait declare that it implements an interface makes the relationship between the interface (specification) and trait (implementation) explicit.
  
 The trait must implement each of the methods from all the interfaces it implements.  Failure to do so will be a fatal error.  The method declarations must be compatible with the interface.  Some or all of the trait’s implementing methods may be abstract, with the class including the trait providing the method implementation (similar to an abstract class that implements an interface). The trait must implement each of the methods from all the interfaces it implements.  Failure to do so will be a fatal error.  The method declarations must be compatible with the interface.  Some or all of the trait’s implementing methods may be abstract, with the class including the trait providing the method implementation (similar to an abstract class that implements an interface).
  
-Concretely, this RFC proposes that this code be valid and functional:+Concretely, Proposal 1 makes this code be valid and functional: 
 + 
 +<PHP><?php 
 + 
 +interface I { 
 +    function foo(); 
 +
 + 
 +trait T implements I { 
 +    function foo() { 
 +    } 
 +
 +</PHP> 
 + 
 +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.  Semantically, this proposal makes the relationship between the trait and interface more explicit, but does not change how classes behave that use such a trait. 
 + 
 +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.  The syntax change is limited to the trait declaration. See “Patches and Tests” for a proposed patch for the language specification. 
 + 
 +==== 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.  When a class inserts that trait, the class now fulfills the interface, but the class must explicitly specify that it implements the interface.  This second proposal is that any class, by inserting a trait that implements an interface, would implicitly be declared to implement that interface.  The class need not repeat the interface declaration. 
 + 
 +Concretely, Proposal 2 makes this code be valid and functional:
  
 <PHP><?php <PHP><?php
Line 42: Line 75:
 // )</PHP> // )</PHP>
  
-Compared to an abstract class, using a trait plus interface provides looser coupling.  A trait implementing an interface provides an implementation of the interface, but it need not be the only implementation.  Classes are still free to implement the interface directly.  Classes that insert the trait may override any members (as with existing traits) and continue to implement the interface.  Other classes need not be aware that a class implements an interface via a trait or directly, in the same way they need not be aware if an interface is inherited by a class.+Classes that insert the trait may override any members (as with existing traits) and continue to implement the interface.  Other classes need not be aware that a class implements an interface via a trait or directly, in the same way they need not be aware if an interface is inherited by a class.
  
-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 that pattern, because a class will only need to implement the trait, not also explicitly specify the interface.  Semantically, it makes the relationship between the trait and interface more explicit.+This change reduces the overhead of implementing an interface via a trait, because a class will only need to insert the trait, not also explicitly declare the interface.
  
-Other languages that implement languages features like PHP’s traits allow for interface specifications like this RFC.  Again, see “References” for links+Other languages that implement languages features like PHP’s traits allow for interface specifications like this RFC.  See “References” for links.
- +
-This change does not introduce any new keywords.  The syntax change is limited to the trait declaration. See “Patches and Tests” for a proposed patch for the language specification.+
  
 ===== Examples ===== ===== Examples =====
  
-==== Example #1: Trait implementing interface and additional methods ====+==== Example #1: Trait implementing interfaceand providing additional methods ====
  
-  <?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->logToFile("ERROR: $message"); +
-      } +
-   +
-      function info($message) { +
-          $this->logToFile("INFO: $message"); +
-      } +
-  } +
-   +
-  class Widget { +
-      use FileLogger; +
-   +
-      function logToFile($message) { +
-          // ... +
-      } +
-  } +
-   +
-  // Prints Array( [Logger] => Logger ) +
-  print_r(class_implements(Widget::class))+
  
 +<PHP>
 +<?php
 +
 +interface Logger {
 +    function error($message);
 +    function info($message);
 +}
 +
 +trait FileLogger implements Logger {
 +    abstract function logToFile($message);
 +
 +    function error($message) {
 +        $this->logToFile("ERROR: $message");
 +    }
 +
 +    function info($message) {
 +        $this->logToFile("INFO: $message");
 +    }
 +}
 +</PHP>
  
 ==== Example #2: Trait that does not implement all required methods ==== ==== Example #2: Trait that does not implement all required methods ====
  
-  <?php +//Relies on Proposal 1// 
-   + 
-  interface Logger { +<PHP> 
-      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::info) +    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::info) 
-      +trait ErrorLogger implements Logger { 
-  }+    function error($message) { 
 +        print $message; 
 +    
 +} 
 +</PHP>
  
 ==== 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) { +
-          // ... +
-      } +
-  } +
-   +
-  // Prints Array( [Logger] => Logger ) +
-  print_r(class_implements(Widget::class))+
  
 +<PHP>
 +<?php
  
-==== Example #4: Method from trait is renamed, so interface is no longer satisfied ====+interface Logger { 
 +    function error($message); 
 +    function info($message); 
 +}
  
-  <?php +trait ErrorLogger implements Logger 
-   +    function error($message) { 
-  trait VarsToJson implements JsonSerializable +        print $message
-      public function jsonSerialize() { +    }
-          return get_object_vars($this)+
-      } +
-  } +
-   +
-  // Fatal error: Access level to VarsToJson::jsonSerialize() must be public (as in class JsonSerializable) +
-  class Widget { +
-      use VarsToJson { +
-          jsonSerialize as private; +
-      } +
-  }+
  
 +    abstract function info($message);
 +}
 +</PHP>
 +
 +==== 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->logToFile("ERROR: $message");
 +    }
 +
 +    function info($message) {
 +        $this->logToFile("INFO: $message");
 +    }
 +}
 +
 +class Widget {
 +    use FileLogger;
 +
 +    function logToFile($message) {
 +        // ...
 +    }
 +}
 +
 +// Prints Array( [Logger] => Logger )
 +print_r(class_implements(Widget::class));
 +</PHP>
 +
 +==== 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::jsonSerialize() must be public (as in class JsonSerializable)
 +class Widget {
 +    use VarsToJson {
 +        jsonSerialize as private;
 +    }
 +}
 +</PHP>
  
 ===== 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?  Example 5 shows this as a fatal error: the class no longer fulfills the interface declared by the trait, which is invalid. 
 + 
 +This is not the only valid behavior in this case.  Alternatively, the interface declaration could be dropped from the class, leaving the class with the trait’s methods but not the interface. 
 + 
 +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 TBDThis change will require a 2/3 majority.+The two proposals will be voted separately and concurrently Both will require a 2/3 majority.  Vote date TBD. 
 + 
 +If Proposal 1 fails to pass, Proposal 2 is moot and also fails.
  
 ===== Patches and Tests ===== ===== Patches and Tests =====
  
-Language spec patch: https://github.com/php/php-langspec/compare/master...kevingessner:traits-with-interfaces+Language spec patch, covering Proposal 1: https://github.com/php/php-langspec/compare/master...kevingessner:traits-with-interfaces
  
-Other patches TBD.+php-src patch implementing Proposal 1: https://github.com/php/php-src/pull/1773
  
 ===== Implementation ===== ===== Implementation =====
rfc/traits-with-interfaces.1455736518.txt.gz · Last modified: 2017/09/22 13:28 (external edit)