PHPConstructor Property Promotion

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
<?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
<?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
<?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
<?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 property
43.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
<?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.

Promotion only works in the constructor
The visibility-keyword-on-a-parameter shorthand is special-cased for `__construct()` only. You cannot promote a parameter on a regular method — writing `public function setName(public string $name)` on an ordinary method is a syntax error. If you need similar behavior elsewhere, you still have to declare the property and assign it by hand.
  • A visibility keyword (public, protected, or private) 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.

  • readonly can 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.

Tip
Reach for promotion by default whenever a constructor parameter's only job is to become a property with no extra validation or transformation. The moment you need to validate, normalize, or derive a value before storing it, drop back to a non-promoted parameter plus an explicit assignment (or a small setter call) inside the constructor body — don't force validation logic to live awkwardly around a promoted parameter list.