I'd like to point out some puzzling behaviors in Traits as they exist in the production releases of PHP 5.4.
- Name collisions between a trait method and a class method using the trait go unreported, the class silently shadowing the trait method:
trait T {
function foo() { $this->bar; }
function bar() { echo 'trait'; }
}
class C {
use T;
function bar() { echo 'class'; }
}
$c = new C;
$c->foo(); // "class"
Proposed behavior: Fatal error on collision, unless the method is imported with a unique name using the { ... as ... } syntax.
- Using "as" syntax when importing a trait does NOT rename a method, but creates an alias CLONE, the original method still callable.
trait T {
function bar() { echo 'trait'; }
}
class C {
use T { bar as foo; }
}
$c = new C;
$c->bar(); // "trait"
Proposed behavior: the original name should be only accessible within the trait and its methods, not from the class methods or by calling the class instance's methods from outside.
- Properties silently collide in traits and classes.
trait T1 {
private $foo;
trait T2 {
private $foo;
}
class C { use T1, T2; } // No error.
Proposed behavior: An error is produced only when the properties differ in visibility or a default value, which is clearly insufficient to determine they're used for the same purpose, and hold the same data. Instead they should use the same logic as method conflicts: fatal error on name collision. Alternatively, each trait property whould be accessible within the trait that defines it, not from other traits used in the same class, or the class itself.
- The documentation says static propeties can't be defined by traits. Yet they can.
I don't know what's the bug here: a doc bug, or a code bug. For consistency, static properties should work, if instance properties work. Nothing is gained supporting it half-way.
Not many use 5.4 yet, so the sooner all this is clarified, the better, before it becomes completely unfixable due to BC. Feedback?
Stan
Dear Stan:
- Name collisions between a trait method and a class method using the trait go unreported, the class silently shadowing the trait method:
trait T {
function foo() { $this->bar; }
function bar() { echo 'trait'; }
}class C {
use T;
function bar() { echo 'class'; }
}$c = new C;
$c->foo(); // "class"Proposed behavior: Fatal error on collision, unless the method is imported with a unique name using the { ... as ... } syntax.
This happens to be 'by design'.
The methods in the class have always higher precedence.
This is the same 'overriding' behavior used for inheritance.
From my personal perspective, changing this would lead to a major inconsistency with how subclassing works.
If you disagree, please open an explicit thread for this problem and report it in the bug tracker.
- Using "as" syntax when importing a trait does NOT rename a method, but creates an alias CLONE, the original method still callable.
trait T {
function bar() { echo 'trait'; }
}class C {
use T { bar as foo; }
}$c = new C;
$c->bar(); // "trait"Proposed behavior: the original name should be only accessible within the trait and its methods, not from the class methods or by calling the class instance's methods from outside.
Again, this happens to be 'by design'.
PHP is a rather dynamic language, and we happen to have things like $c->$foo();
where $foo is a string. Renaming is technically not reasonable, and would also lead to major confusion when metaprogramming is used.
Bye the way, this is documented: https://wiki.php.net/rfc/horizontalreuse#renaming
- Properties silently collide in traits and classes.
trait T1 {
private $foo;trait T2 {
private $foo;
}class C { use T1, T2; } // No error.
Proposed behavior: An error is produced only when the properties differ in visibility or a default value, which is clearly insufficient to determine they're used for the same purpose, and hold the same data. Instead they should use the same logic as method conflicts: fatal error on name collision. Alternatively, each trait property whould be accessible within the trait that defines it, not from other traits used in the same class, or the class itself.
Please read https://wiki.php.net/rfc/horizontalreuse#handling_of_propertiesstate carefully.
Again, this is intended behavior.
If you want to be notified of such collisions, please use E_STRICT.
- The documentation says static propeties can't be defined by traits. Yet they can.
I don't know what's the bug here: a doc bug, or a code bug. For consistency, static properties should work, if instance properties work. Nothing is gained supporting it half-way.
Could you please point me exactly to the paragraph where we have something written about static properties? I do not see that I wrote anything special about static properties in the RFC. And I do not find it in the docs either.
static properties should work like normal properties.
Best regards
Stefan
--
Stefan Marr
Software Languages Lab
Vrije Universiteit Brussel
Pleinlaan 2 / B-1050 Brussels / Belgium
http://soft.vub.ac.be/~smarr
Phone: +32 2 629 2974
Fax: +32 2 629 3525
The methods in the class have always higher precedence.
This is the same 'overriding' behavior used for inheritance.
From my personal perspective, changing this would lead to a major
inconsistency with how subclassing works.
Hi, I've subsequently read your RFC carefully and saw some of those items
are by design. However while they're by design, the end result seems rather
confusing I should say.
While I'm glad the RFC was shooting for consistency in general, traits are
not subclassing. Therefore aligning the behavior of features for vertical
reuse with features for horizontal reuse seems somewhat arbtirary to me. The
use cases for overriding methods like this seem sketchy at best. What is the
use case, and how are those use cases more important than the confusion
caused by allowig this?
- Using "as" syntax when importing a trait does NOT rename a method, but
creates an alias CLONE, the original method still callable.
Proposed behavior: the original name should be only accessible within
the trait and its methods, not from the class methods or by calling the
class instance's methods from outside.
Again, this happens to be 'by design'.
PHP is a rather dynamic language, and we happen to have things like
$c->$foo();
where $foo is a string. Renaming is technically not reasonable, and would
also lead to major confusion when metaprogramming is used.
Can you please explain how are function names by variable at all related to
this? $object->foo() and $object->$foo() aren't even related in any way
semantically. If a conflict exists while grafting a trait, the original
method will be overshadowed silently and the method will be only accessible
by the alias. The entire idea of aliasing is to avoid a conflict with an
existing method of the same name. While if there's no collision, it's
accessible by both. So in practice it works by chance. How is "working by
chance" in any way aiding meta programming?
- Properties silently collide in traits and classes.
Please read
https://wiki.php.net/rfc/horizontalreuse#handling_of_propertiesstate
carefully.Again, this is intended behavior.
If you want to be notified of such collisions, please use E_STRICT.
I've read them carefully now, but there's no solid reason given for this
behavior, or why the proper behavior only exists in E_STRICT.
I can't really see "state is a complex topic" as a reason why trait property
collisions are handles inconsistently with method collisions.
- The documentation says static propeties can't be defined by traits.
Yet they can.I don't know what's the bug here: a doc bug, or a code bug. For
consistency, static properties should work, if instance properties work.
Nothing is gained supporting it half-way.
Could you please point me exactly to the paragraph where we have
something written about static properties? I do not see that I wrote
anything special about static properties in the >RFC. And I do not find it
in the docs either. static properties should work like normal properties.
Sure. From the manual page for traits: "Static variables can be referred to
in trait methods, but cannot be defined by the trait." This paragraph is
then following by a code example using function-static variables as what
seems like an example for a replacement for static trait properties,
strongly suggesting the former aren't supported. Yet they are.
The RFC seems to also give examples of method-level static variables, and I
honestly can't see how this is related to traits or trait/class-level
members at all.
Stan
Hi Stan:
The methods in the class have always higher precedence.
This is the same 'overriding' behavior used for inheritance.
From my personal perspective, changing this would lead to a major inconsistency with how subclassing works.Hi, I've subsequently read your RFC carefully and saw some of those items are by design. However while they're by design, the end result seems rather confusing I should say.
While I'm glad the RFC was shooting for consistency in general, traits are not subclassing. Therefore aligning the behavior of features for vertical reuse with features for horizontal reuse seems somewhat arbtirary to me.
Horizontal reuse is 'fit into' the hierarchy of things:
- super class
- horizontal reuse
- actual class
From that follows that traits override the super class's methods.
And, conflict resolution is only done between traits, not between a class and its traits.
The class body is a definite thing. Why would you purposefully add something to a class body that conflicts with a trait?
The class needs to know exactly what the traits are doing. There is no black-box reuse.
The use cases for overriding methods like this seem sketchy at best. What is the use case, and how are those use cases more important than the confusion caused by allowig this?
I don't see your problem. Can you please elaborate with an example (other than your initial one?
The use case for overriding is typically that you got a conflict that you need to resolve by making a call to both conflicting methods.
Consider two traits that happen to provide a log()
function, and in the resulting class you need to call both in all places log()
has been called before.
Since the user of these traits is the only place where such semantic is sensibly defined, the method in the class body is overriding the traits methods, and can access aliases you introduced for the traits' methods.
- Using "as" syntax when importing a trait does NOT rename a method, but creates an alias CLONE, the original method still callable.
Proposed behavior: the original name should be only accessible within the trait and its methods, not from the class methods or by calling the class instance's methods from outside.Again, this happens to be 'by design'.
PHP is a rather dynamic language, and we happen to have things like $c->$foo();
where $foo is a string. Renaming is technically not reasonable, and would also lead to major confusion when metaprogramming is used.
Can you please explain how are function names by variable at all related to this? $object->foo() and $object->$foo() aren't even related in any way semantically. If a conflict exists while grafting a trait, the original method will be overshadowed silently and the method will be only accessible by the alias. The entire idea of aliasing is to avoid a conflict with an existing method of the same name. While if there's no collision, it's accessible by both. So in practice it works by chance. How is "working by chance" in any way aiding meta programming?
Let's use your example and make something more complex out of it:
trait T {
function bar(){ $this->print();
$str = 'print';
$this->$str(); }
function print() { echo 'trait'; }
}
class C {
use T { print as foo; }
}
What is supposed to happen in this example if we assume renaming?
Would you expect the first function call to print() in bar() to work after the renaming?
Would you expect the second call, using metaprogramming, to work as well?
If we use aliasing instead of renaming, we have predictable behavior for all options.
foo just happens to be another alias for print.
If print is conflicting with something else, the conflict can be resolved, but print() is still going to be called reliably. If you do renaming, it will be partial, and you will get foo() called one time, and print() the other time.
- Properties silently collide in traits and classes.
Please read https://wiki.php.net/rfc/horizontalreuse#handling_of_propertiesstate carefully.Again, this is intended behavior.
If you want to be notified of such collisions, please use E_STRICT.I've read them carefully now, but there's no solid reason given for this behavior, or why the proper behavior only exists in E_STRICT.
I can't really see "state is a complex topic" as a reason why trait property collisions are handles inconsistently with method collisions.
Please do me the favor and consult the mailing list archives.
It is all in the various discussions.
There is no proper way to handle state, state requires splitting, merging, and what not. None of the research prototypes provides a simple solution.
The solution we have is a 'best effort' solution.
If you can do better, please make a proposal.
- The documentation says static propeties can't be defined by traits. Yet they can.
I don't know what's the bug here: a doc bug, or a code bug. For consistency, static properties should work, if instance properties work. Nothing is gained supporting it half-way.
Could you please point me exactly to the paragraph where we have something written about static properties? I do not see that I wrote anything special about static properties in the >RFC. And I do not find it in the docs either. static properties should work like normal properties.
Sure. From the manual page for traits: "Static variables can be referred to in trait methods, but cannot be defined by the trait."
This paragraph is then following by a code example using function-static variables as what seems like an example for a replacement for static trait properties, strongly suggesting the former aren't supported. Yet they are.The RFC seems to also give examples of method-level static variables, and I honestly can't see how this is related to traits or trait/class-level members at all.
I assume we are talking about: http://php.net/manual/en/language.oop5.traits.php
Static variables and members/properties are not related at all. The writer of the documentation didn't get the wording correct. Please feel free to submit a patch.
The example code given in the manual is correct. (Example #9 Static Variables)
The section heading is misleading. It is not about static members, and members and variables are not synonym. Members are synonym with properties, and properties can be static.
Static variables are local to a class, not to a trait.
Traits do not exist at runtime, they are flattened into classes.
With regard to your other mail.
I didn't get a bug report with the keyword trait in it.
If you want me to clarify the confusion, please give me a link.
Best regards
Stefan
Stan
--
--
Stefan Marr
Software Languages Lab
Vrije Universiteit Brussel
Pleinlaan 2 / B-1050 Brussels / Belgium
http://soft.vub.ac.be/~smarr
Phone: +32 2 629 2974
Fax: +32 2 629 3525
From that follows that traits override the super class's methods.
And, conflict resolution is only done between traits, not between a class
and its traits.
The class body is a definite thing. Why would you purposefully add
something to a class body that conflicts with a trait?
The class needs to know exactly what the traits are doing. There is no
black-box reuse.
But that's precisely my point. No class would purposefully add something
to conflict with a trait. Then by your own description the only case is
accidentally adding something to conflict with it. In such a case why on
Earth is it allowed without a fatal error?
The use case for overriding is typically that you got a conflict that you
need to resolve by making a call to both conflicting methods.
Consider two traits that happen to provide alog()
function, and in the
resulting class you need to call both in all placeslog()
has been called
before.
Since the user of these traits is the only place where such semantic is
sensibly defined, the method in the class body is overriding the traits
methods, and can access aliases you
introduced for the traits' methods.
The way it works right now, on conflict, a class may access the trait method
via the alias, or its own overriden copy by the conflicting name.
What is a trait to do? It can't access its own method at all as it doesn't
"know" the alias.
The way this is implemented makes no sense, I'm afraid.
Let's use your example and make something more complex out of it:
trait T {
function bar(){ $this->print();
$str = 'print';
$this->$str(); }
function print() { echo 'trait'; }
}class C {
use T { print as foo; }
}What is supposed to happen in this example if we assume renaming?
Would you expect the first function call to print() in bar() to work after
the renaming?
Would you expect the second call, using metaprogramming, to work as well?
It's very simple:
-
only the original name should work for methods defined in a trait
(including metacalls). -
only the new name should work from methods defined in the using class
(including metacalls).
Yes, I know: this is a lot more complicated to implement. But if we'll
take shortcuts in user-facing behavior just because it's simpler to
implement, then aliasing should've have never been added at all for 5.4,
until it can be implemented reasonably.
If we use aliasing instead of renaming, we have predictable behavior for
all options.
It's not predictable at all. If the class defines "function print" in your
example above, the trait suddenly can't call it's own method anymore!
Metacall or not.
Please do me the favor and consult the mailing list archives.
It is all in the various discussions.
If behavior specification and reasoning worked by consulting the archives
for internals, we wouldn't have RFCs. I see no good reasoning in the RFC for
this behavior, and going though thousands of emails in the archive is not a
reasonable way to find out the reasoning.
There is no proper way to handle state, state requires splitting, merging,
and what not. None of the research prototypes provides a simple solution.
State does not require splitting, merging. What does state "splitting" even
mean? Here's a very simple design principle here: if you can't guarantee
correctness for a behavior, provide a limited behavior, which guarantees
correctness. This is how language design works. If there's a conflict, die
with a fatal error, make people rename their properties so there's no
conflict.
Assuming silently that the user is intentionally colliding state variables
between traits because he/she wants to merge/split state is insanity IMO.
The solution we have is a 'best effort' solution.
If you can do better, please make a proposal.
I did in my original email. No property collisions. Fatal errors. It's very
simple.
I assume we are talking about:
http://php.net/manual/en/language.oop5.traits.phpStatic variables and members/properties are not related at all. The writer
of the documentation didn't get the wording correct. Please feel free to
submit a patch.
I've submitted a doc bug. No one want to fix it since they think it's PHP
that's wrong, not the manual. I can't do anything more about it.
Here's the link: https://bugs.php.net/bug.php?id=62156
Stan
Hi Stan:
From that follows that traits override the super class's methods.
And, conflict resolution is only done between traits, not between a class and its traits.
The class body is a definite thing. Why would you purposefully add something to a class body that conflicts with a trait?
The class needs to know exactly what the traits are doing. There is no black-box reuse.But that's precisely my point. No class would purposefully add something to conflict with a trait. Then by your own description the only case is accidentally adding something to conflict with it. In such a case why on Earth is it allowed without a fatal error?
Because it is not a matter of horizontal reuse.
Why don't you get a warning when you override an inherited method?
Because that is precisely the way things are supposed to work.
The body of a class is not a trait. These methods are not 'equals'.
I still think that design decision is a sensible one with respect to the rest of PHP.
The use case for overriding is typically that you got a conflict that you need to resolve by making a call to both conflicting methods.
Consider two traits that happen to provide alog()
function, and in the resulting class you need to call both in all placeslog()
has been called before.
Since the user of these traits is the only place where such semantic is sensibly defined, the method in the class body is overriding the traits methods, and can access aliases you
introduced for the traits' methods.The way it works right now, on conflict, a class may access the trait method via the alias, or its own overriden copy by the conflicting name.
What is a trait to do? It can't access its own method at all as it doesn't "know" the alias.The way this is implemented makes no sense, I'm afraid.
Traits are not classes, traits are supposed to enable composition.
There is no static binding between trait methods. And, yes, this is by design.
It makes sense when you use traits for things you couldn't use classes for.
If you need strong consistency guarantees then you want to use classes.
Let's use your example and make something more complex out of it:
trait T {
function bar(){ $this->print();
$str = 'print';
$this->$str(); }
function print() { echo 'trait'; }
}class C {
use T { print as foo; }
}What is supposed to happen in this example if we assume renaming?
Would you expect the first function call to print() in bar() to work after the renaming?
Would you expect the second call, using metaprogramming, to work as well?It's very simple:
- only the original name should work for methods defined in a trait (including metacalls).
- only the new name should work from methods defined in the using class (including metacalls).
Yes, I know: this is a lot more complicated to implement. But if we'll take shortcuts in user-facing behavior just because it's simpler to implement, then aliasing should've have never been added at all for 5.4, until it can be implemented reasonably.
So, what is your proposal?
If we use aliasing instead of renaming, we have predictable behavior for all options.
It's not predictable at all. If the class defines "function print" in your example above, the trait suddenly can't call it's own method anymore! Metacall or not.
It is calling another method instead, indeed. That's composition, and that's what traits are designed for.
There is no proper way to handle state, state requires splitting, merging, and what not. None of the research prototypes provides a simple solution.
State does not require splitting, merging. What does state "splitting" even mean? Here's a very simple design principle here: if you can't guarantee correctness for a behavior, provide a limited behavior, which guarantees correctness. This is how language design works. If there's a conflict, die with a fatal error, make people rename their properties so there's no conflict.
http://scg.unibe.ch/archive/papers/Berg07aStatefulTraits.pdf
Assuming silently that the user is intentionally colliding state variables between traits because he/she wants to merge/split state is insanity IMO.
Instead of calling my insane, I would prefer if you make constructive proposals.
I guess, since you are an expert in language design (and an active user of PHP), you are aware of this behavior:
class T1 {
public function foo() {
$this->prop = 1;
}
}
class T2 extends T1 {
public function bar() {
$this->prop = 2;
}
}
$o = new T2;
$o->foo();
$o->bar();
var_dump($o);
What is your point again?
Traits offer already a much stronger safety-net than most other constructs in the language. And, thereby are way to strict for some people.
The solution we have is a 'best effort' solution.
If you can do better, please make a proposal.I did in my original email. No property collisions. Fatal errors. It's very simple.
There is nothing simple in PHP...
People don't like fatal errors, especially not for things they intend to just work.
From my example above, the kind of guarantees you expect are just not something that is part of what PHP is.
I assume we are talking about: http://php.net/manual/en/language.oop5.traits.php
Static variables and members/properties are not related at all. The writer of the documentation didn't get the wording correct. Please feel free to submit a patch.
I've submitted a doc bug. No one want to fix it since they think it's PHP that's wrong, not the manual. I can't do anything more about it.
Here's the link: https://bugs.php.net/bug.php?id=62156
I don't know who 'they' are, but I am the insane (thanks for that attribute) person who wrote the RFCs and the implementation. So, either you accept my answer that static properties just work, and you fix the manual by providing a greatly appreciate patch,
or, you open a new thread and propose a change, start an RFC, and all that jazz.
Best regards
Stefan
--
Stefan Marr
Software Languages Lab
Vrije Universiteit Brussel
Pleinlaan 2 / B-1050 Brussels / Belgium
http://soft.vub.ac.be/~smarr
Phone: +32 2 629 2974
Fax: +32 2 629 3525
Because it is not a matter of horizontal reuse.
Why don't you get a warning when you override an inherited method?
Because that is precisely the way things are supposed to work.The body of a class is not a trait. These methods are not 'equals'.
I still think that design decision is a sensible one with respect to the
rest of PHP.
If traits act like superclasses, why can the class call a private trait
method, by that logic? It shouldn't be able to. You've mixed two paradigms
here and the result is messy.
Traits are for horizontal reuse. If you're justifying this by giving
examples about superclasses, this is the wrong mental model for traits.
Ask anybody "what are traits". The most common answer is "a glorified
copy-and-paste". Do it, you'll see. "Glorified copy-and-paste."
Now ask yourself, if you have class Foo { public $bar; public $bar; } would
that be fine? No. So it's not a copy-and-paste.
But if you copy pasted a private method would you be able to call it? Yes.
So it's copy-and-paste.
So what is it? It's neither and both. I expect a mighty confusion and abuse
of traits because they can't decide what they are in 5.4. Traits should pick
one model and stick to it.
It's very simple:
- only the original name should work for methods defined in a trait
(including metacalls).- only the new name should work from methods defined in the using
class (including metacalls).Yes, I know: this is a lot more complicated to implement. But if we'll
take shortcuts in user-facing behavior just because it's simpler to
implement, then aliasing should've have never been added at all for 5.4,
until it can be implemented reasonably.
So, what is your proposal?
My proposal is what I said above. Either aliases should be dropped as a
feature from traits and the feature has to be rethought, or they have to
work sensibly as outlined in 1) and 2) above.
IMO they do more harm than help in their current form. Sometimes not
implementing a compromise is a feature itself.
If we use aliasing instead of renaming, we have predictable behavior for
all options.It's not predictable at all. If the class defines "function print" in your
example above, the trait suddenly can't call it's own method anymore!
Metacall or not.
It is calling another method instead, indeed. That's composition, and that's
what traits are designed for.
http://scg.unibe.ch/archive/papers/Berg07aStatefulTraits.pdf
Let me quote that PDF you've linked me to:
"By default, variables are private to the trait that defines them. Because
variables are private, CONFLICTS BETWEEN VARIABLES CANNOT OCCUR when traits
are composed. If, for example, traits T1 and T2 each define a variable x,
then the composition of T1 + T2 does not yield a variable conflict. Variables
are only visible to the trait that defines them"
"By default, instance variables are private to their trait. If the scope of
variables is not broadened at composition time using the variable access
operator, CONFLICTS DO NOT OCCUR AND THE TRAITS DO NOT SHARE STATE."
Emphasis mine.
This is absolutely not what is implemented in 5.4, and this is in part why
we're having this thread now.
Here's the proof:
trait T {
private $foo = 0;
public function set($x) { $this->foo = $x; }
public function get() { echo $this->foo . "<br>"; }
}
trait T2 {
private $foo = 0;
public function set2($x) { $this->foo = $x; }
public function get2() { echo $this->foo . "<br>"; }
}
class C {
use T, T2;
public function classGet () { echo $this->foo . "<br>"; }
}
$x = new C;
$x->set(321);
$x->get(); // Correctly produces "321"
$x->set2(123);
$x->get2(); // Correctly produces "123"
$x->get(); // Incorrect, the paper says it should still be "321", instead
the var is shared, so it's "123"
$x->classGet(); // Incorrect, the paper says this shouldn't work at all,
instead it's "123"
Assuming silently that the user is intentionally colliding state
variables between traits because he/she wants to merge/split state is
insanity IMO.Instead of calling my insane, I would prefer if you make constructive
proposals.
I'm not calling you insane, I'm calling PHP's behavior on trait conflicts
insane, and I definitely stand by this, and the paper you're linking me to
also seems to confirm this. I keep giving you more reasonable behaviors, but
I don't think you're acknowledging them.
I guess, since you are an expert in language design (and an active user of
PHP), you are aware of this behavior:class T1 {
public function foo() {
$this->prop = 1;
}
}class T2 extends T1 {
public function bar() {
$this->prop = 2;
}
}$o = new T2;
$o->foo();
$o->bar();var_dump($o);
What is your point again?
My point which I've described multiple times at this point is that traits
are not classes, we already have classes in PHP, you know? Imitiating
classes with traits is the wrong mental model for their behavior, and the
paper you're linking me to confirms tht.
There is nothing simple in PHP...
People don't like fatal errors, especially not for things they intend to
just work.
From my example above, the kind of guarantees you expect are just not
something that is part of what PHP is.
Letting conflicts overwrite randomly properties is not "just working". It's
an example of "not working, and it's very hard to debug why."
I don't know who 'they' are, but I am the insane (thanks for that
attribute) person who wrote the RFCs and the implementation. So, either
you accept my answer that static properties > just work, and you fix the
manual by providing a greatly appreciate patch,
or, you open a new thread and propose a change, start an RFC, and all that
jazz.
Once more, I've not called you insane, and I hope you can keep the
discussion to the problem at hand. The most important thing here is what is
PHP's problem with the implementation of traits, and it certainly has some.
My goal isn't to turn this into an exchange of insults, but I feel strongly
about the current implementation of traits, and I'm gonna qualify it as I
see is appropriate.
Because it is not a matter of horizontal reuse.
Why don't you get a warning when you override an inherited method?
Because that is precisely the way things are supposed to work.The body of a class is not a trait. These methods are not 'equals'.
I still think that design decision is a sensible one with respect to the rest of PHP.If traits act like superclasses, why can the class call a private trait method, by that logic?
Traits have a very simple semantics:
Step 1: identify which traits are to be used.
Step 2: resolve conflicts between traits.
Step 3: flatten the result into the class.
Step 4: do in the class what ever you are used to do in a normal class.
This results in a "Glorified copy-and-paste.".
This explains why traits are allowed to access private methods, because they are part of the class. They are flattened into it.
But, they are flattened into the class before the body of the class is processed.
So what is it? It's neither and both. I expect a mighty confusion and abuse of traits because they can't decide what they are in 5.4. Traits should pick one model and stick to it.
Yes, I agree with you that there are known problems with state.
To quote myself:
https://wiki.php.net/rfc/horizontalreuse#handling_of_propertiesstate
"Traits do not provide any provisioning for handling state. They are meant to provide a light-weight mechanism for flexible code reuse, with the main goal being to avoid code duplication."
Property support is a best-effort solution at the moment.
Following the rules outlined in the RFC:
"
• Properties are considered incompatible if they differ in their definition. This means, they differ in the applied modifiers (static, public, protected, private) or their initial value.
• Incompatible properties result in a fatal error.
• In all other cases, i.e., when the definitions are identical, an E_STRICT
notice is shown to raise awareness about the potentially problematic, and discouraged use of properties.
• For those checks, all properties are treated equal. Properties from the base class and the composing class have to be compatible with properties from traits as well as the properties between all traits have to be compatible.
• Non-coliding properties, and properties which are not considered incompatible behave exactly the same as if they would have been defined in the composing class.
"
Please provide a fix in form of an RFC and a patch.
Thank you!
My proposal is what I said above. Either aliases should be dropped as a feature from traits and the feature has to be rethought, or they have to work sensibly as outlined in 1) and 2) above.
IMO they do more harm than help in their current form. Sometimes not implementing a compromise is a feature itself.
I disagree with the premise that renaming is necessary.
http://scg.unibe.ch/archive/papers/Berg07aStatefulTraits.pdf
Let me quote that PDF you've linked me to:
"By default, variables are private to the trait that defines them. Because variables are private, CONFLICTS BETWEEN VARIABLES CANNOT OCCUR when traits are composed. If, for example, traits T1 and T2 each define a variable x, then the composition of T1 + T2 does not yield a variable conflict. Variables are only visible to the trait that defines them"
"By default, instance variables are private to their trait. If the scope of variables is not broadened at composition time using the variable access operator, CONFLICTS DO NOT OCCUR AND THE TRAITS DO NOT SHARE STATE."
Emphasis mine.
This is absolutely not what is implemented in 5.4, and this is in part why we're having this thread now.
You are missing the point of the complexity that comes with the whole proposal.
Granting access, and merging add quite some complexity to the language (not only the implementation).
There have been proposal like a 'local' keyword for traits before.
They never got implemented.
My point which I've described multiple times at this point is that traits are not classes, we already have classes in PHP, you know? Imitiating classes with traits is the wrong mental model for their behavior, and the paper you're linking me to confirms tht.
How do you prevent a user from using dynamic (properties that are not predeclared) in a method that comes from a trait?
There is nothing simple in PHP...
People don't like fatal errors, especially not for things they intend to just work.
From my example above, the kind of guarantees you expect are just not something that is part of what PHP is.Letting conflicts overwrite randomly properties is not "just working". It's an example of "not working, and it's very hard to debug why."
Again, what is your point? PHP is the wrong language for you then.
PHP is very dynamic and let's you shoot yourself in foot in all kind of strange ways.
Traits do not change these semantics. They try to integrate with them.
And the main problem here is that PHP got dynamic properties.
If you want to change the semantics to 'local-to-trait'-by-default fields, please provide a patch and an RFC. I won't stop you. On the contrary, it is certainly an appealing option.
Throwing a FATAL however is not an appealing option.
We are already pretty strict when we see that it is most likely a bug.
And we give you a E_STRICT
warning. That's what we can do for the moment.
Best regards
Stefan
--
Stefan Marr
Software Languages Lab
Vrije Universiteit Brussel
Pleinlaan 2 / B-1050 Brussels / Belgium
http://soft.vub.ac.be/~smarr
Phone: +32 2 629 2974
Fax: +32 2 629 3525
I'd like to apologize for my language. My goal isn't to insult anybody,
especially PHP contributors of major features like traits.
I hope we can talk strictly implementation issues, I think I have a good
idea how we can move this forward, I'll email you a list of behavior changes
that I think make sense next 48 hours and we can discuss it offlist to keep
the noise low here, if you'd like.
Stan
Could you please point me exactly to the paragraph where we have
something written about static properties? I do not see that I wrote
anything special about static properties in the RFC. And I do not find it
in the docs either. static properties should work like normal properties.
I'd like to add something to my answer to this question: while I've added a
doc bug for this quote I gave, people handling the documentation don't want
to fix it, because they think static members should notwork in traits,
this is how they read the RFC for some reson. Thus a clear sign people are
confused about this behavior. It has to be clarified.
Stan.
I'd like to point out some puzzling behaviors in Traits as they exist in the production releases of PHP 5.4.
Regardless of the outcome of the mail thread, can you review the traits tests and create new tests for
any behaviour not already covered?
Chris