Constructor Property Promotion

Yo Yogesh.M Updated 17 Oct 2026
6 min read ·Lesson 60 of 248
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 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 User-defined Functions Slicing Strings Function Arguments 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 AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention Garbage Collection Project: Simple Blog PHP What is OOP PHP Classes/Objects The $this Keyword Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning MySQL Database MySQL Connect MySQL Create DB MySQL Create Table MySQL Insert Data MySQL Get Last ID MySQL Insert Multiple MySQL Prepared MySQL Select Data MySQL Where Debugging PHP Project: Image Upload Gallery $_SESSION $_ENV PHP RegEx RegEx Patterns Heredoc and Nowdoc RegEx Modifiers preg_match preg_replace preg_split PHP RegEx Functions Nullable Types Mixed and Never Types Nullsafe Operator PHP Attributes PHP Fibers Readonly Classes Property Hooks (PHP 8.4) Asymmetric Visibility (PHP 8.4) Reflection API SPL Data Structures Composer PHP Form Handling PDO Transactions PDO Error Handling PHP XML Parsers PHP SimpleXML Parser PHP SimpleXML - Get PHP XML Expat Parser PHP DOM Parser PHP XMLReader PHP XMLWriter AJAX Intro PHP Class Constants Encryption with Sodium Recursive Functions Variable Functions Anonymous Functions Closures Arrow Functions First-class Callable Syntax PHP Arrays Indexed Arrays Associative Arrays Create Arrays Variables Scope Static Variables Variable Variables PHP Echo / Print PHP printf / sprintf PHP Data Types PHP Booleans PHP NULL PHP Strings String Functions array_map / array_filter / array_reduce Array Functions PHP Superglobals $GLOBALS What Is `$_SERVER` ? $_REQUEST $_POST $_GET $_FILES $_COOKIE PHP Type Juggling MySQL Order By MySQL Delete Data MySQL Update Data MySQL Limit Data MySQL Joins with PHP Pagination with PHP PDO Introduction PDO Connect PDO Prepared Statements PDO Fetch Modes PSR-4 Autoloading Installing Extensions (PECL / PIE) OPcache & Performance PHP-FPM Project: Contact Form with Email Project: Login & Registration System Project: CRUD App with MySQL Project: Shopping Cart with Sessions Project: REST API with PHP DateTime Class Timezones PHP Include Include vs Require PHP File Handling PHP File Open/Read PHP File Create/Write File Delete / Rename / Copy PHP Directories PHP CSV Files
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.