Constructor Property Promotion

Yo Yogesh.M Updated 17 Oct 2026
6 min read ·Lesson 61 of 109
In this post

What Is Constructor Property Promotion? In simple terms: Many constructors do nothing except copy their parameters into properties with the same names. Constructor property promotion (PHP 8.0+) lets y

What Is Constructor Property Promotion?

In simple terms: Many constructors do nothing except copy their parameters into properties with the same names. Constructor property promotion (PHP 8.0+) lets you declare the property, the parameter, and the assignment in one place, by adding a visibility keyword in front of the constructor parameter.

Technically: A constructor parameter preceded by a visibility modifier (public, protected, private), optionally with readonly, is a promoted parameter. PHP automatically declares a property with the same name and type, and assigns the argument to it before the constructor body runs.

The Problem It Solves

Before PHP 8.0, a simple class repeated every name three times:

class Customer
{
    private string $name;
    private string $email;
    private string $city;

    public function __construct(string $name, string $email, string $city)
    {
        $this->name = $name;
        $this->email = $email;
        $this->city = $city;
    }
}

With promotion:

class Customer
{
    public function __construct(
        private string $name,
        private string $email,
        private string $city,
    ) {}
}

Both classes are identical in behavior: the same private, typed properties, assigned from the same constructor arguments. The second version has a third of the code and no chance of a mismatch between declaration and assignment.


Syntax and Behavior

public function __construct(
    visibility [readonly] Type $name [= default],
    ...
) {
    // optional body: runs after promoted properties are assigned
}

Example

<?php
class Point
{
    public function __construct(
        public float $x = 0.0,
        public float $y = 0.0,
    ) {}

    public function distanceTo(Point $other): float
    {
        return sqrt(($other->x - $this->x) ** 2 + ($other->y - $this->y) ** 2);
    }
}

$origin = new Point();
$target = new Point(3, 4);

var_dump($target);
echo $origin->distanceTo($target) . "\n";

Output

object(Point)#2 (2) {
  ["x"]=>
  float(3)
  ["y"]=>
  float(4)
}
5

