Skip to main content

Inheritance

Hack supports single inheritance between classes.

class IntBox {
public function __construct(protected int $value) {}

public function get(): int {
return $this->value;
}
}

class MutableIntBox extends IntBox {
public function set(int $value): void {
$this->value = $value;
}
}

Visibility Modifiers​

Every property and method in a class has a visibility modifier that controls where it can be accessed.

public​

A public member can be accessed from anywhere: inside the class, in subclasses, and from outside code.

class IntBox {
public int $value = 0;

public function get(): int {
return $this->value;
}
}

function example(IntBox $box): void {
$box->value = 42; // OK: public property
$box->get(); // OK: public method
}

Use public for the external API of your class.

protected​

A protected member can be accessed inside the class and in any subclass, but not from outside code.

class Base {
protected int $value = 0;

protected function getValue(): int {
return $this->value;
}
}

class Child extends Base {
public function getDoubled(): int {
return $this->getValue() * 2; // OK: subclass can access protected members
}
}

function example(Base $b): void {
$b->value; // Error: cannot access protected property
$b->getValue(); // Error: cannot access protected method
}

Use protected for implementation details that subclasses need to access or override.

private​

A private member can only be accessed inside the class that defines it. Subclasses cannot access it.

class Counter {
private int $count = 0;

public function increment(): void {
$this->count++;
}

public function get(): int {
return $this->count;
}
}

class DoubleCounter extends Counter {
<<__Override>>
public function increment(): void {
$this->count++; // Error: cannot access private property from subclass
}
}

Use private when a member is an internal implementation detail that subclasses should not depend on.

internal​

The internal modifier restricts access to the same module. An internal member can be accessed by any code in the same module, but not from outside it.


class MyService {
internal function helper(): void {}
}

See Using internal for full details.

protected internal​

A protected internal member requires the caller to satisfy both constraints: the caller must be in a subclass of the declaring class and in the same module.

class Base {
protected internal function helper(): void {}
}

// Same module, subclass: OK
class Child extends Base {
<<__Override>>
public function helper(): void {
parent::helper(); // OK: subclass + same module
}
}

// Same module, not a subclass: Error (protected)
// Different module, subclass: Error (internal)
// Different module, not a subclass: Error (both)

The keyword order does not matter — internal protected is also accepted. This is the only multi-modifier visibility combination allowed in Hack.

Constructor Promoted Properties​

In constructor parameter promotion, the visibility modifier also declares the property.

class IntBox {
public function __construct(protected int $value) {}
// Equivalent to declaring `protected int $value;` and assigning in the constructor
}

Overriding Methods​

You can override methods in subclasses by defining a method with the same name.

class IntBox {
public function __construct(protected int $value) {}

public function get(): int {
return $this->value;
}
}

class IncrementedIntBox extends IntBox {
<<__Override>>
public function get(): int {
return $this->value + 1;
}
}

If a method is intended to override a method in a parent class, you should annotate it with <<__Override>>. This has no runtime effect, but ensures you get a type error if the parent method is removed.

Hack does not support method overloading. Subclasses methods must have a return type, parameters and visibility that is compatible with the parent class.

class NumBox {
public function __construct(protected num $value) {}

protected function get(): num {
return $this->value;
}
}

class FloatBox extends NumBox {
<<__Override>>
public function get(): float {
return (float)$this->value;
}
}

The only exception is constructors, which may have incompatible signatures with the parent class. You can use <<__ConsistentConstruct>> to require subclasses to have compatible types.

class User {
// This constructor takes one argument.
public function __construct(protected string $name) {}
}

class Player extends User {
// But this constructor takes two arguments.
<<__Override>>
public function __construct(protected int $score, string $name) {
parent::__construct($name);
}
}

Visibility Overriding Rules​

When overriding a method in a subclass, you can make it more visible but not less visible. For example, a protected method can be overridden as public, but a public method cannot be overridden as protected.

Private members are not subject to these rules. Since private members are not visible to subclasses, a subclass can define a member with the same name — this is a new, independent definition, not an override. The <<__Override>> attribute should not be used in this case.

class NumBox {
public function __construct(protected num $value) {}

protected function get(): num {
return $this->value;
}
}

class FloatBox extends NumBox {
<<__Override>>
public function get(): float { // OK: widening from protected to public
return (float)$this->value;
}
}

Calling Overridden Methods​

You can use parent:: to call an overridden method in the parent class.

class IntBox {
public function __construct(protected int $value) {}

public function get(): int {
return $this->value;
}
}

class IncrementedIntBox extends IntBox {
<<__Override>>
public function get(): int {
return parent::get() + 1;
}
}

This also works for static methods.

class MyParent {
public static function foo(): int {
return 0;
}
}

class MyChild extends MyParent {
<<__Override>>
public static function foo(): int {
return parent::foo() + 1;
}
}

Abstract Classes​

An abstract class cannot be instantiated. Attempting to create an instance of an abstract class or call an unimplemented abstract method causes a runtime error. The type checker helps prevent these errors. Given the following code:

abstract class Animal {
public abstract function greet(): string;
}

class Dog extends Animal {
<<__Override>>
public function greet(): string {
return "woof!";
}
}

new Dog() is allowed but not new Animal().

Abstract classes are similar to interfaces, but they can include implementations of methods.

Static Methods and Late Static Binding​

Abstract classes can have abstract static methods. When you use static:: inside a method, late static binding determines which class's method runs based on the runtime class.

This creates a potential problem: what if someone calls your method in a context where static refers to an abstract class?

abstract class Animal {
public static function introduce(): void {
echo "I say: ";
static::speak(); // Error: static might be abstract at runtime
}

public static abstract function speak(): void;
}

class Dog extends Animal {
public static function speak(): void {
echo "Woof!";
}
}

When you call Dog::introduce(), static::speak() successfully resolves to Dog::speak(). But if someone calls Animal::introduce() directly, static::speak() calls an abstract method, causing a runtime error.

The type checker tries to prevent such problems. To fix the error, use the <<__NeedsConcrete>> attribute:

abstract class Animal {
<<__NeedsConcrete>>
public static function introduce(): void {
echo "I say: ";
static::speak(); // OK: attribute indicates `static` should resolve to a concrete class
}

public static abstract function speak(): void;
}

This tells the type checker that introduce() should only be called when the runtime class is concrete. See the __NeedsConcrete documentation for more details.

Final Classes​

A final class cannot have subclasses.

final class Dog {
public function greet(): string {
return "woof!";
}
}

If your class has subclasses, but you want to prevent additional subclasses, use <<__Sealed>>.

If you want to inherit from a final class for testing, use <<__MockClass>>.

You can also combine final and abstract on classes. This produces a class that cannot be instantiated or have subclasses. The class is effectively a namespace of grouped functionality.

abstract final class Example {
public static function callMe(int $i): int {
return static::helper($i);
}

private static function helper(int $i): int {
return $i + 1;
}
}

Final Methods​

A final method cannot be overridden in subclasses.

class IntBox {
public function __construct(protected int $value) {}

final public function get(): int {
return $this->value;
}
}