0

PHP 8.0 — Constructor property promotion

Beginner5 min read·php-09-007

Concept

Constructor property promotion (PHP 8.0) was covered in detail in php-07-003. This lesson covers the edge cases, restrictions, and interaction with other PHP 8 features.

What promotion compiles to: The engine generates: a property declaration with the declared type and default, a constructor parameter with the same type and default, and an assignment statement $this->name = $name. It's identical to writing all three manually.

Restrictions:

  • Only in __construct, not other methods.
  • Cannot combine with a separate property declaration for the same name.
  • Callable/static default values are not allowed for promoted properties (same restriction as property defaults).
  • Abstract constructors (in abstract classes) cannot use promotion.
  • readonly properties cannot have default values in PHP 8.1 (but PHP 8.2+ allows readonly on promoted with a default if it's a constant expression).

PHP 8.4 property hooks + promotion: In PHP 8.4, you can combine promoted properties with hooks: public string $name { set => ucfirst($value) } in the constructor. This is new and particularly powerful for validation and normalization at construction time.

Common antipattern: Using promotion for everything including dependencies that require setup. If a constructor parameter requires complex initialization before being stored, use a regular parameter and assign manually.

Code Example

php
<?php
declare(strict_types=1);

// Basic promotion — the common case
class Point
{
    public function __construct(
        public readonly float $x,
        public readonly float $y,
        public float $z = 0.0,
    ) {}
}

// Mixing promoted and regular (when setup is needed)
class DatabaseConnection
{
    private \PDO $pdo; // NOT promoted — we need to configure it

    public function __construct(
        public readonly string $dsn,   // promoted — stored directly
        string $username,              // NOT promoted — used to create PDO below
        string $password,
    ) {
        $this->pdo = new \PDO($dsn, $username, $password, [
            \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
        ]);
        // $username/$password not stored — promoted would store them (security risk)
    }
}

// PHP 8.4: promotion + property hooks
class User84
{
    public function __construct(
        public string $name {
            set => ucfirst(trim($value))  // normalize on every set
        },
        public string $email {
            set {
                if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
                    throw new \InvalidArgumentException("Invalid email: $value");
                }
                $this->email = strtolower($value);
            }
        },
    ) {}
}

$user = new User84('  alice ', 'Alice@Example.COM');
echo $user->name;  // 'Alice' (trimmed, ucfirst)
echo $user->email; // 'alice@example.com' (lowercased)

// readonly with promotion — write-once after construction
class ImmutableConfig
{
    public function __construct(
        public readonly string $env,
        public readonly bool   $debug = false,
        public readonly int    $workers = 1,
    ) {}
}
$cfg = new ImmutableConfig(env: 'production', workers: 4);
// $cfg->env = 'staging'; // Fatal Error: Cannot modify readonly property