How It Works

  1. public float $x = 0.0 in the parameter list declares a public float property $x and a constructor parameter $x with the default 0.0.
  2. When the object is created, PHP assigns each argument to its property before running the (here empty) constructor body.
  3. The integer arguments 3 and 4 are converted to floats, exactly as for a declared float property.
  4. var_dump() shows that real properties were created. (#2 is the object's ID: $origin was object #1.)

Mixing Promoted and Regular Parameters

Not every parameter has to be promoted. Regular parameters are useful for values that are only needed to calculate properties. The constructor body can validate promoted values and set other properties:

<?php
class Product
{
    private string $slug;

    public function __construct(
        public readonly string $sku,
        private string $name,
        private int $pricePaise,
        string $category = 'general',
    ) {
        if ($pricePaise <= 0) {
            throw new InvalidArgumentException('Price must be positive.');
        }

        $this->slug = $category . '/' . strtolower(str_replace(' ', '-', $name));
    }

    public function slug(): string
    {
        return $this->slug;
    }

    public function price(): string
    {
        return '₹' . number_format($this->pricePaise / 100, 2);
    }
}

$lamp = new Product('LAMP-01', 'Desk Lamp', 129900, 'home');

echo $lamp->sku . "\n";
echo $lamp->slug() . "\n";
echo $lamp->price() . "\n";

try {
    new Product('PEN-01', 'Gel Pen', 0);
} catch (InvalidArgumentException $e) {
    echo "Error: " . $e->getMessage() . "\n";
}

Output

LAMP-01
home/desk-lamp
₹1,299.00
Error: Price must be positive.

Points to Notice

  • $sku, $name, and $pricePaise are promoted; $category is a plain parameter used only to build the slug and is not stored as a property.
  • The constructor body runs after promotion, so $pricePaise is already assigned when it is validated. The body uses the parameter variable $pricePaise (also available as $this->pricePaise).
  • If validation fails, the exception prevents the object from being created, even though the property had already been assigned internally.
  • public readonly promotion creates a property that can be read publicly but never changed (see Readonly Properties).

Readonly Data Objects

Promotion is especially convenient for classes that simply carry data — data transfer objects (DTOs) and value objects:

<?php
final class CreateOrderRequest
{
    public function __construct(
        public readonly int $customerId,
        public readonly array $items,
        public readonly ?string $couponCode = null,
        public readonly string $paymentMethod = 'upi',
    ) {}
}

$request = new CreateOrderRequest(
    customerId: 7,
    items: [['sku' => 'PEN-01', 'qty' => 3]],
    paymentMethod: 'card',
);

echo "Customer {$request->customerId} pays by {$request->paymentMethod}";
echo $request->couponCode === null ? ", no coupon\n" : ", coupon {$request->couponCode}\n";

Output

Customer 7 pays by card, no coupon

Combined with named arguments, the call reads almost like a configuration file, and each value is clearly labeled. Readonly properties guarantee that the request cannot be changed after it has been validated and created.


Rules and Restrictions

RuleError If Violated
Promotion is only allowed in constructorsCompile error
Not allowed in abstract constructors or interfacesCannot declare promoted property in an abstract constructor
A promoted property must not also be declared separatelyCannot redeclare ClassName::$property
Variadic parameters cannot be promotedCannot declare variadic promoted property
The type callable is not allowed for propertiesProperty ClassName::$property cannot have type callable (use Closure instead)
A nullable parameter type must be written explicitly?string $x = null, not string $x = null

Defaults Belong to the Parameter

A default value like public float $x = 0.0 is a parameter default: it applies when the argument is omitted. The property itself has no default; it is assigned when the constructor runs. This matters only in rare situations (for example, when objects are created without calling the constructor, as some serialization libraries do).

Attributes and Doc Comments

Attributes placed on a promoted parameter apply to both the parameter and the property, which is how validation and serialization libraries read rules from them:

public function __construct(
    #[Assert\Email]
    public readonly string $email,
) {}

Formatting Conventions

For readability, the PER Coding Style recommends:

  • one promoted parameter per line,
  • a trailing comma after the last parameter,
  • an empty body written as {} on the same line as the closing parenthesis.
public function __construct(
    private Mailer $mailer,
    private Logger $logger,
    private int $retryLimit = 3,
) {}

When to Use Promotion

Use PromotionUse Regular Properties
Values passed in and stored unchangedProperties computed from several inputs
Dependencies (services)Properties with complex initialization
DTOs and value objectsProperties not set through the constructor at all
Simple validation in the bodyClasses where explicit declarations aid documentation

Mixing both styles in one class is normal: promote what comes in directly, declare the rest normally.


Common Mistakes

Forgetting the Visibility Keyword

Effect: __construct(string $name) is a plain parameter; no property is created, and $this->name is undefined (or creates a deprecated dynamic property).

Fix: Add private, protected, or public.

Declaring the Property Twice

Effect: Compile error "Cannot redeclare".

Fix: Either promote or declare, not both.

Assigning in the Body Again

Effect: $this->name = $name; repeats what promotion already did.

Fix: Remove the redundant assignment, or assign a normalized value intentionally (for example, $this->name = trim($name); — but then consider not promoting that parameter).

Promoting Everything Publicly

Effect: Data can be changed from outside without validation.

Fix: Prefer private or public readonly.


Best Practices

  • Use promotion for constructor parameters that are stored as-is.
  • Prefer private or public readonly visibility for promoted properties.
  • Keep validation in the constructor body; it runs after promotion.
  • Put one parameter per line with a trailing comma.
  • Combine promotion with named arguments to create readable value objects and DTOs.

Official reference: https://www.php.net/manual/en/language.oop5.decon.php#language.oop5.decon.constructor.promotion

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Home PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else Slicing Strings User-defined Functions Slicing Strings PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators
Ask about this post
AI Ask about this post

Ask questions about Constructor Property Promotion and get answers drawn from it.

Signed-in readers only.