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);
}
}
Что думаешь?