rfc:friend-classes
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionLast revisionBoth sides next revision | ||
rfc:friend-classes [2016/01/07 16:07] – mdwheele | rfc:friend-classes [2018/07/07 18:14] – fix date mdwheele | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== PHP RFC: Class Friendship ====== | ====== PHP RFC: Class Friendship ====== | ||
- | * Version: 0.1 | + | * Version: |
- | * Date: 2015-12-10 | + | * Date: 2017-09-21 |
* Author: Dustin Wheeler, mdwheele@ncsu.edu | * Author: Dustin Wheeler, mdwheele@ncsu.edu | ||
- | * Status: | + | * Status: |
* First Published at: http:// | * First Published at: http:// | ||
+ | ===== Introduction ===== | ||
+ | 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 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 " | ||
- | ===== Introduction ===== | + | The purpose of the feature should not be conflated or confused with the goals of something like package-private |
- | Class Friendship allows a class to be better encapsulated by granting per-class access to protected properties that would otherwise have to be marked public. This affords developers an opportunity to better-model objects as behavioural units while making-explicit presentation concerns through friendship. Class Friendship is a valuable expression for object modeling when used properly. It also has value in Characterization Testing as a short-term strategy for refactoring legacy applications. It provides a natural marker / target for further work through explicit friend relationships between Systems Under Test and their test cases. | + | |
- | + | ||
- | There have been [[https:// | + | |
===== Proposal ===== | ===== Proposal ===== | ||
- | Support for class friendship is added through a new keyword, '' | + | Support for class friendship is added through a new keyword, '' |
==== Basic Usage ==== | ==== Basic Usage ==== | ||
- | A subject class may declare another class a friend through the use of a new '' | + | A subject class may declare another class a friend through the use of a new '' |
- | C++ implements Class Friendship such that friends have access to both private and protected | + | C++ implements Class Friendship such that friends have access to both private and protected |
- | Below, a class '' | + | Below, a class '' |
<code php> | <code php> | ||
class Person | class Person | ||
{ | { | ||
- | friend | + | friend |
| | ||
+ | protected $id; | ||
protected $firstName; | protected $firstName; | ||
protected $lastName; | protected $lastName; | ||
- | public function __construct($firstName, | + | public function __construct($id, $firstName, $lastName) |
{ | { | ||
+ | $this-> | ||
$this-> | $this-> | ||
$this-> | $this-> | ||
} | } | ||
- | public function | + | public function |
{ | { | ||
- | return new PersonFormatter($this); | + | return new HumanResourceReport($this); |
} | } | ||
} | } | ||
Line 45: | Line 47: | ||
<code php> | <code php> | ||
- | class PersonFormatter | + | class HumanResourceReport |
{ | { | ||
private $person; | private $person; | ||
Line 56: | Line 58: | ||
public function getFullName() | public function getFullName() | ||
{ | { | ||
- | // PersonFormatter | + | // HumanResourceReport |
- | // of Person if not explicitly listed as a friend. | + | // members |
return $this-> | return $this-> | ||
+ | } | ||
+ | | ||
+ | public function getReportIdentifier() | ||
+ | { | ||
+ | return " | ||
} | } | ||
} | } | ||
Line 64: | Line 71: | ||
<code php> | <code php> | ||
- | $person = new Person(' | + | $person = new Person(Uuid:: |
- | $formatter | + | $report |
- | var_dump($formatter-> | + | var_dump($report-> |
+ | var_dump($report-> | ||
</ | </ | ||
- | Class friendship can also be used to implement characterization tests as part of a refactoring project for legacy applications. Consider the following class responsible for executing a Fibonacci sequence: | + | Class friendship can also be used to implement |
<code php> | <code php> | ||
Line 104: | Line 112: | ||
<code php> | <code php> | ||
- | class FibonacciTest | + | class FibonacciTest |
{ | { | ||
public function testAssignmentAlgoForStateIsCorrect() | public function testAssignmentAlgoForStateIsCorrect() | ||
Line 124: | Line 132: | ||
</ | </ | ||
- | Characterization Tests are a form of white-box test useful for describing | + | Characterization Tests are a form of white-box test useful for characterizing |
- | Currently, in many examples, we have to either change visibility of properties that only exist for implementation, | + | Currently, in many examples, we have to either change visibility of properties that only exist for implementation, |
==== Other Properties ==== | ==== Other Properties ==== | ||
Line 133: | Line 141: | ||
=== Friendships are not symmetric === | === Friendships are not symmetric === | ||
If class '' | If class '' | ||
+ | |||
+ | <code php> | ||
+ | class A | ||
+ | { | ||
+ | protected $property = ' | ||
+ | | ||
+ | public function touch(B $instance) | ||
+ | { | ||
+ | echo $instance-> | ||
+ | } | ||
+ | } | ||
+ | |||
+ | class B | ||
+ | { | ||
+ | friend A; | ||
+ | | ||
+ | protected $property = ' | ||
+ | | ||
+ | public function touch(A $instance) | ||
+ | { | ||
+ | echo $instance-> | ||
+ | } | ||
+ | } | ||
+ | |||
+ | $a = new A(); | ||
+ | $b = new B(); | ||
+ | |||
+ | $b-> | ||
+ | $a-> | ||
+ | </ | ||
=== Friendships are not transitive === | === Friendships are not transitive === | ||
- | If class '' | + | If class '' |
+ | |||
+ | <code php> | ||
+ | class A | ||
+ | { | ||
+ | public function touch(C $instance) | ||
+ | { | ||
+ | echo $instance-> | ||
+ | } | ||
+ | } | ||
+ | |||
+ | class B | ||
+ | { | ||
+ | friend A; | ||
+ | } | ||
+ | |||
+ | class C | ||
+ | { | ||
+ | friend B; | ||
+ | |||
+ | protected $property = ' | ||
+ | } | ||
+ | |||
+ | $a = new A(); | ||
+ | $c = new C(); | ||
+ | |||
+ | $a-> | ||
+ | </ | ||
=== Friendships are not inherited === | === Friendships are not inherited === | ||
A friend of class '' | A friend of class '' | ||
+ | |||
+ | <code php> | ||
+ | class Base | ||
+ | { | ||
+ | friend Friendly; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | <code php> | ||
+ | class Derived extends Base | ||
+ | { | ||
+ | protected $property = ' | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | <code php> | ||
+ | class Friendly | ||
+ | { | ||
+ | public function touch(Derived $instance) | ||
+ | { | ||
+ | echo $instance-> | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | <code php> | ||
+ | $derived = new Derived(); | ||
+ | $friendly = new Friendly(); | ||
+ | |||
+ | $friendly-> | ||
+ | </ | ||
=== Access due to friendship is inherited === | === Access due to friendship is inherited === | ||
- | A friend of '' | + | A friend of '' |
<code php> | <code php> | ||
Line 149: | Line 245: | ||
protected $accessible = 'to child classes of Base'; | protected $accessible = 'to child classes of Base'; | ||
| | ||
- | | + | |
{ | { | ||
echo $this-> | echo $this-> | ||
Line 168: | Line 264: | ||
class Friendly | class Friendly | ||
{ | { | ||
- | public function touch(Derived $derived) | + | public function touch(Derived $instance) |
{ | { | ||
- | var_dump($derived-> | + | var_dump($instance-> |
| | ||
- | var_dump($derived-> | + | var_dump($instance-> |
// While Friendly is not a friend of Base, it can still access this | // While Friendly is not a friend of Base, it can still access this | ||
- | // property because it is accessible to Derived, for which it *is* | + | // property because it is accessible to Derived |
- | // a friend. | + | // property. |
- | var_dump($derived-> | + | var_dump($instance-> |
} | } | ||
} | } | ||
Line 186: | Line 282: | ||
$friendly = new Friendly | $friendly = new Friendly | ||
- | $derived-> | ||
$friendly-> | $friendly-> | ||
</ | </ | ||
==== Errors ==== | ==== Errors ==== | ||
- | In all cases above, | + | In all cases above, error messages |
==== Additional Thoughts ==== | ==== Additional Thoughts ==== | ||
- | I have purposely kept this RFC fairly slim for a number of reasons. First and foremost, I want to make it clear that I do **not** see this feature in competition with any other RFC or suggestion for limited-visibility collaborators. Rather, I see it working | + | I have purposely kept this RFC fairly slim for a number of reasons. First and foremost, I want to make it clear that I do **not** see this feature in competition with any other RFC or suggestion for limited-visibility collaborators. Rather, I see it as a feature used in concert with something like namespace visibility or package-privacy. I feel that class friendship is about object modeling and making explicit privileged relationships between two or more classes. It is a form of tighter coupling to achieve better encapsulation of behaviour. |
- | Secondly, the RFC is purposefully (yet still usefully) slim to "test the waters" | + | Secondly, the RFC is purposefully (yet usefully) slim to "test the waters" |
- | * Friendship to global | + | * Friendship to global |
- | * Friendship to a single | + | * Friendship to class methods |
- | * Friendship to a namespace (caveat) | + | * Friendship to namespace(s), possibly |
- | While namespace friendship might seem like a good idea, it is actually | + | While namespace friendship might seem like a good idea, it is probably |
===== Proposed PHP Version(s) ===== | ===== Proposed PHP Version(s) ===== | ||
- | This proposal targets | + | I had intended to target |
===== 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 '' | ||
- | |||
- | - '' | ||
- | - '' | ||
- | |||
- | ===== 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 | ||
- | * Add example for access inheritance | ||
- | |||
- | ==== Reflection API ==== | ||
- | * Implement addition of a '' | ||
===== Future Scope ===== | ===== Future Scope ===== | ||
- | While this RFC specifies friendship between classes, there is opportunity to extend this implementation and syntax: | + | While this RFC specifies friendship between classes, there is opportunity to extend this implementation and syntax |
- | * Friendship to global functions | + | |
- | * Friendship to class methods | + | |
- | * Friendship to namespace(s) | + | |
===== Proposed Voting Choices ===== | ===== Proposed Voting Choices ===== | ||
- | This RFC proposes two voting | + | As this RFC adds syntax to the language, a 2/3 majority is required. (see [[voting]]) |
- | | + | Voting starts on 2018-07-06 21:00 UTC and closes on 2018-07-13 21:00 UTC. |
- | | + | |
- | As this is a language change, a 2/3 majority is required for whether to add Friend Classes to PHP. (see [[voting]]) | + | <doodle title=" |
+ | * 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 have implemented the RFC as described with tests to verify all usage examples above. |
+ | |||
+ | https:// | ||
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 258: | Line 342: | ||
===== Changelog ===== | ===== Changelog ===== | ||
* v0.1 - Created | * v0.1 - Created | ||
+ | * 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.txt · Last modified: 2018/07/14 12:52 by mdwheele