I’m very happy with the constructor property promotion RFC, it’s passed and will be added in PHP 8. You see, this feature reduces a lot of boilerplate code when constructing simple objects such as VOs and DTOs.
In short: property promotion allows you to combine class fields, constructor definition, and variable assignments all into one syntax, in the construct parameter list.
So instead of doing this:
class CustomerDTO
{
public string $name;
public string $email;
public DateTimeImmutable $birth_date;
public function __construct(
string $name,
string $email,
DateTimeImmutable $birth_date
) {
$this->name = $name;
$this->email = $email;
$this->birth_date = $birth_date;
}
}
You would write this:
class CustomerDTO
{
public function __construct(
public string $name,
public string $email,
public DateTimeImmutable $birth_date,
) {}
}
Let’s look at how it works!
How it works
The basic idea is simple: ditch all the class properties and the variable assignments, and prefix the constructor parameters with public
, protected
or private
. PHP will take that new syntax, and transform it to normal syntax under the hood, before actually executing the code.
So it goes from this:
class MyDTO
{
public function __construct(
public string $name = 'Teach',
) {}
}
To this:
class MyDTO
{
public string $name;
public function __construct(
string $name = 'Teach'
) {
$this->name = $name;
}
}
And only executes it afterward.
Note by the way that the default value is not set on the class property, but on the method argument in the constructor.
Promoted property properties
So let’s look at what promoted properties can and can’t do, there are quite a lot of little intricacies worth mentioning!
Only in constructors
Promoted properties can only be used in constructors. That might seem obvious but I thought it was worth mentioning this, just to be clear.
No duplicates allowed
You’re not able to declare a class property and a promoted property with the same name. That’s also rather logical, since the promoted property is simply transpired to a class property at runtime.
class MyClass
{
public string $a;
public function __construct(
public string $a,
) {}
}
Untyped properties are allowed
You’re allowed to promote untyped properties.
class MyDTO
{
public function __construct(
public $untyped,
) {}
}
Simple defaults
Promoted properties can have default values, but expressions like new …
these are not allowed.
public function __construct(
public string $name = 'Teach',
public DateTimeImmutable $date = new DateTimeImmutable(),
) {}
Combining promoted- and normal properties
Not all constructor properties should be promoted, you can mix and match.
class MyClass
{
public string $b;
public function __construct(
public string $a,
string $b,
) {
$this->b = $b;
}
}
I’d say: be careful mixing the syntaxes, if it makes the code less clear, consider using a normal constructor instead.
Access promoted properties from the constructor body
You’re allowed to read the promoted properties in the constructor body. This can be useful if you want to do extra validation checks. You can use both the local variable and the instance variable, both work fine.
public function __construct(
public int $a,
public int $b,
) {
assert($this->a >= 100);
if ($b >= 0) {
throw new InvalidArgumentException('…');
}
}
Doc comments on promoted properties
You can add doc comments on promoted properties, and they are still available via reflection.
class MyClass
{
public function __construct(
/** @var string */
public $a,
) {}
}
$property = new ReflectionProperty(MyClass::class, 'a');
$property->getDocComment(); // "/** @var string */"
Attributes
Just like doc blocks, attributes are allowed on promoted properties. When transpired, they will be present both on the constructor parameter, as well as the class property.
class MyClass
{
public function __construct(
#[MyAttribute]
public $a,
) {}
}
Will be transpiled to:
class MyClass
{
#[MyAttribute]
public $a;
public function __construct(
#[MyAttribute]
$a,
) {
$this->a = $a;
}
}
Not allowed in abstract constructors
I didn’t even know abstract constructors were a thing, but here goes! Promoted properties are not allowed in them.
abstract class A
{
abstract public function __construct(
public string $a,
) {}
}
Allowed in traits
On the other hand, they are allowed in traits. This makes sense since the transpiled syntax is also valid in traits.
trait MyTrait
{
public function __construct(
public string $a,
) {}
}
var
is not supported
Old, I mean, experienced PHP developers might have used var
in a distant past to declare class variables. It’s not allowed with constructor promotion. Only public
, protected
and private
are valid keywords.
public function __construct(
var string $a,
) {}
Variadic parameters cannot be promoted
Since you can’t convert to a type that’s, it’s not possible to promote variadic parameters.
public function __construct(
public string ...$a,
) {}
Still waiting for generics…
Reflection for isPromoted
Both ReflectionProperty
and ReflectionParameter
have a new isPromoted
method to check whether the class property or method parameter is promoted.
Inheritance
Since PHP constructors don’t need to follow the declaration of their parent constructor, there’s little to be said: inheritance is allowed. If you need to pass properties from the child constructor to the parent constructor though, you’ll need to manually pass them:
class A
{
public function __construct(
public $a,
) {}
}
class B extends A
{
public function __construct(
$a,
public $b,
) {
parent::__construct($a);
}
}