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.