Перейти к содержимому
Теория · Классы и объекты · урок 2 из 11

Конструктор и продвижение свойств

В прошлом уроке каждый новый объект получал начальные значения из класса: счётчик всегда начинался с 0. Но многое нельзя знать заранее. У каждого пользователя своё имя, и задать его нужно в тот момент, когда объект появляется.

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

Метод __construct

В PHP такой заказ принимает конструктор. Конструктор — метод с именем __construct, который PHP вызывает сам, когда new строит объект. Аргументы из new User('Anna') попадают в его параметры:

PHP
<?php

class User
{
    public string $name;

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

$anna = new User('Anna');
echo $anna->name, "\n"; // Anna

Разберём по шагам:

  1. public string $name; объявляет свойство без начального значения: имя придёт снаружи.
  2. new User('Anna') строит объект и сразу вызывает его __construct со строкой 'Anna'.
  3. Внутри конструктора $this — новый объект, и $this->name = $name; записывает имя в его свойство.
  4. Готовый объект попадает в $anna, и echo печатает Anna.

Два подчёркивания в начале имени отмечают методы, которые PHP вызывает сам. Писать $anna->__construct('Anna') не нужно: конструктор срабатывает один раз, при каждом new.

Продвижение свойств

Посмотрите, сколько раз в классе User встречается имя: в объявлении свойства, в параметре и в присваивании. Для каждого нового свойства пришлось бы писать три строки. С PHP 8.0 всё это делает одна запись:

PHP
<?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 перед параметром конструктора объявляет свойство и присваивает ему аргумент. Давайте разберём:

  1. public string $name создаёт свойство $name и кладёт в него первый аргумент new.
  2. public int $level = 1 работает так же, но у параметра есть значение по умолчанию: new User('Anna') получит уровень 1.
  3. Тело конструктора пустое, {}: всю работу сделало продвижение. Если тело есть, оно выполняется уже после записи свойств.
  4. У каждого объекта свои значения, поэтому программа печатает Anna 1 и Boris 3.

Вместо public можно поставить private или protected: тогда свойство закроется от кода снаружи класса. Эти три слова называют модификаторами видимости. Типом может быть и имя класса: параметр private Logger $log объявит свойство, в котором лежит объект класса Logger.

На собеседовании спрашивают, что делает продвижение свойств. Ответ:

  • модификатор у параметра конструктора объявляет свойство и присваивает ему аргумент за один шаг;
  • строка $this->name = $name; больше не нужна;
  • получается обычное свойство, своё у каждого объекта, с типом и значением по умолчанию, если их указать.

Частая ошибка

Свойство объявили без начального значения и забыли задать в конструкторе:

PHP
<?php

class Order
{
    public int $total;
}

$order = new Order();
echo $order->total, "\n"; // ✗
TEXT
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.