user-documentation
user-documentation copied to clipboard
Clarify trait semantics
-
you can define a constructor in a trait
-
trait T implements IFoo and trait T { require implements IFoo; } are both legal, but have slightly different meanings
-
You can call T::someStaticMethod() directly today (but this should hopefully be banned soon)
-
You can define abstract methods in traits
-
You can use __ConsistentConstruct on a trait, and it's different to a class
- private methods in traits are only copied into using classes if the class doesn't have one. effectively this means a class can override a trait private method.
- every class using a trait gets its own copy of the trait's static properties
- but only one copy, if the trait is inherited more than once: trait T1{} trait T2{use T1;} trait T3{use T1}; class C{use T2,T3};
- in
class C{use T1,T2}, if T1+T2 define conflicting abstract methods, T1 wins... but if either T1 or T2 has a concrete method, that one wins. finally, if C has the method as well, C wins. - in the same example, if T1+T2 define conflicting concrete methods, it's an error unless C has the method.
but have slightly different meanings
what are the two meanings?
T::someStaticMethod()
what happens if someStaticMethod() contains language constructs (e.g. parent or access to static properties) that only work in the context of a using class?
private methods in traits are only copied into using classes if the class doesn't have one.
HH typechecks this with override semantics - C's concrete method must have a compatible signature with the private method in T. hhvm doesn't care (T's private method is simply hidden).
__CLASS__ in a trait is rewritten to the class using the trait.
T::somestaticMethod()
what happens if someStaticMethod() contains language constructs (e.g. parent or access to static properties) that only work in the context of a using class?
It throws an exception/fatals. This is why we need to ban it :)
what are the two meanings?
Given two traits:
interface IHasFoo {
public function foo(): void;
}
trait FooWithRequire {
require implements IHasFoo;
}
trait FooWithImplements implements IHasFoo {}
Using them:
class ExampleOne implements IHasFoo {
use FooWithRequire;
public function foo(): void {}
}
class ExampleTwo {
use FooWithImplements;
public function foo(): void {}
}
ExampleOne is required to write implements IHasFoo, whereas ExampleTwo implicitly gets implements IHasFoo from using the trait.
https://fburl.com/1810mh57 has some additional discussion.
IIUC, both ExampleOne and ExampleTwo end up identical -- they both have the same bases, interfaces, and methods, correct? That said, I agree there is value to only allowing ExampleOne style.
both ExampleOne and ExampleTwo end up identical
Yep, the only difference is that you're required to write implements IHasFoo for ExampleOne. At runtime they should be the same.