PHP 8: Продвижение свойств конструктора

PHP 8: Продвижение свойств конструктора

-

RFC по продвижению свойств конструктора прошел и будет добавлен в PHP 8 . Эта функция сократит количество стандартного кода при создании простых объектов, таких как VO и DTO.

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

Поэтому вместо этого:


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 = 'Sergey',
) {}
}

Приводится к этому:


class MyDTO
{
public string $name;

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

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

Продвигаемые свойства

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


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

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


Дубликаты не допускаются

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

Этот код вызовет ошибку:


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 = 'Sergey',
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 >= 500);

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


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

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


class MyClass 
{
public function __construct(
/** @var string */
public $a,
) {}
}


$property = new ReflectionProperty(MyClass::class, 'a');

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


Атрибуты

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


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 не поддерживается

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

Этот код будет не валиден:


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


Невозможно продвигать Вариативные свойства 


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);
}
}


Сергей Мухин

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

Есть вопросы?

Я почти всегда в режиме онлайн

Связаться со мной