0

Constructor promotion (PHP 8.0+)

Beginner5 min read·php-07-003
compare

Concept

Constructor promotion (PHP 8.0) eliminates the three-part boilerplate of: (1) declare property, (2) add constructor parameter, (3) assign in constructor body. Instead, you prefix the constructor parameter with a visibility modifier (public, protected, or private), and PHP automatically does all three.

The feature is purely syntactic sugar — the compiled result is identical to writing out all three steps. There are no performance differences. It dramatically reduces boilerplate for Data Transfer Objects (DTOs), Value Objects, and simple models.

Rules and limitations:

  • Can only be used in __construct, not in other methods.
  • Parameters with promotion cannot have separate property declarations.
  • readonly works with promotion: public readonly string $name in the constructor.
  • Default values are allowed: public string $role = 'user'.
  • Non-promoted parameters can be mixed with promoted ones in the same constructor.
  • Cannot promote abstract constructor parameters.
  • Promoted properties can use property hooks (PHP 8.4): public string $slug { set => slugify($value) } combined with promotion.

When NOT to use it: When __construct needs to do something non-trivial with the parameter before storing it (validation, transformation). In that case, use a regular parameter and assign to a property after your logic.

Code Example

php
<?php
declare(strict_types=1);

// Before PHP 8.0 — lots of boilerplate
class OldPoint
{
    public float $x;
    public float $y;
    public string $label;

    public function __construct(float $x, float $y, string $label = 'point')
    {
        $this->x     = $x;
        $this->y     = $y;
        $this->label = $label;
    }
}

// PHP 8.0+ — constructor promotion
class Point
{
    public function __construct(
        public readonly float $x,
        public readonly float $y,
        public string $label = 'point',
    ) {}
}

$p = new Point(1.0, 2.0, 'origin');
echo $p->x;     // 1.0
echo $p->label; // 'origin'

// Mixed promoted and non-promoted
class HttpRequest
{
    public readonly string $fingerprint;

    public function __construct(
        public readonly string $method,
        public readonly string $uri,
        public readonly array  $headers = [],
        string $body = '', // NOT promoted — we process it below
    ) {
        $this->fingerprint = md5($method . $uri . $body);
    }
}

// DTO with promotion
class CreateUserDto
{
    public function __construct(
        public readonly string $name,
        public readonly string $email,
        public readonly string $password,
        public readonly string $role = 'user',
    ) {}
}

// Instantiate directly from array
$data = ['name' => 'Alice', 'email' => 'alice@ex.com', 'password' => 'secret'];
$dto  = new CreateUserDto(...$data); // spread operator + named args

// Laravel Eloquent models DON'T use constructor promotion
// (they use $fillable and mass-assignment protection)
// But service/command objects are perfect candidates