Конструктор и продвижение свойств
В прошлом уроке каждый новый объект получал начальные значения из класса: счётчик всегда начинался с 0. Но многое нельзя знать заранее. У каждого пользователя своё имя, и задать его нужно в тот момент, когда объект появляется.
Вернёмся к картинке с домами. Строители не начинают работу с пустыми руками: им сразу дают заказ — сколько этажей и какого цвета стены. По этому заказу дом сразу строят таким, каким он должен быть.
Метод __construct
В PHP такой заказ принимает конструктор. Конструктор — метод с именем __construct, который PHP вызывает сам, когда new строит объект. Аргументы из new User('Anna') попадают в его параметры:
<?php
class User
{
public string $name;
public function __construct(string $name)
{
$this->name = $name;
}
}
$anna = new User('Anna');
echo $anna->name, "\n"; // AnnaРазберём по шагам:
public string $name;объявляет свойство без начального значения: имя придёт снаружи.new User('Anna')строит объект и сразу вызывает его__constructсо строкой'Anna'.- Внутри конструктора
$this— новый объект, и$this->name = $name;записывает имя в его свойство. - Готовый объект попадает в
$anna, иechoпечатаетAnna.
Два подчёркивания в начале имени отмечают методы, которые PHP вызывает сам. Писать $anna->__construct('Anna') не нужно: конструктор срабатывает один раз, при каждом new.
Продвижение свойств
Посмотрите, сколько раз в классе User встречается имя: в объявлении свойства, в параметре и в присваивании. Для каждого нового свойства пришлось бы писать три строки. С PHP 8.0 всё это делает одна запись:
<?php
class User
{
public function __construct(
public string $name,
public int $level = 1,
) {}
}
$anna = new User('Anna');
$boris = new User('Boris', 3);
echo $anna->name, ' ', $anna->level, "\n"; // Anna 1
echo $boris->name, ' ', $boris->level, "\n"; // Boris 3Продвижение свойств — запись, в которой слово public перед параметром конструктора объявляет свойство и присваивает ему аргумент. Давайте разберём:
public string $nameсоздаёт свойство$nameи кладёт в него первый аргументnew.public int $level = 1работает так же, но у параметра есть значение по умолчанию:new User('Anna')получит уровень1.- Тело конструктора пустое,
{}: всю работу сделало продвижение. Если тело есть, оно выполняется уже после записи свойств. - У каждого объекта свои значения, поэтому программа печатает
Anna 1иBoris 3.
Вместо public можно поставить private или protected: тогда свойство закроется от кода снаружи класса. Эти три слова называют модификаторами видимости. Типом может быть и имя класса: параметр private Logger $log объявит свойство, в котором лежит объект класса Logger.
На собеседовании спрашивают, что делает продвижение свойств. Ответ:
- модификатор у параметра конструктора объявляет свойство и присваивает ему аргумент за один шаг;
- строка
$this->name = $name;больше не нужна; - получается обычное свойство, своё у каждого объекта, с типом и значением по умолчанию, если их указать.
Частая ошибка
Свойство объявили без начального значения и забыли задать в конструкторе:
<?php
class Order
{
public int $total;
}
$order = new Order();
echo $order->total, "\n"; // ✗Fatal error: Uncaught Error: Typed property Order::$total must not be accessed before initializationЭто ошибка Error: программа останавливается на этой строке. У типизированного свойства без значения особое состояние «не задано», и это не null. Здесь картинка с домом не работает: недостроенный дом можно хотя бы осмотреть, а незаданное свойство PHP прочитать не даст.
Исправление — дать свойству начальное значение, public int $total = 0;, или записать его в конструкторе.
Главное
- Конструктор
__constructвызывается сам при каждомnewи получает аргументы изnew User('Anna'). - Продвижение свойств:
public string $nameв параметрах конструктора объявляет свойство и присваивает ему аргумент. - У продвинутого параметра может быть значение по умолчанию:
public int $level = 1. - Свойство с типом без значения читать нельзя: это
Error, а неnull.