Larry Garfield пишет отличную серию статей о том, что из себя представляет PHP 8: о скорости, атрибутах, свойствах в конструкторе и многом другом. Сегодня посмотрим на его статью о объявляемых в конструкторе свойствах.
https://platform.sh/blog/2020/php-80-feature-focus-constructor-property-promotion/
Возможно, наиболее существенное улучшение PHP 8, в плане удобства разработки, это объявление свойств в конструкторе. Синтаксис в основном заимствован из Hack — PHP-форка от Facebook, но похожий синтаксис используется и в других языках, например TypeScript или Kotlin.
Сразу приведу простой пример. Рассмотрим типичный класс в современном приложении:
class FormRenderer
{
private ThemeSystem $theme;
private UserRepository $users;
private ModuleSystem $modules;
public function __construct(ThemeSystem $theme, UserRepository $users, ModuleSystem $modules) {
$this->theme = $theme;
$this->users = $users;
$this->modules = $modules;
}
public function renderForm(Form $form): string
{
// ...
}
}
Класс имеет 3 зависимости. Название переменной для каждой зависимости повторяется 4 раза: при описании аргумента конструктора, при описании свойства класса, при описании ссылки на свойство внутри конструктора и при инициализации этой ссылки. Получается очень громоздко, и хотя большинство IDE решают такие задачи авто-подстановкой, это всё ещё боль. Я знаю, что многие люди избегают инъекцию зависимостей из-за громоздкого описания.
В PHP 8 вышеописанную запись сократили до:
<?php
class FormRenderer
{
public function __construct(
private ThemeSystem $theme,
private UserRepository $users,
private ModuleSystem $modules,
) { }
public function renderForm(Form $form): string
{
// ...
}
}
Перемещение области видимости свойства в конструктор говорит PHP «Этот параметр — свойство, ты должен назначить ему значение». Эффект во время выполнения точно такой же, как в первом примере, но для такой записи нам потребовалось в 4 раза меньше текста.
Тем не менее, мы всё ещё можем описывать параметры по-старинке, и конструктор, конечно, всё ещё может иметь тело конструктора. В этом примере тело конструктора — пустое, но если нам требуется сделать ещё что-то, помимо инъекции зависимостей, мы можем сделать это в теле конструктора, и это будет выполнено после инициализации свойств:
<?php
class FormRenderer
{
private User $currentUser;
public function __construct(
private ThemeSystem $theme,
private UserRepository $users,
private ModuleSystem $modules,
) {
$this->currentUser = $this->users->getActiveUser();
}
public function renderForm(Form $form): string
{
// ...
}
}
Но на практике, большинство классов, которые я писал за последние несколько лет — имели конструктор, где не было ничего кроме инициализации.
Объявление свойств в конструкторе удобно использовать для простых объектов. Простые объекты — это объекты с некоторым количеством свойств и геттеров/сеттеров для этих свойств. Для таких объектов весь класс можно свести к конструктору:
<?php
Class MailMessage
{
public function __construct(
public string $to,
public string $subject,
public string $from,
public string $body,
public array $cc,
public array $bcc,
public string $attachmentPath,
) {}
}
Для внутреннего (не являющегося частью API) объекта public-свойства вполне могут использоваться. Если же объект является частью API или взаимодействует с какой-либо внешней системой, то свойства нужно инкапсулировать, и добавить методы для работы с ними. В любом случае код станет компактнее и удобнее.
При объявлении свойств в конструкторе нужно учитывать несколько моментов:
- Определение области видимости применимо только к конструкторам, использование их в любой другой функции приведёт к ошибке;
- Свойство не может быть объявлено как в конструкторе, так и за его пределами.
Спасибо Никите Попову за этот RFC, хотя поводом послужила моя запись в начале года в этом блоге, так что я требую себе 0.1% 🙂
Мы уже в плотную подошли к релизу PHP 8 и вот-вот сможем увидеть на практике что из себя представляет объявление свойств в конструкторе.