PHPMiddleTechnical
Как PHP 8 обрабатывает constructor property promotion?
Constructor Property Promotion (PHP 8.0+) позволяет объявлять свойства прямо в сигнатуре конструктора через модификаторы public/protected/private. В PHP 8.1 сочетается с readonly для неизменяемых Value Objects.
Что такое Constructor Property Promotion
Constructor Property Promotion (CPP) — синтаксический сахар PHP 8.0, позволяющий объявлять и инициализировать свойства класса прямо в сигнатуре конструктора. Вместо трёх шагов (объявление свойства, параметр конструктора, присваивание) пишется одна строка.
До PHP 8.0 vs после
<?php
declare(strict_types=1);
// PHP 7.x — verbose boilerplate
class UserDTO
{
public string $name;
public string $email;
private int $age;
public function __construct(string $name, string $email, int $age)
{
$this->name = $name;
$this->email = $email;
$this->age = $age;
}
}
// PHP 8.0+ — constructor property promotion
class UserDTO
{
public function __construct(
public string $name,
public string $email,
private int $age,
) {}
}
Поддерживаемые модификаторы
В параметре конструктора можно указывать все те же модификаторы доступа, что и у обычных свойств:
public,protected,privatereadonly(PHP 8.1+) — свойство можно записать только один раз- Типы: примитивы, union types, nullable, intersection types
<?php
declare(strict_types=1);
// PHP 8.1 — readonly + CPP = идеальные Value Objects
final class Money
{
public function __construct(
public readonly int $amount,
public readonly string $currency,
) {
if ($amount < 0) {
throw new \InvalidArgumentException('Amount cannot be negative');
}
}
}
$price = new Money(1000, 'USD');
echo $price->amount; // 1000
$price->amount = 500; // Error: Cannot modify readonly property
Смешивание promoted и обычных свойств
<?php
declare(strict_types=1);
class OrderService
{
// Это свойство объявлено обычным способом (нет модификатора в конструкторе)
private array $cache = [];
public function __construct(
private readonly UserRepository $users, // promoted
private readonly OrderRepository $orders, // promoted
private readonly LoggerInterface $logger, // promoted
) {
// Обычный код конструктора по-прежнему работает
$this->logger->info('OrderService initialized');
}
}
Работа с атрибутами PHP 8.x
<?php
use Symfony\Component\Validator\Constraints as Assert;
class CreateUserRequest
{
public function __construct(
#[Assert\NotBlank]
#[Assert\Length(min: 2, max: 50)]
public readonly string $name,
#[Assert\Email]
public readonly string $email,
) {}
}
Атрибуты применяются к промотированному свойству, а не только к параметру — это важно для фреймворков вроде Symfony, которые читают атрибуты через Reflection.
Подводные камни
- Promoted свойства нельзя инициализировать дефолтными значениями-объектами. Значение по умолчанию должно быть скалярным или
null. Нельзя написатьpublic array $tags = new ArrayObject()— это синтаксическая ошибка. - Порядок выполнения. Промотирование происходит до тела конструктора: свойство уже установлено в момент входа в тело
__construct. Это означает, что валидация в теле может обращаться к уже инициализированным свойствам. - Readonly и клонирование. Клон
readonly-объекта нельзя изменить в__clone()начиная с PHP 8.1. PHP 8.3 добавилclone withдля создания изменённых копий Value Objects. - Serialization и promoted свойства.
var_export()иserialize()работают корректно, но некоторые кастомные сериализаторы могут не видеть promoted свойства через Reflection, если не используютReflectionProperty::isPromoted(). - Inheritance. Дочерний класс не наследует promoted свойства автоматически — нужно явно вызывать
parent::__construct(...)с нужными аргументами, иначе свойства родителя не будут инициализированы. - Перегрузка в тестах. Promoted + readonly затрудняет создание test doubles без реального конструктора. Используйте интерфейсы или
Mockery::mock()вместо частичных моков. - Анализаторы и IDE. PHPStan и Psalm полностью поддерживают CPP с PHP 8.0. Старые версии (PHPStan < 0.12) могут давать ложные срабатывания на promoted свойства.
Common mistakes
- Сводить constructor property promotion к названию метода без lifecycle и failure path.
- Игнорировать модель runtime: интерпретируемый runtime PHP 8.x, обычно запускаемый как отдельный запрос в FPM, CLI или worker-процессе.
- Не отделять validation, authorization, transaction boundary и business logic.
What the interviewer is testing
- Объясняет constructor property promotion через конкретную точку lifecycle в PHP.
- Приводит корректный минимальный пример без вымышленных методов или callbacks.
- Называет edge cases: пустые значения, ошибки, транзакции, безопасность или concurrency.