Quick Links

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.

Reflection

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.

Conclusion

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.