Hi!

The topic of class / function friendship has been recently discussed and
previously covered in the past through this list as well as through feature
requests against bugs.php.net. I've recently developed an interest in the
feature after reaching for a tool that just didn't exist in a language I
really love. Given the opportunity to learn something new, I decided I
would learn what it took to add a new feature to PHP7. That said, I would
like to describe the case for class friendship in the context of a
landscape of discussion around private collaborators, in general. I would
then like to submit an RFC for discussion that proposes class friendship
(without friend functions; reasons described below). In addition, I have a
functional implementation with tests that is ready for review. I have begun
a draft of the RFC but am unable to post it do to lack of wiki karma. If I
need to contribute in other ways to gain karma, please let me know!

As an aside (before I continue), I would like to contribute implementation
notes I took along this path to the project wiki and would like review
(fact-checking) from those more knowledgeable to that regard as there is
little recent online documentation on implementing a feature that cuts
across language internals like this does. In order to implement this
feature, I had to source information from a 2012 article from nikic, the
RFC for AST and random tweets to SaraMG. Not exactly the clearest path for
a newbie! I would like to get this information somewhere it might be useful
so that others may benefit. That said, I do not want the fact that I have
implemented this feature before engaging this mailing list to sway
consideration for RFC discussion. I did this as a learning opportunity,
first; to scratch an itch, second.

To continue; class friendship allows an object to be better encapsulated by
granting per-class access to private and protected properties that would
otherwise have to be marked public or exposed through public getters. This
feature affords developers an opportunity to better model objects as
behavioral units while making explicit presentation concerns through
friendship. Admittedly, class friendship has a narrow use-case, but is
nonetheless a valuable expression for object modeling when used properly.

The purpose of the feature should not be conflated / confused with the
goals of something like package protected classes or package 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.

In my opinion, class friendship has at least two known (to me) applications:

1. White-box (characterization) testing as a tactical refactoring tool when
approaching legacy codebases
2. Modeling a tightly-coupled relationship between behavioral and
presentation concerns in a domain model

Class friendship allows a developer to make a specific test-case a friend
of a unit under test. This grants the test-case access to internal
implementation details of what may or may not be a properly factored
behavioral unit. While it may be "best practice" to model behavior rather
than expose state, the latter is a grossly common case in many PHP
code-bases, thus lending value to the strategy of white-box testing /
characterization testing in the short-term to enable a safe refactoring
process. In a language that does not implement class friendship (or private
collaborators, in general) a developer is left with a trade-off to
sacrifice modeling concerns for the benefit of a passing test-suite. If the
situation dictates characterization tests as "what's best", then the
developer either marks internal state as public or has to implement public
getters to proxy calls to internal state. In either case, for the duration
of refactor, the unit is vulnerable to misuse by clients of the package the
unit is a member of. Thus, a reason for the birth of the `@internal`
doc-block. In addition, explicitly marking a class with a collaborator
given special access is an opportunity for static analysis that highlights
isolated coupling between collaborators. In the case of testing, you could
use this as a means of determining next-work towards moving beyond the
characterization / white-box test-suite towards a more behavioral /
spec-driven test-suite. This, rather than simply deleting the
characterization tests after they are no longer needed; saving some level
of effort.

Class friendship allows an explicit and concise expression of tight
coupling between collaborators to split responsibilities. A common use-case
here is splitting presentation concerns from what would otherwise be a
"tell don't ask" behavioral unit. This has immediate practical applications
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 in the future under a major revision.
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 about pulling off "access to private class properties",
but about modeling a specific relationship between two collaborators. In
fact, access to private class properties is already possible through
closure scope "juggling" or nasty use of `debug_backtrace`. Both of these
are included in references below. While it is technically possible to
execute friend-like features in user-land, I would rather be able to
concisely represent the relationship with a known and well-documented
concept supported by the language.

The syntax I have implemented is very similar to `trait`. It introduces a
new keyword `friend` followed by a name_list. Here is a simple example of
syntax:
https://gist.github.com/mdwheele/6d9b178dc25ebb829e4c#file-1sample-php-L3-L14.
It feels natural and is forwards-compatible with friend functions, should
it be discussed and implemented. That brings me to one of my leading
statements: my current un-submitted RFC is only for friend classes, but
includes future scope towards implementation of friend functions as class
methods and global functions.

I want to limit the scope of the RFC for two reasons:

1. An RFC that implements friend classes affords the benefits I've
described above immediately. The RFC vote will also either prove desire for
the feature or will put-to-rest further discussion of related features.
Supporting friend functions as class methods offers tighter control over
property access by limiting access to a single method on the friend. This
IS valuable, but pales in comparison to what friend classes offer, by
default. The testing use-case would not really leverage this quality as
each test method would presumably require access to the system under test.
Expressing that tight of control isn't really something I would personally
advise as far as modeling goes.

2. It seems with recent feature additions (return types, primarily) the
syntax around class methods needs some time to settle and prove itself
before disrupting it through the addition of additional keywords. If friend
classes were accepted or folks are interested in having productive
discussion, I am definitely willing to discuss syntax options. It's just
the value-add at the trade-off of syntax disruption isn't very favorable,
to me.

I have more examples of class friendship available in the RFC but as per
documented processes want to "test the waters" so-to-say before officially
submitting an RFC draft for review. I would like to hold those for now as I
have a hard time organizing code samples via email, but am willing to
answer questions on other properties of class friendship (non-symmetric,
non-transitive, not inherited, access inherited).

Finally, it is my opinion that implementing class friendship -- as well as
considering other features in the domain of private collaborators -- opens
up new opportunities in object modeling for the PHP community and while it
not be the "next big thing", would be a welcome addition to the lovely
language that is PHP.

Thanks for your consideration and I look forward to further discussion!

P.S.: Congratulations on PHP7!

Previous discussion / related materials:

- https://en.wikipedia.org/wiki/Friend_class
- https://bugs.php.net/bug.php?id=34044
- https://marc.info/?l=php-internals&m=144639394529142
-
http://ocramius.github.io/blog/accessing-private-php-class-members-without-reflection/

-- 
Dustin Wheeler | Software Developer
NC State University
m: mdwhe...@ncsu.edu | w: 5-9786
"If you don't know where you're going, it's easy to iteratively not get
there."

Reply via email to