PHP 8.0 — Constructor property promotion
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.
readonlyproperties cannot have default values in PHP 8.1 (but PHP 8.2+ allowsreadonlyon 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
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