PHP 8.0 : Class constructor property promotion
28, June 2020

Class properties are a great way to store statefull data in object. Sometimes we accept and set them via __construct(), sometime we set them via a setter, some other time we initiate them internally in the class.

When we accept class properties in __construct(), the resulting class code becomes like this:

<?php

class Car
{
  protected string $brand;
  protected string $model;
  protected int $price;

  public function __construct(string $brand, string $model, int $price){
      $this->brand = $brand;
      $this->model = $model;
      $this->price = $price;
  }
}

As we see, We had to repeat all the property names four times just to declare and initiate them as class properties. If the class is simple DTO or value object and there are more properties, class code can really be big enough to seem like clunky and duplicated. Declaring a new property in that class may be annoying task to many lazy people like me.


Welcome to Class constructor property promotion

Class constructor property promotion lets you just define properties in one place and PHP will take care of all the define and initiate tasks itself.

Above boilerplate code becomes like below code:

class Car
{
  public function __construct(
     protected string $brand,
     protected string $model,
     protected int $price
  )
  {
  }
}

$car = new Car('Ford', 'Mustang', 100000);

var_dump($car);
// object(Car)#1 (3) { ["brand":protected]=> string(4) "Ford" ["model":protected]=> string(7) "Mustang" ["price":protected]=> int(100000) }

We have declared our properties in __construct() and PHP sets them as class properties. This way, we don't need to repeat ourself, just declare properties in one place and we are done. Sometimes, Syntax sugars are so sweet ;)


Usage Details

  • To be able to promote to class properties, we need to declare them in __construct with visibility. We can use any visibility for properties. any mix of public, protected, private is allowed.
class Car {
  // we are using all public, protected, private visibility here
  public function __construct(public string $brand, protected string $model, private int $price)
  {
  }
}
  • It's not required to define type of properties. You can omit type definition or use any type (except callable)
class Player {
  // We do not need to decalre any type for the properties
  public function __construct(public $name, public $score){
  } 
}

$ronaldo = new Player('Ronaldo', 50);

echo $ronaldo->score;
// 50 
  • We can write any logic in __construct() body. We can even re-initiate the properties or do other logic.
class Car
{
  public int $price;

  public function __construct(public string $brand, public string $model)
  {
     // let's hardcode model property. You know... for fun :p
     $this->model = 'Hardcoded';
     // We can even set other properties
     $this->price = 500000;
  }
}

$car = new Car('Ford', 'Mustang', 100000);

echo $car->price;
// 500000

properties are promoted before the execution of __construct body.

class User {
    public function __construct(public $name)
    {
       // $this->name is available here
    }
}


  • We can't use promoted class properties in normal function or abstract __construct()
function promote(public $name){
  // does not work here
  // because this is not a class construct
}

abstract class AbstractUser {
    // not allowed in abstract
    abstract public function __construct(public $name);
}
// Fatal error: Cannot declare promoted property in an abstract constructor

interface UserInterface {
    // __construct in Interface is considered as abstract
    public function __construct(public $name);
}
// Fatal error: Cannot declare promoted property in an abstract constructor

We can however use promoted properties in traits. We can even overload the whole __construct in child class.

  • We can't declare class properties in __construct() if explicitly declared in class.
class User {
    public $name;

    public function __construct(public $name){}
}

// Fatal error: Cannot redeclare User::$name on line 4
  • var keyword is not allowed for property promotion. Personally I think we should avoid using this legacy keyword.
class User {
    public function __construct(var $name){}
}
// Parse error: syntax error, unexpected 'var' (T_VAR), expecting variable
  • If we want to use nullable properties, we must define them explicitly (by adding ? before type definition). Implicit nullable properties are not supported.
class User {
    // This does not work
    public function __construct(public string $name = null){}
}

class User {
    // works fine
    public function __construct(public ?string $name){}
}
  • callable type is not a valid property type, so it is not allowed for promoted class property too.
class EventHandler {
    public function __construct(public callable $event){}
}
// Fatal error: Property EventHandler::$event cannot have type callable
  • Variadic parameters cannot be promoted:
class EventHandler {
    public function __construct(public ...$events){}
}
// Fatal error: Cannot declare variadic promoted property



Reflection:

When using Reflection API, Both ReflectionProperty and ReflectionParameter classes will have a new method isPromoted() which returns true if the property was promoted via a constructor.


RFC: https://wiki.php.net/rfc/constructor_promotion