rfc:friend-classes

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:friend-classes [2016/01/07 19:57] mdwheelerfc:friend-classes [2018/07/14 12:52] (current) – vote end mdwheele
Line 1: Line 1:
 ====== PHP RFC: Class Friendship ====== ====== PHP RFC: Class Friendship ======
-  * Version: 0.2 +  * Version: 1.0.0 
-  * Date: 2015-12-10+  * Date: 2017-09-21
   * Author: Dustin Wheeler, mdwheele@ncsu.edu   * Author: Dustin Wheeler, mdwheele@ncsu.edu
-  * Status: Under Discussion+  * Status: Declined
   * First Published at: http://wiki.php.net/rfc/friend-classes   * First Published at: http://wiki.php.net/rfc/friend-classes
  
 ===== Introduction ===== ===== Introduction =====
-Class Friendship allows a class to be better encapsulated by granting per-class access to protected properties. Class Friendship is an explicit and concise expression of tight-coupling between collaborators that separates concerns delegated to a friended class for purposes of better encapsulation of behaviour. This affords developers an opportunity to better-model objects as behavioural "tell don't ask" units while explicating concerns like presentation through friendship. Class Friendship is a valuable expression for object modeling when used properly. It also has value in white-box testing as a tactical refactoring tool when approaching legacy applications. +Class Friendship allows a class to be better encapsulated by granting per-class access to protected members. Class Friendship is an explicit and concise expression of tight-coupling between collaborators that separates concerns delegated to a friended class for purposes of better encapsulation of behaviour. This affords developers an opportunity to better-model objects as behavioural "tell don't ask" units while explicating concerns like presentation through friendship. Class Friendship is a valuable expression for object modeling when used properly. It also has value in white-box testing as a tactical refactoring tool when approaching legacy applications. 
  
