PHP Logo

Constructor property promotion is a new PHP 8 convenience feature which helps you minimise code repetition. It lets you combine the definition and initialisation of properties into a single constructor statement.

A Traditional Class

Constructor property promotion (CPP from hereon) is most useful in the context of value objects. These tend to be simple classes which describe a data structure. Here’s what one might look like in PHP 7.4:

class BlogPost {
    protected string $Title;
    protected string $Content;
    protected DateTimeImmutable $PostedDate;
    public function __construct(
        string $Title,
        string $Content,
        DateTimeImmutable $PostedDate) {
        $this -> Title = $Title;
        $this -> Content = $Content;
        $this -> PostedDate = $PostedDate;

We’ve created a reasonably lengthy class, despite having only three properties! Each property name occurs four times, with the typehints being written twice. If you needed to amend one of the properties, or add another, you’d be touching three lines of code.

Adding Constructor Property Promotion

Here’s the same example, rewritten to use CPP in PHP 8:

class BlogPost {
    public function __construct(
        protected string $Title,
        protected string $Content,
        protected DateTimeImmutable $PostedDate) {}

By using CPP we’ve drastically cut the size of our class. You’d only need to add or alter one line of code when working with this class’ properties.

A Closer Look

CPP combines property definition with the constructor’s parameter list. To create a promoted property, prefix its name with a class visibility modifier – public, protected or private.

Once promoted, the property behaves like any other class property. You can access it within the class using $this -> Property or externally (if it’s a public property). Internally, PHP’s simply transforming the compact syntax into the much lengthier version supported by PHP 7.

You don’t have to use typehints with promoted properties – it’s acceptable to write protected $Title, for example. You can set default values using the same syntax as a regular constructor parameter (protected string $Title = "Example Post").

Using With a Constructor Body

Your constructor won’t always be quite as simple as our example. You might need to perform some validation checks or transform a value.

You can still write a constructor body when using CPP. You’ll be able to access the values of your promoted properties either as the promoted instance variable or using the local variable:

public function __construct(protected string $Title) {
    if (!$this -> Title) {
        throw new InvalidArgumentException("Title cannot be empty.");
    // OR
    if (!$Title) {
        throw new InvalidArgumentException("Title cannot be empty.");

Whichever approach you choose, the variable or the property, you’ll be working with the same value. PHP simply hides the $this -> Title = $Title line you’d normally be writing manually.

Combining with Regular Properties and Parameters

You can freely mix promoted properties with regular property definitions. In addition, you can combine promoted and non-promoted properties in your constructor parameters.

class BlogPost {
    public ?DateTimeImmutable $PostedDate = null;
    public function __construct(public string $Title, bool $isPosted=true) {
        if ($isPosted) $this -> PostedDate = new DateTimeImmutable();

You should be careful when mixing these syntaxes together. Promoted properties can easily be overlooked when skimming the code, especially if preceded by regular class property definitions. If your class already possesses several properties using the traditional form, using the new syntax for one extra addition might not be the best approach for that file.

Use with Attributes

One of PHP 8’s other new features was attributes. These allow you to annotate extra metadata against entities in your codebase.

Attributes are fully supported on promoted properties. As it’s ambiguous whether the attribute applies to a property definition or a method parameter, PHP will apply it to both.

That means you’ll get the same value back whether you’re inspecting the property or the constructor parameter. This flexible approach ensures you don’t lose any functionality of attributes by adopting constructor property promotion.


Promoted properties – and constructor arguments which promote a property – behave intuitively when introspected via the Reflection APIs. Reflection looks at promoted properties in their post-transpilation state, so they appear identical to an explicitly declared property.

Both the ReflectionProperty and ReflectionParameter classes have new isPromoted() methods to let you check whether they were involved with CPP. Generally, this shouldn’t need to be considered though, unless you’re writing a tool which wants to replicate the exact source structure.

Possible Gotchas

You cannot declare duplicate class and promoted property names. The following example would result in a runtime error:

class BlogPost {
    public string $Title;
    public function __construct(public string $Title) {}

This is because PHP doesn’t really do anything special with promoted property definitions. It simply transpiles your terse source code into the PHP 7.4 style. This would result in two public string $Title lines, which has always been forbidden.

There are other cases where CPP might result in an error. Promoted properties are forbidden in abstract classes, although they can be used in traits. You’re unable to use the callable type with them because it’s not supported for property definitions. Finally, there’s no support for variadic parameters (e.g. public string ...$strings) because the property would end up being an array of string instances, not a string itself.


Constructor property promotion makes it much quicker to write new value objects in your codebase. It can cut the number of lines by up to two thirds! CPP also helps you DRY (Don’t Repeat Yourself) your code by avoiding the unsightly repetition of typehints and property names which PHP 7.4 required.

Adopting CPP is optional and won’t always make sense for existing classes. It’s best suited to simple struct-like classes, where you want to provide strong typing to data that’s going to be passed through your codebase.

Profile Photo for James Walker James Walker
James Walker is a contributor to How-To Geek DevOps. He is the founder of Heron Web, a UK-based digital agency providing bespoke software development services to SMEs. He has experience managing complete end-to-end web development workflows, using technologies including Linux, GitLab, Docker, and Kubernetes.
Read Full Bio »