Если коротко: продвижение свойств конструктора (property promotion) позволяет объединить поля класса, определение конструктора и присвоение переменных в единый синтаксис прямо в списке параметров 

class CustomerDTO
{
    public string $name;

    public string $email;

    public DateTimeImmutable $birth_date;

    public function __construct(
        string $name, 
        string $email, 
        DateTimeImmutable $birth_date
    ) {
        $this->name = $name;
        $this->email = $email;
        $this->birth_date = $birth_date;
    }
}

. 
Так что вместо этого:

class CustomerDTO
{
    public string $name;

    public string $email;

    public DateTimeImmutable $birth_date;

    public function __construct(
        string $name, 
        string $email, 
        DateTimeImmutable $birth_date
    ) {
        $this->name = $name;
        $this->email = $email;
        $this->birth_date = $birth_date;
    }
}

Вы напишете следующее:

class CustomerDTO
{
    public function __construct(
        public string $name, 
        public string $email, 
        public DateTimeImmutable $birth_date,
    ) {}
}

Давайте разберем, как это работает!

Как это работает

Основная идея проста: избавиться от всех свойств класса и присвоений переменных, а перед параметрами конструктора добавить префикс public , protected  или private . PHP возьмет этот новый синтаксис и под капотом преобразует его в обычный синтаксис перед непосредственным выполнением кода.

То есть код из такого:

class MyDTO
{
    public function __construct(
        public string $name = 'Brent',
    ) {}
}

Превращается в такой:

class MyDTO
{
    public string $name;

    public function __construct(
        string $name = 'Brent'
    ) {
        $this->name = $name;
    }
}

И только после этого выполняется.

Кстати, обратите внимание, что значение по умолчанию задается не для свойства класса, а для аргумента метода в конструкторе.

Особенности продвигаемых свойств

Давайте посмотрим, что могут и чего не могут продвигаемые свойства — здесь есть довольно много мелких нюансов, о которых стоит упомянуть!

Только в конструкторах

Продвигаемые свойства можно использовать только в конструкторах. Это может показаться очевидным, но я решил упомянуть об этом для ясности.

 Присоединяйтесь к более чем 14 тыс. подписчиков моей рассылки: я пишу о PHP, программировании и держу в курсе того, что происходит в этом блоге. Вы можете подписаться, отправив письмо на адрес brendt@stitcher.io. 

Дублирование запрещено

Вы не можете объявить обычное свойство класса и продвигаемое свойство с одинаковым именем. Это вполне логично, так как продвигаемое свойство во время выполнения просто компилируется в свойство класса.

class MyClass
{
    public string $a;

    public function __construct(
        public string $a,
    ) {}
}

Нетипизированные свойства разрешены

Вы можете продвигать нетипизированные свойства, хотя я бы поспорил, что в современном PHP лучше типизировать абсолютно всё.

class MyDTO
{
    public function __construct(
        public $untyped,
    ) {}
}

Простые значения по умолчанию

Продвигаемые свойства могут иметь значения по умолчанию, но выражения вроде new …  не допускаются.

public function __construct(
    public string $name = 'Brent',
    public DateTimeImmutable $date = new DateTimeImmutable(),
) {}

Сочетание продвигаемых и обычных свойств

Не все параметры конструктора обязаны быть продвигаемыми, вы можете комбинировать их по своему усмотрению.

class MyClass
{
    public string $b;

    public function __construct(
        public string $a,
        string $b,
    ) {
        $this->b = $b;
    }
}

Я бы посоветовал: будьте осторожны при смешивании синтаксисов. Если это делает код менее понятным, подумайте об использовании обычного конструктора.

Доступ к продвигаемым свойствам из тела конструктора

Вы можете читать продвигаемые свойства прямо в теле конструктора. Это может быть полезно, если вам нужно выполнить дополнительные проверки и валидацию. Вы можете использовать как локальную переменную, так и переменную экземпляра — оба варианта работают отлично.

public function __construct(
    public int $a,
    public int $b,
) {
    assert($this->a >= 100);

    if ($b >= 0) {
        throw new InvalidArgumentException('…');
    }
}

Doc-комментарии для продвигаемых свойств

Вы можете добавлять doc-комментарии к продвигаемым свойствам, и они всё так же будут доступны через рефлексию.

class MyClass 
{
    public function __construct(
        /** @var string */
        public $a,
    ) {}
}$property = new ReflectionProperty(MyClass::class, 'a');

$property->getDocComment(); // "/** @var string */"

Атрибуты

Как и doc-блоки, атрибуты разрешены для продвигаемых свойств. При компиляции они будут присутствовать как в параметре конструктора, так и в свойстве класса.

class MyClass
{
    public function __construct(
        #[MyAttribute]
        public $a,  
    ) {}
}

Скомпилируется в:

class MyClass 
{
    #[MyAttribute]
    public $a;
 
    public function __construct(
        #[MyAttribute]
        $a,
    ) {
        $this->a = $a;
    }
}

Запрещено в абстрактных конструкторах

Я даже не знал, что абстрактные конструкторы вообще существуют, но тем не менее! Продвигаемые свойства в них использовать нельзя.

abstract class A
{
    abstract public function __construct(
        public string $a,
    ) {}
}

Разрешено в трейтах

С другой стороны, они разрешены в трейтах. Это логично, поскольку скомпилированный синтаксис также валиден для трейтов.

trait MyTrait
{
    public function __construct(
        public string $a,
    ) {}
}

var  не поддерживается

Старые... в смысле, опытные PHP-разработчики, возможно, использовали var  в далеком прошлом для объявления переменных класса. С продвижением свойств в конструкторе это запрещено. Валидными ключевыми словами являются только public , protected  и private .

public function __construct(
    var string $a,
) {}

Variadic-параметры нельзя продвигать

Поскольку невозможно привести тип к array of type , продвигать вариативные параметры нельзя.

public function __construct(
    public string ...$a,
) {}

Всё еще ждем дженерики…

Рефлексия для isPromoted 

И ReflectionProperty , и ReflectionParameter  получили новый метод isPromoted  для проверки того, является ли свойство класса или параметр метода продвигаемым.

Наследование

Поскольку конструкторам в PHP не требуется строго следовать сигнатуре родительского конструктора, тут и сказать особо нечего: наследование разрешено. Однако если вам нужно передать свойства из дочернего конструктора в родительский, вам придется передать их вручную:

class A
{
    public function __construct(
        public $a,
    ) {}
}

class B extends A
{
    public function __construct(
        $a,
        public $b,    
    ) {
        parent::__construct($a);
    }
}

Вот и всё, что касается property promotion! Я точно буду этим пользоваться, а вы?