Other Features: Trait And Interface Requirements

Traits provide a method of code reuse. A trait can define properties and methods, and those definitions can refer to properties and methods that the enclosing class must define. The reliance on what the enclosing class is often implicit, or specified through a cumbersome series of abstract method requirements.

Interfaces provide a way to encapsulate common operations that will be implemented by classes that implement the interface.

Trait and interface requirements allow you to restrict the use of these constructs by specifying what classes may actually use a trait or implement an interface. This can simplify long list of abstract method requirements, and provide a hint to the reader as to the intended use of the trait or interface.

Syntax

To introduce a trait requirement, you can have one or more of these in your trait:

require extends <class name>;
require implements <interface name>;

To introduce an interface requirement, you can have one or more of these in your interface:

require extends <class name>;

Traits

Here is an example of a trait introducing a class and interface requirement and shows a class that meets the requirement.

<?hh

namespace Hack\UserDocumentation\OtherFeatures\TIR\Examples\TraitReqGood;

abstract class Machine {
  public function openDoors(): void {
    return;
  }
  public function closeDoors(): void {
    return;
  }
}
interface Fliers {
  public function fly(): bool;
}

trait Plane {
  require extends Machine;
  require implements Fliers;

  public function takeOff(): bool {
    $this->openDoors();
    $this->closeDoors();
    return $this->fly();
  }
}

class AirBus extends Machine implements Fliers {
  use Plane;

  public function fly(): bool {
    return true;
  }
}

function run(): void {
  $ab = new AirBus();
  var_dump($ab);
  var_dump($ab->takeOff());
}

run();
Output
object(Hack\UserDocumentation\OtherFeatures\TIR\Examples\TraitReqGood\AirBus)#1 (0) {
}
bool(true)

Here is an example of a trait introducing a class and interface requirement and shows a class that does not meet the requirement.

<?hh

namespace Hack\UserDocumentation\OtherFeatures\TIR\Examples\TraitReqBad;

abstract class Machine {
  public function openDoors(): void {
    return;
  }
  public function closeDoors(): void {
    return;
  }
}
interface Fliers {
  public function fly(): bool;
}

trait Plane {
  require extends Machine;
  require implements Fliers;

  public function takeOff(): bool {
    $this->openDoors();
    $this->closeDoors();
    return $this->fly();
  }
}

// Having this will not only cause a typechecker error, but also cause a fatal
// error in HHVM since we did not meet the trait requirement.
class Paper implements Fliers {
  use Plane;

  public function fly(): bool {
    return false;
  }
}

function run(): void {
  // This code will actually not run in HHVM because of the fatal mentioned
  // above.
  $p = new Paper();
  var_dump($p);
  var_dump($p->takeOff());
}

run();
Output
Fatal error: Class 'Hack\UserDocumentation\OtherFeatures\TIR\Examples\TraitReqBad\Paper' required to extend class 'Hack\UserDocumentation\OtherFeatures\TIR\Examples\TraitReqBad\Machine' by trait 'Hack\UserDocumentation\OtherFeatures\TIR\Examples\TraitReqBad\Plane' in /data/users/joelm/user-documentation/guides/hack/47-other-features/03-trait-and-interface-requirements-examples/trait-bad.php.type-errors on line 30

NOTE: require extends should be taken literally. The class must extend the required class; thus the actual required class does not meet that requirement. This is to avoid some subtle circular dependencies when checking requirements.

Interfaces

Here is an example of an interface introducing a class requirement and shows a class that meets the requirement.

<?hh

namespace Hack\UserDocumentation\OtherFeatures\TIR\Examples\InterfaceReqGood;

abstract class Machine {
  public function openDoors(): void {
    return;
  }
  public function closeDoors(): void {
    return;
  }
}
interface Fliers {
  require extends Machine;
  public function fly(): bool;
}

class AirBus extends Machine implements Fliers {
  public function takeOff(): bool {
    $this->openDoors();
    $this->closeDoors();
    return $this->fly();
  }

  public function fly(): bool {
    return true;
  }
}

function run(): void {
  $ab = new AirBus();
  var_dump($ab);
  var_dump($ab->takeOff());
}

run();
Output
object(Hack\UserDocumentation\OtherFeatures\TIR\Examples\InterfaceReqGood\AirBus)#1 (0) {
}
bool(true)

Here is an example of an interface introducing a class requirement and shows a class that does not meet the requirement.

<?hh

namespace Hack\UserDocumentation\OtherFeatures\TIR\Examples\InterfaceReqBad;

abstract class Machine {
  public function openDoors(): void {
    return;
  }
  public function closeDoors(): void {
    return;
  }
}
interface Fliers {
  require extends Machine;
  public function fly(): bool;
}

// Having this will not only cause a typechecker error, but also cause a fatal
// error in HHVM since we did not meet the interface requirement (extending
// Machine).
class Paper implements Fliers {
  public function fly(): bool {
    return false;
  }
}

function run(): void {
  // This code will actually not run in HHVM because of the fatal mentioned
  // above.
  $p = new Paper();
  var_dump($p);
  var_dump($p->takeOff());
}

run();
Output
Fatal error: Class 'Hack\UserDocumentation\OtherFeatures\TIR\Examples\InterfaceReqBad\Paper' required to extend class 'Hack\UserDocumentation\OtherFeatures\TIR\Examples\InterfaceReqBad\Machine' by interface 'Hack\UserDocumentation\OtherFeatures\TIR\Examples\InterfaceReqBad\Fliers' in /data/users/joelm/user-documentation/guides/hack/47-other-features/03-trait-and-interface-requirements-examples/interface-bad.php.type-errors on line 21