Traits

May 30, 2017 · View on GitHub

General

Hack's class model allows single inheritance with contracts being enforced separately via interfaces. A trait can provide both implementation and contracts. Specifically, a class can inherit from a base class while getting implementation from one or more traits. At the same time, that class can implement contracts from one or more interfaces as well as from one or more traits. The use of a trait by a class does not involve any inheritance hierarchy, so unrelated classes can use the same trait. In summary, a trait is a set of methods and/or state information that can be reused.

Traits are designed to support classes; a trait cannot be instantiated directly.

The members of a trait each have visibility, which applies once they are used by a given class. The class that uses a trait can change the visibility of any of that trait's members, by either widening or narrowing that visibility. For example, a private trait member can be made public in the using class, and a public trait member can be made private in that class.

Once implementation comes from both a base class and one or more traits, name conflicts can occur. However, trait usage provides a means of disambiguating such conflicts. Names gotten from a trait can also be given aliases.

A class member with a given name overrides one with the same name in any traits that class uses, which, in turn, overrides any such name from base classes. 

Traits can contain both instance and static members, including both methods and properties. In the case of a trait with a static property, each class using that trait has its own instance of that property.

Methods in a trait have full access to all members of any class in which that trait is used.

Trait Declarations

Syntax

  trait-declaration:
   attribute-specificationopt  trait  name  generic-type-parameter-listopt  class-interface-clauseopt  {
     trait-use-clausesopt  trait-member-declarationsopt  }

  trait-use-clauses:
    trait-use-clause
    trait-use-clauses  trait-use-clause

  trait-use-clause:
    use  trait-name-list  ;

  trait-name-list:
    qualified-name  generic-type-argument-listopt
    trait-name-list  ,  qualified-name  generic-type-argument-listopt

Defined elsewhere

Constraints

The names in trait-name-list must designate trait names, excluding the name of the trait being declared.

A generic trait and a non-generic trait in the same scope cannot have the same name.

Semantics

A trait-declaration defines a named set of members, which are made available to any class that uses that trait.

Trait names are case-preserved.

A trait-declaration may also use other traits. This is done via one or more trait-use-clauses, each of which contains a comma-separated list of trait names.

The optional class-interface-clause specifies the one or more interfaces that must be implemented by any class that uses this trait.

Examples

trait T1 { public function compute( … ) : … { … } }
trait T2 { public function compute( … ) : … { … } }
trait T1 { public function sort( … ) : … { … } }
trait T4 {
  use T3;
  use T1, T2;
}

Trait Members

Syntax

  trait-member-declarations:
    trait-member-declaration
    trait-member-declarations   trait-member-declaration

  trait-member-declaration:
    require-extends-clause
    require-implements-clause
    property-declaration
    method-declaration
    constructor-declaration
    destructor-declaration

  require-extends-clause:
    require  extends  qualified-name  ;
  require-implements-clause:
    require  implements  qualified-name  ;

Defined elsewhere

Constraints

The qualified-name in require-extends-clause must designate the name of a class that does not directly use the trait being defined.

The qualified-name in require-implements-clause must designate the name of an interface that does not directly use the trait being defined.

Semantics

The members of a trait are those specified by its trait-member-declarations, and the members from any other traits it uses.

A trait may contain the following members:

  • require-extends-clauses each of which requires the class using this trait to directly or indirectly extend the class type designated by qualified-name.
  • require-implements-clauses each of which requires the class using this trait to directly or indirectly implement the interface type designated by qualified-name.
  • Properties – the variables made available to the class in which the trait is used.
  • Methods – the computations and actions that can be performed by the class in which the trait is used. See also §§.
  • Constructor – the actions required to initialize an instance of the class in which the trait is used.
  • Destructor – the actions to be performed when an instance of the class in which the trait is used is no longer needed.

trait-member-declarations may contain multiple require-extends-clauses that designate the same class, in which case, the duplicates are redundant.

trait-member-declarations may contain multiple require-implements-clauses that designate the same interface, in which case, the duplicates are redundant.

Examples

trait T {
  require extends C1;
  private int $prop1 = 1000;
  require implements I1;
  protected static int $prop2;
  public function compute( … ): void { … }
  public static function getData( … ): void { … }
}