Other Features: Constructor Parameter Promotion

If you have created a class in Hack or PHP, you probably have seen a pattern like this:

<?hh

namespace Hack\UserDocumentation\OtherFeatures\CPP\Examples\Duplication;

class Users {
  public static array<User> $users = array();
}

class User {
  private int $id;
  private string $name;
  private bool $preferred;

  public function __construct(int $id, string $name, bool $preferred) {
    $this->id = $id;
    $this->name = $name;
    $this->preferred = $preferred;

    // Put user at the beginning of the database if preferred.
    if ($this->preferred) {
      array_unshift(Users::$users, $this);
    } else {
      Users::$users[] = $this;
    }
  }
}

function run(): void {
  $u1 = new User(1, 'Joel', false);
  $u2 = new User(2, 'Fred', true);
  $u3 = new User(3, "Sam", false);
  $u4 = new User(4, 'Matthew', true);
  var_dump(Users::$users);
}

run();
Output
array(4) {
  [0]=>
  object(Hack\UserDocumentation\OtherFeatures\CPP\Examples\Duplication\User)#4 (3) {
    ["id":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Duplication\User":private]=>
    int(4)
    ["name":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Duplication\User":private]=>
    string(7) "Matthew"
    ["preferred":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Duplication\User":private]=>
    bool(true)
  }
  [1]=>
  object(Hack\UserDocumentation\OtherFeatures\CPP\Examples\Duplication\User)#2 (3) {
    ["id":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Duplication\User":private]=>
    int(2)
    ["name":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Duplication\User":private]=>
    string(4) "Fred"
    ["preferred":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Duplication\User":private]=>
    bool(true)
  }
  [2]=>
  object(Hack\UserDocumentation\OtherFeatures\CPP\Examples\Duplication\User)#1 (3) {
    ["id":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Duplication\User":private]=>
    int(1)
    ["name":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Duplication\User":private]=>
    string(4) "Joel"
    ["preferred":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Duplication\User":private]=>
    bool(false)
  }
  [3]=>
  object(Hack\UserDocumentation\OtherFeatures\CPP\Examples\Duplication\User)#3 (3) {
    ["id":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Duplication\User":private]=>
    int(3)
    ["name":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Duplication\User":private]=>
    string(3) "Sam"
    ["preferred":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Duplication\User":private]=>
    bool(false)
  }
}

Code like this repeats the class properties multiple times: at declaration, in the constructor parameters, and in the assignment. This can be quite cumbersome.

With constructor parameter promotion, all that repetitive boilerplate is removed.

<?hh

namespace Hack\UserDocumentation\OtherFeatures\CPP\Examples\Promotion;

class Users {
  public static array<User> $users = array();
}

class User {
  public function __construct(private int $id, private string $name,
                              private bool $preferred) {
    // The constructor parameter promotion assignments are done before the code
    // below is run. That's why we can use $this->preferred, for example

    // Put user at the beginning of the database if preferred.
    if ($this->preferred) {
      array_unshift(Users::$users, $this);
    } else {
      Users::$users[] = $this;
    }
  }
}

function run(): void {
  $u1 = new User(1, 'Joel', false);
  $u2 = new User(2, 'Fred', true);
  $u3 = new User(3, "Sam", false);
  $u4 = new User(4, 'Matthew', true);
  var_dump(Users::$users);
}

run();
Output
array(4) {
  [0]=>
  object(Hack\UserDocumentation\OtherFeatures\CPP\Examples\Promotion\User)#4 (3) {
    ["id":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Promotion\User":private]=>
    int(4)
    ["name":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Promotion\User":private]=>
    string(7) "Matthew"
    ["preferred":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Promotion\User":private]=>
    bool(true)
  }
  [1]=>
  object(Hack\UserDocumentation\OtherFeatures\CPP\Examples\Promotion\User)#2 (3) {
    ["id":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Promotion\User":private]=>
    int(2)
    ["name":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Promotion\User":private]=>
    string(4) "Fred"
    ["preferred":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Promotion\User":private]=>
    bool(true)
  }
  [2]=>
  object(Hack\UserDocumentation\OtherFeatures\CPP\Examples\Promotion\User)#1 (3) {
    ["id":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Promotion\User":private]=>
    int(1)
    ["name":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Promotion\User":private]=>
    string(4) "Joel"
    ["preferred":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Promotion\User":private]=>
    bool(false)
  }
  [3]=>
  object(Hack\UserDocumentation\OtherFeatures\CPP\Examples\Promotion\User)#3 (3) {
    ["id":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Promotion\User":private]=>
    int(3)
    ["name":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Promotion\User":private]=>
    string(3) "Sam"
    ["preferred":"Hack\UserDocumentation\OtherFeatures\CPP\Examples\Promotion\User":private]=>
    bool(false)
  }
}

All you do is put a modifier in front of the constructor parameter name and everything else in the previous example is done automatically, including the actual creation of the property.

Rules

  • A modifier of private, protected or public must precede the parameter name in the constructor.
  • Other, non-class property parameters may also exist in the constructor, as normal.
  • You can put type annotations between the modifier and the name.
  • The parameters can have default values.
  • Other code in the constructor is run after the parameter promotion assignment.