PHP 8.1: оператор new в инициализаторах
В PHP 8.1 добавлена интересная функция, которая на первый взгляд может показаться мелочью, но я думаю, что она будет оказывать значительное повседневное влияние на многих людей.
Так в чем же польза от оператора new в инициализаторах? Давайте посмотрим на пример, раньше мы все писали такой код:
class MyStateMachine
{
public function __construct(
private ?State $state = null,
) {
$this->state ??= new InitialState();
}
}
В этом примере с конечным автоматом мы хотели бы построить наш класс двумя способами: с начальным состоянием и без него. Если мы построим его без начального состояния, мы хотим, чтобы было установлено состояние по умолчанию. PHP, конечно, поддерживает установку начальных значений непосредственно в списке параметров, но только для примитивных типов. Например, если бы наш конечный автомат использовал строки вместо объектов внутри, мы могли бы написать его конструктор следующим образом:
class MyStateMachine
{
public function __construct(
private string $state = 'initial',
) {
}
}
Таким образом, с PHP 8.1 мы можем использовать тот же синтаксис «значения по умолчанию» и для объектов. Другими словами: вы можете использовать new для аргументов по умолчанию (которые являются одним из примеров «инициализаторов»):
class MyStateMachine
{
public function __construct(
private State $state = new InitialState(),
) {
}
}
«Инициализаторы» - это больше, чем значения параметров по умолчанию, вот простое объяснение из RFC:
Этот RFC предлагает разрешить использование новых выражений внутри значений параметров по умолчанию, аргументов атрибутов, инициализаторов статических переменных и инициализаторов глобальных констант.
Да, вы правильно прочитали: атрибуты тоже есть в этом списке! Представьте себе простую библиотеку проверки, которая использует атрибуты для проверки ввода свойств. Возможно, она должна иметь возможность проверять элементы массива, примерно так:
class CreateEmailsRequest extends FormRequestData
{
#[ValidArray(
email: [new Required, new ValidEmail],
name: [new Required, new ValidString],
)]
public array $people;
}
До PHP 8.1 вы не могли писать такой код, потому что вам не разрешалось использовать new в атрибутах из-за способа их оценки, но теперь вы можете!
Давайте взглянем на некоторые важные детали, о которых стоит упомянуть.
Создание только при необходимости
Такого рода «новые значения» будут создаваться только тогда, когда это действительно необходимо. Это означает, что в нашем первом примере PHP создаст только новый объект, если InitialState не содержит аргументов:
class MyStateMachine
{
public function __construct(
private State $state = new InitialState(),
) {
}
}
new MyStateMachine(new DraftState()); // InitialState не создается
new MyStateMachine(); // Сейчас создается
В случае атрибутов, например, объекты будут созданы только при newInstance вызове атрибута отражения.
Не в свойствах класса
Также вы должны знать, что не можете использовать new значение по умолчанию в свойствах класса. Поддержка этой функции привела бы к множеству непредвиденных побочных эффектов, например, при сериализации и десериализации объектов:
class MyStateMachine
{
private State $state = new InitialState();
}
Сначала кажется, что это большое упущение, но к счастью, это решается с помощью объявления свойства в конструкторе, которые допускают значение по умолчанию, поскольку PHP будет переносить синтаксис объявления свойств, сохраняя значение по умолчанию в аргументе конструктора, но не фактически в свойстве:
class MyStateMachine
{
private State $state;
public function __construct(
State $state = new InitialState(),
) {
$this->state = $state;
}
}
Ограниченный ввод
Возможно, вы уже догадались, но вы можете передать только ограниченный набор входных данных при создании новых объектов в инициализаторах. Например, вы не можете использовать переменные, оператор распаковки, анонимные классы и т.д.
Что думаешь?