Constructor Property Promotion
Before PHP 8.0, every class property that a constructor needed to fill in had to be written out three separate times: once as a property declaration at the top of the class, once as a constructor parameter, and once as an assignment line inside the constructor body copying the parameter into the property. For a class with five or six simple properties, that ritual added a lot of repetitive lines that carried no real logic — just plumbing. Constructor property promotion, introduced in PHP 8.0, lets you collapse all three of those steps into a single parameter written directly in the constructor's signature.
The old way: three steps per property
To see exactly what promotion removes, it helps to look at the pre-8.0 style in full. Here is a small Product class written the traditional way, with property declarations, constructor parameters, and manual assignments all spelled out separately.
Before: property, parameter, and assignment written separately
<?php
class Product
{
public string $name;
public float $price;
public int $quantity;
public function __construct(string $name, float $price, int $quantity)
{
$this->name = $name;
$this->price = $price;
$this->quantity = $quantity;
}
}
$item = new Product('Keyboard', 49.99, 3);
echo "{$item->name}: {$item->quantity} x \${$item->price}\n";Keyboard: 3 x $49.99
Nothing here is wrong, but notice how mechanical it is: the type and
name appear on the property line, then again on the parameter line,
then a third time on the assignment line. Adding a fourth property
means adding a line in three different places, and it's easy to
typo one of the three and introduce a silent bug — for example
assigning $this->price = $quantity by mistake.
The new way: promoted parameters
Constructor promotion lets you add a visibility keyword (public,
protected, or private) directly in front of a constructor
parameter. When PHP sees that visibility keyword, it automatically
declares a property of the same name and type, and automatically
assigns the incoming argument to it — you never write $this->name = $name at all.
After: the same class with promoted properties
<?php
class Product
{
public function __construct(
public string $name,
public float $price,
public int $quantity,
) {
}
}
$item = new Product('Keyboard', 49.99, 3);
echo "{$item->name}: {$item->quantity} x \${$item->price}\n";Keyboard: 3 x $49.99
Both classes behave identically from the outside — $item->name,
$item->price, and $item->quantity are ordinary public properties
in either version. The promoted version simply skipped writing the
property declarations and the three assignment lines, cutting the
class down to essentially just its constructor signature. The
constructor body is empty here because there was no assignment logic
left to write; if you needed extra setup — validation, for instance —
you'd still put that inside the braces.
Mixing promoted and non-promoted parameters
You are not required to promote every parameter. A constructor can mix promoted parameters (with a visibility keyword) and ordinary parameters (without one) that are only used for local logic and never stored as properties.
Promoting some parameters, computing with others
<?php
class Invoice
{
public float $total;
public function __construct(
public string $customer,
float $subtotal,
float $taxRate,
) {
$this->total = $subtotal + ($subtotal * $taxRate);
}
}
$invoice = new Invoice('Acme Co.', 200.00, 0.08);
echo "{$invoice->customer} owes \${$invoice->total}\n";Acme Co. owes $216
$customer is promoted straight to a property, while $subtotal and $taxRate exist only long enough to compute $total inside the constructor body — they are never stored. This mix is common in real code: some constructor inputs map 1-to-1 onto stored state, others are just ingredients for a calculation.
Combining promotion with readonly
PHP 8.1 added the readonly modifier, which can be placed right alongside a promoted property's visibility keyword. A readonly property can be set exactly once — during construction — and any later attempt to write to it throws an error. This pairs naturally with promotion because promoted properties are usually meant to be the object's fixed identity, set once and never mutated.
Promoted properties that are also readonly
<?php
class Coordinate
{
public function __construct(
public readonly float $latitude,
public readonly float $longitude,
) {
}
}
$point = new Coordinate(43.6532, -79.3832);
echo "{$point->latitude}, {$point->longitude}\n";
$point->latitude = 0.0; // fatal error: cannot modify a readonly property43.6532, -79.3832 PHP Fatal error: Uncaught Error: Cannot modify readonly property Coordinate::$latitude
Coordinate objects are effectively immutable value objects in four lines: no separate property block, no manual assignments, and no way to accidentally mutate a coordinate after it's created. This is arguably the single most common use of promotion in modern PHP code — small, immutable data-carrying classes such as DTOs (data transfer objects), value objects, and event payloads.
Default values on promoted parameters
Promoted parameters accept default values exactly like ordinary parameters do. This lets a class provide sensible fallbacks without any extra code in the constructor body.
Promoted parameters with defaults
<?php
class Connection
{
public function __construct(
public string $host,
public int $port = 5432,
public bool $useSsl = true,
) {
}
}
$default = new Connection('db.internal');
$custom = new Connection('legacy.internal', 5433, false);
echo "{$default->host}:{$default->port} ssl=" . var_export($default->useSsl, true) . "\n";
echo "{$custom->host}:{$custom->port} ssl=" . var_export($custom->useSsl, true) . "\n";db.internal:5432 ssl=true legacy.internal:5433 ssl=false
$default only supplies the host, so $port and $useSsl fall back to 5432 and true. This is identical in spirit to default values on any PHP function parameter — promotion doesn't change how defaults work, it just means the parameter and the property share the same default.
A visibility keyword (
public,protected, orprivate) in front of a constructor parameter promotes it: PHP declares the property and assigns it for you.Promoted and ordinary parameters can be mixed in the same constructor — only promoted ones become stored properties.
readonlycan be combined with promotion (public readonly string $name) to get an immutable property with almost no boilerplate.Promoted parameters accept default values exactly like normal function parameters do.
Promotion is exclusive to
__construct()— it cannot be used on any other method.