-A common use-case for friendship here is separating presentation concerns from what would otherwise be a "tell don't ask" behavioral unit or write model. This can be applied in a variety of scenarios. Without this feature, developers are left with a trade-off of marking internal properties public to make them available and sacrificing design opportunities torwards the future. Another common occurrence is the addition of getters that simply proxy internal state and grow the public API of a unit by necessity. To be sure, this feature is not merely about pulling off "access to protected class properties", but about modeling a semantic relationship between two collaborators. In fact, access to private and protected class properties is already possible through closure scope "juggling" or use of ''[[http://php.net/manual/en/function.debug-backtrace.php|debug_backtrace]]'', which can be difficult to reason-about. Both of these are included in references below. While it is technically possible to execute friend-like features in user-land, it would be preferable to concisely represent the relationship with a known and well-documented concept supported by the language.+A common use-case for friendship here is separating presentation concerns from what would otherwise be a "tell don't ask" behavioral unit or write model. This can be applied in a variety of scenarios. Without this feature, developers are left with a trade-off of marking internal members public to make them available and sacrificing design opportunities towards the future. Another common occurrence is the addition of getters that simply proxy internal state and grow the public API of a unit by necessity. To be sure, this feature is not merely about pulling off "access to protected class members", but about modeling a semantic relationship between two collaborators. In fact, access to private and protected class members is already possible through closure scope "juggling" or use of ''[[http://php.net/manual/en/function.debug-backtrace.php|debug_backtrace]]'', which can be difficult to reason-about. Both of these are included in references below. While it is technically possible to execute friend-like features in user-land, it would be preferable to concisely represent the relationship with a known and well-documented concept supported by the language.
  
-The purpose of the feature should not be conflated or confused with the goals of something like package-private classes or namespace visibility, in general. I feel those features apply more closely to the types of behaviors user-land sees in Symfony (and other framework) packages that mark properties as ''@internal'' but are forced to make them public to share access between internal data structures. I don't necessarily feel that class friendship is the "Right Answer TM" in this case, but I think that the dance package developers currently have to do to express "don't use this property, we use this internally to help you" is worth improving.+The purpose of the feature should not be conflated or confused with the goals of something like package-private classes or namespace visibility, in general. I feel those features apply more closely to the types of behaviors user-land sees in Symfony (and other framework) packages that mark members as ''@internal'' but are forced to make them public to share access between internal data structures. I don't necessarily feel that class friendship is the "Right Answer TM" in this case, but I think that the dance package developers currently have to do to express "don't use this property, we use this internally to help you" is worth improving.
  
 ===== Proposal ===== ===== Proposal =====
-Support for class friendship is added through a new keyword, ''friend''. It allows per-class access to protected properties as follows:+Support for class friendship is added through a new keyword, ''friend''. It allows per-class access to protected members as follows:
  
 ==== Basic Usage ==== ==== Basic Usage ====
-A subject class may declare another class a friend through the use of a new ''friend'' keyword similar to how Trait syntax works. This enables the named friend class access to protected properties of the subject. There are other properties of Class Friendship, as implemented in C++. These properties are described below. +A subject class may declare another class a friend through the use of a new ''friend'' keyword similar to how Trait syntax works. This enables the named friend class access to protected members of the subject. There are other properties of Class Friendship, as implemented in C++. These properties are described below. 
  
-C++ implements Class Friendship such that friends have access to both private and protected properties. [[https://marc.info/?l=php-internals&m=144972388219786&w=2|In discussing this implementation detail]], there was concern that allowing unfettered access to all properties risks exposing intentionally hidden implementation details local to a given unit whereby a developer absolutely did not want the given property accessible by any means other than the subject class. This RFC suggests that friend classes in PHP only have access to protected and higher properties+C++ implements Class Friendship such that friends have access to both private and protected members. [[https://marc.info/?l=php-internals&m=144972388219786&w=2|In discussing this implementation detail]], there was concern that allowing unfettered access to all members risks exposing intentionally hidden implementation details local to a given unit whereby a developer absolutely did not want the given property accessible by any means other than the subject class. This RFC suggests that friend classes in PHP only have access to protected and higher members
  
 Below, a class ''Person'' declares ''HumanResourceReport'' as a friend for the purposes of separating presentation concerns: Below, a class ''Person'' declares ''HumanResourceReport'' as a friend for the purposes of separating presentation concerns:
Line 59: Line 59:
     {     {
         // HumanResourceReport would not have access to protected          // HumanResourceReport would not have access to protected 
-        // properties of Person if not explicitly listed as a friend.+        // members of Person if not explicitly listed as a friend.
         return $this->person->firstName . ' ' . $this->person->lastName;         return $this->person->firstName . ' ' . $this->person->lastName;
     }     }
Line 237: Line 237:
  
 === Access due to friendship is inherited === === Access due to friendship is inherited ===
-A friend of ''Derived'' can access the protected properties of ''Derived'' that were inherited from ''Base''. Note, however, that a friend of ''Derived'' only has access to properties inherited from ''Base'' to which ''Derived'' has access, itself, (e.g. if ''Derived'' inherits from ''Base'', ''Derived'' only has access to the protected properties inherited from ''Base'', not private properties, so neither does a friend.)+A friend of ''Derived'' can access the protected members of ''Derived'' that were inherited from ''Base''. Note, however, that a friend of ''Derived'' only has access to members inherited from ''Base'' to which ''Derived'' has access, itself, (e.g. if ''Derived'' inherits from ''Base'', ''Derived'' only has access to the protected members inherited from ''Base'', not private members, so neither does a friend.)
  
 <code php> <code php>
Line 286: Line 286:
  
 ==== Errors ==== ==== Errors ====
-In all cases above, error messages received are no different than if an object attempted to read or write private or protected properties of a class it did not have access to. That is to say, the error message will not hint properties / rules of class friendship (e.g. "You don't have access to this property because friendship isn't symmetric.")+In all cases above, error messages received are no different than if an object attempted to read or write private or protected members of a class it did not have access to. That is to say, the error message will not hint members / rules of class friendship (e.g. "You don't have access to this property because friendship isn't symmetric.")
  
 ==== Additional Thoughts ==== ==== Additional Thoughts ====
Line 300: Line 300:
  
 ===== Proposed PHP Version(s) ===== ===== Proposed PHP Version(s) =====
-This proposal targets the next minor version of PHP 7, which at the time of this writing is PHP 7.1.+I had intended to target the next minor version of PHP with this change. Since 7.3 is closing in on feature freeze and whether or not 7.4 will happen before 8.0 is currently under discussion, this RFC's target depends on outcome of discussion. If there is to be a PHP 7.4, it will go there. Otherwise, it will wait until PHP 8.
  
 ===== RFC Impact ===== ===== RFC Impact =====
 ==== To Opcache ==== ==== To Opcache ====
 This is an open issue pending code review. I am unfamiliar-enough with Opcache implementation to be able to appropriately assess impact. This is an open issue pending code review. I am unfamiliar-enough with Opcache implementation to be able to appropriately assess impact.
- 
-==== To Reflection API ==== 
-New methods are added to ''ReflectionClass'': 
- 
-  - ''public array ReflectionClass::getTraitNames(void)'' - Returns an array of friend names on current class. Returns ''NULL'' in case of error. 
-  - ''public array ReflectionClass::getTraits(void)'' - Returns associative array of friend names in keys and instances of friend's ''ReflectionClass'' in values. Returns ''NULL'' in case of error. 
- 
-===== Open Issues ===== 
-==== Policy ==== 
-  * Implementation requires code review to advise on improvements as well as inform that Opcache was appropriately considered 
-  * Verify current PHP functionality around class property visibility is undisturbed 
- 
-==== Reflection API ==== 
-  * Implement new methods on ''ReflectionClass'' 
  
 ===== Future Scope ===== ===== Future Scope =====
 While this RFC specifies friendship between classes, there is opportunity to extend this implementation and syntax to include progressive enhancements. Snippets below are hypothetical implementations, but have not been discussed and are not tied to this RFC. I on include them as examples of further possible work. While this RFC specifies friendship between classes, there is opportunity to extend this implementation and syntax to include progressive enhancements. Snippets below are hypothetical implementations, but have not been discussed and are not tied to this RFC. I on include them as examples of further possible work.
  
-==== Friendship to global functions ==== +  * **Friendship to global functions**\\ A class may declare a global function as friend. This might be used if someone wanted to expose a procedural interface to an existing object model or begin to refactor a procedural model to become a façade over a new object model. Modeling best-practice aside, it functions much like standard class friendship
-A class may declare a global function as friend. This might be used if someone wanted to expose a procedural interface to an existing object model or begin to refactor a procedural model to become a façade over a new object model. Modeling best-practice aside, it functions much like standard class friendship:+  * **Friendship to class methods**\\ Friendship to class methods is a narrower expression of standard class friendship. Instead of declaring the entire class a friend, we declare that a method from a friended class can access the subject's protected properties. 
 +  * **Friendship to namespace(s)**\\ A class might declare an entire namespace as friend. In this way, any class that is part of that namespace would be friended.
  
-<code php> 
-class Person 
-{ 
-    friend fullname; // Syntax is debatable as part of separate RFC. Idea is same. 
- 
-    protected $first_name = 'Alice'; 
-    protected $last_name = 'Wonderland'; 
-} 
- 
-function fullname(Person $instance) 
-{ 
-    return $instance->first_name . ' ' . $instance->last_name; 
-} 
- 
-$alice = new Person(); 
-var_dump(fullname($alice)); // string(16) "Alice Wonderland" 
-</code> 
- 
-==== Friendship to class methods ==== 
-Friendship to class methods is a narrower expression of standard class friendship. Instead of declaring the entire class a friend, we declare that a method from a friend class can access the subject's protected properties. 
- 
-<code php> 
-class Person 
-{ 
-    friend Report::getFullName;  // Syntax is debatable as part of separate RFC. Idea is same. 
- 
-    protected $first_name = 'Alice'; 
-    protected $last_name = 'Wonderland'; 
-} 
- 
-class Report 
-{ 
-    private $person; 
- 
-    public function __construct(Person $instance) 
-    { 
-        $this->person = $instance; 
-    } 
-     
-    public function getFullName() 
-    { 
-        return $this->person->first_name . ' ' . $this->person->last_name; 
-    } 
-     
-    public function getIdentifier() 
-    { 
-        return "REPORT_ID_{$this->person->first_name}_{$this->person->last_name}"; 
-    } 
-} 
- 
-$report = new Report(new Person()); 
- 
-var_dump($report->getFullName()); // string(16) "Alice Wonderland" 
-var_dump($report->getIdentifier()); // Fatal error: Uncaught Error: Cannot access protected property Person::$first_name 
-</code> 
- 
-==== Friendship to namespace(s), possibly ==== 
-A class might declare an entire namespace as friends. In this way, any class that is part of that namespace would be friended. 
- 
-==== Combinations of Class, Class Method and Namespace Friendship ==== 
-Theoretically, a class could declare any number of collaborators as friend through a combination of the above strategies. You might declare the class' namespace friend as well as a single class outside that namespace: 
- 
-<code php> 
-namespace Vendor\Internals { 
-    class Person 
-    { 
-        friend __NAMESPACE__, SomeOtherClass, some_function; // Syntax debatable as part of separate RFC. Idea is same. 
-         
-        protected $property = 'foo'; 
-    } 
-     
-    class ImplementationDetail 
-    { 
-        public function touch(Person $instance) 
-        { 
-            return $instance->property; 
-        } 
-    } 
-} 
-</code> 
- 
-<code php> 
-class SomeOtherClass 
-{ 
-    public function touch(Person $instance) 
-    { 
-        return $instance->property; 
-    } 
-} 
-</code> 
- 
-<code php> 
-function some_function(Person $instance) 
-{ 
-    return $instance->property 
-} 
-</code> 
- 
-<code php> 
-$person = new \Vendor\Internals\Person(); 
-$detail = new \Vendor\Internals\ImplementationDetail(); 
-$other = new \SomeOtherClass(); 
- 
-var_dump($detail->touch($person)); // string(3) "foo" 
-var_dump($other->touch($person)); // string(3) "foo" 
-var_dump(some_function($person)); // string(3) "foo" 
-</code> 
 ===== Proposed Voting Choices ===== ===== Proposed Voting Choices =====
  
-This RFC proposes two voting choices:+As this RFC adds syntax to the language, a 2/3 majority is required. (see [[voting]])
  
-  Should friend classes be added to PHP (yes / no)? +Voting starts on 2018-07-06 21:00 UTC and closes on 2018-07-13 21:00 UTC. 
-  Should friends have access to private properties (yes / no)?+
  
-As this is a language change, a 2/3 majority is required for whether to add Friend Classes to PHP. (see [[voting]])+<doodle title="Support Class Friendship?" auth="mdwheele" voteType="single" closed="true"> 
 +   * Yes 
 +   * No 
 +</doodle>
  
 ===== Patches and Tests ===== ===== Patches and Tests =====
-I have implemented the RFC as described with tests to verify all usage examples above. I will link this as soon as I am able and will open a pull request against ''master'' to monitor TravisCI build status as I make changes.+I have implemented the RFC as described with tests to verify all usage examples above.  
 + 
 +https://github.com/php/php-src/pull/3347
  
 As this is my first contribution to PHP, it is my opinion that my request should be placed under higher scrutiny and I am completely ready and willing to accept all feedback to improve implementation. As this is my first contribution to PHP, it is my opinion that my request should be placed under higher scrutiny and I am completely ready and willing to accept all feedback to improve implementation.
Line 459: Line 343:
   * v0.1 - Created   * v0.1 - Created
   * v0.2 - Copy-editing. Clarifications. Add more code examples.   * v0.2 - Copy-editing. Clarifications. Add more code examples.
 +  * v0.2.1 - Fix copy+paste error from ReflectionClass documentation regarding trait methods.
 +  * v0.2.2 - Remove voting choice on implementation detail. Remove example of combined future scope syntax. Correct lacking clarity that friendship applies to all protected members, not just properties.
 +  * v1.0.0 - Final draft of RFC before re-opening discussion
rfc/friend-classes.1452196655.txt.gz · Last modified: 2017/09/22 13:28 (external edit)