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;
    }
}Ограниченный ввод
Возможно, вы уже догадались, но вы можете передать только ограниченный набор входных данных при создании новых объектов в инициализаторах. Например, вы не можете использовать переменные, оператор распаковки, анонимные классы и т.д.
 
                         
                             
                                 
                                 
                                 
                        
 
                        
Что думаешь?