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:
- White-box (characterization) testing as a tactical refactoring tool when
approaching legacy codebases - 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:
-
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. -
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: mdwheele@ncsu.edu | w: 5-9786
"If you don't know where you're going, it's easy to iteratively not get
there."
Hi,
My biggest concern about supporting friend classes is the ability to access
non-intentional to be accessed code outside of the original class's
knowledge. This by itself is very dangerous.
I do see however package-private classes as a possibility (I actually have
a partially running patch for that) and allowing it to access protected
(not private) members is a second stage of my goal, so very similar to what
your suggested, with the exception that the original class defined what can
be accessed.
Regards,
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:
- White-box (characterization) testing as a tactical refactoring tool when
approaching legacy codebases- Modeling a tightly-coupled relationship between behavioral and
presentation concerns in a domain modelClass 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 ofdebug_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 keywordfriend
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:
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.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: mdwheele@ncsu.edu | w: 5-9786
"If you don't know where you're going, it's easy to iteratively not get
there."
--
Guilherme Blanco
MSN: guilhermeblanco@hotmail.com
GTalk: guilhermeblanco
Toronto - ON/Canada
Hi,
Thanks for this interesting and well-documented message.
When looking at the history about class friendship, you may have seen that the concept was globally rejected in favor of Guilherme's project of 'package-privacy'. I personnally think friend classes are a different and more powerful concept and, if you feel strong enough to write and defend an RFC and defend it, I encourage you to go on.
These are details:
-
I would prefer providing the same access to friend classes as parents and descendants : that is allow 'protected' but keep private reserved to the class. This way, when looking at the class code, you know that private data cannot be accessed, even by friends.
-
I would extend the syntax to namespaces. For examples, '\MyNS' (syntax to dicuss) would declare every class in the '\MyNS' namespaces or sub-namespaces as friends. Must support 'friend NAMESPACE' or something equivalent. This, coupled with a protected __construct() method would provide something quite similar to package-private access, but more extensible and powerful.
Regards
François
Thank you very much for the comments, François.
Hi,
Thanks for this interesting and well-documented message.
When looking at the history about class friendship, you may have seen that the concept was globally rejected in favor of Guilherme's project of 'package-privacy'. I personnally think friend classes are a different and more powerful concept and, if you feel strong enough to write and defend an RFC and defend it, I encourage you to go on.
Firstly, I apologize for the time it's taken to reply.
I agree that friend classes approach a perhaps subtly different
modeling concern than do package-private / package-visibility
features. This is to say that they are not in competition as
solutions, but are different tools for expressing different object
modeling concerns that happen to share an overlapping problem-space.
In my opinion, this is a key statement to make and describe going
forward. Both of these approaches acknowledge the problem-space of
"private collaborators". "Private collaborators" itself is perhaps
loaded terminology as it happens to reuse an already-defined concept
in this domain: private
. The intent is to be able to express an
intimate relationship between two or more classes of object for some
justifiable domain-specific modeling reason.
I do not believe there to be any "supported features" in PHP that
directly support the problem-space of private collaborators. We have,
at best, work-arounds to achieve the end goal, but at a translation
cost of having to explain "This scope-juggling I'm doing with
Closure::bind means this class is a friend." I have a desire to be
able to express that relationship directly and I see value in being
able to do so.
I want to write and defend an RFC for implementation of friend classes
in PHP for the reasons above. In addition, I believe that scoping an
RFC for friend classes somewhat tightly works quickly towards a few
outcomes:
-
Does the community and core developer team see value in private
collaborators as a concept, in general? If the RFC is scoped
appropriately, it could serve as a barometer of interest that would
validate further work on the concept (addition of class-methods and
global functions) as well as further validate implementation of
package-private classes. If the "smallest thing that could provide
value" fails miserably, that is enough data to pivot on. -
The RFC, even if it fails, shows formal historical interest in the
feature and the feedback and references captured through the RFC
process will inform alternative implementations or alternative
approaches to the private collaboration problem. I really see that as
a win for the language and this problem space. I'm willing to defend
an RFC for friend classes in PHP for this reason alone.
- I would prefer providing the same access to friend classes as parents and descendants : that is allow 'protected' but keep private reserved to the class. This way, when looking at the class code, you know that private data cannot be accessed, even by friends.
I could not agree more with this and believe this is an extremely
healthy compromise on the concern of non-intentional access to
members. The reasons for this compromise should be expressed in any
RFC as well as documentation, in my opinion (if things get to that
point). I have already started adjusting my tests for this compromise.
- I would extend the syntax to namespaces. For examples, '\MyNS' (syntax to dicuss) would declare every class in the '\MyNS' namespaces or sub-namespaces as friends. Must support 'friend NAMESPACE' or something equivalent. This, coupled with a protected __construct() method would provide something quite similar to package-private access, but more extensible and powerful.
François, would you be willing to describe more of the reasoning
behind this? Specifically "more extensible and powerful" [for what?].
Also, I can't think of a better way to word that request, but see how
it can come across as a direct-challenge. Just want to say I'm
genuinely curious to what the intended outcome is and don't mean to
come across as "point-y".
In my opinion, class friendship is explicit. I reach for this feature
when I need to say, "Hey, you two objects; one of you is going to know
more than the rest of my system knows about this other object. I'm
doing this so that [insert domain-specific modeling concern or
justification]." In my usage, I don't know that I would use friendship
as a means to make an entire package (namespace, in PHP's case) aware
of a single object. If I had to model this concern, I would instead
look to grab some tool that implemented some sort of namespace /
package visibility.
The other thought I have is that when I go to an object that has
friended another, I want it to be clear and determined; the degree to
which I have coupled that object to collaborators through friendship.
Allowing friend __NAMESPACE__
allows this to grow somewhat
unbounded; which... isn't the end-of-the-world by any means and I can
see where it could be applied. Currently, however, I see it as a
misapplication of the tool?
Haha, I apologize. I have a hard time finding concision sometimes. I
guess what I'm saying is that I am definitely not against this idea,
but that I'm not generally for it, yet. To me, it doesn't map as
cleanly to the goal of better encapsulation of behaviour in one object
through strategic tighter-coupling to another object. I'd like to hear
more of your thoughts to gain some perspective.
Would this be something you would consider being a candidate for
future development and exploration of the feature? I am already
planning to put class friendship to methods and global functions on
the list for future work. Having explicit class friendship, period,
would be a pretty awesome win and meet 80% of the use-case (80/20
rule-ish here). Having clear and identified opportunities for
continued effort to flesh out the feature is a good idea, I believe.
It's also a trade-off of making a small step in the feature to allow
it to settle while other features in this problem-space move forward.
In this way, we can be sure to stay consistent in the "spirit of
private collaborators" (if that isn't too wishy-washy to say). I only
ask this question because I think it can be misunderstood as a
substitution for more robust namespace-visibility features and
possibly devalue efforts in those directions; namely Guilherme's. I
see value in that work and think there's a bit of strategy to consider
here for the sake of language design. This particular extension is
definitely in the overlapping problem-space of these two modeling
concerns!
For inheritance rules, you may be interested by a small article I wrote on the subject (http://tekwire.net/joomla/projects/ideas/php-friend).
Yes! You might be delighted to know that the current implementation
adheres to these rules, exactly. I honestly don't know how I didn't
come across your article in gathering references. Are you amenable to
me adding it to my list of references?
Thanks to both of you for your comments thus far and I will be sending
a request for RFC karma to create an RFC in Draft. I have a lot of
information attached to that already that describes the feature itself
with examples, but also includes background information on the
feature's lifecycle in C++ (not to say we compare ourselves, but to
describe lessons learned from that community as far as the
implementation, maintenance and community-usage of the feature). I am
eager to continue working with you all on this.
Thanks!
--
Dustin Wheeler | Software Developer
NC State University
m: mdwheele@ncsu.edu | w: 5-9786
"If you don't know where you're going, it's easy to iteratively not get there."
Both of these approaches acknowledge the problem-space of
"private collaborators". "Private collaborators" itself is perhaps
loaded terminology as it happens to reuse an already-defined concept
in this domain:private
. The intent is to be able to express an
intimate relationship between two or more classes of object for some
justifiable domain-specific modeling reason.
Since friendship is about visibility, and "private" is already a
well-defined term in discussions about visibility, I would avoid the term
"private" in discussions about friendship unless you actually mean "private
visibility". To me, the nut of the solution is expressed as:
Class members declared as friend can be accessed by only those classes
explicitly listed as being collaborators.
- I would extend the syntax to namespaces. For examples, '\MyNS' (syntax
to dicuss) would declare every class in the '\MyNS' namespaces or
sub-namespaces as friends. Must support 'friend NAMESPACE' or something
equivalent. This, coupled with a protected __construct() method would
provide something quite similar to package-private access, but more
extensible and powerful.François, would you be willing to describe more of the reasoning
behind this? Specifically "more extensible and powerful" [for what?].
Also, I can't think of a better way to word that request, but see how
it can come across as a direct-challenge. Just want to say I'm
genuinely curious to what the intended outcome is and don't mean to
come across as "point-y".
One example comes to mind. Imagine you have a service "manager" class that
accepts an abstract "worker" class. You then have several concrete worker
implementations. It would be nice to be able to, at a namespace level, say
all these workers are friends of the manager.
Systems where you have pluggable drivers (eg databases) or lots of
collaborating objects (charting systems) would benefit from a granular way
to declare friendship.
I don't know that I would use friendship
as a means to make an entire package (namespace, in PHP's case) aware
of a single object.
I think it's the other way around: a single object declares its friendship
with an entire namespace.
Looking forward to the RFC!
Bishop Bettini wrote on 10/12/2015 14:44:
I don't know that I would use friendship
as a means to make an entire package (namespace, in PHP's case) aware
of a single object.I think it's the other way around: a single object declares its friendship
with an entire namespace.
I guess the point is that if package visibility were also implemented,
you could simply make the relevant methods public, and the class as a
whole private to the namespace. Any public APIs could then be on a
public class in the same namespace, e.g in the same way as having
methods "public final function foo()" and "protected function do_foo()",
you could have "public class Thing" referencing "private class
InternalThing".
The advantage of friend declarations seems to be precisely that they are
not making a general declaration of scope, but marking very specific
couplings. If a large number of classes need access to something marked
private, it's clearly not really private.
Regards,
Rowan Collins
[IMSoP]