Поиск по сайту:

Как использовать свойства только для чтения в PHP 8.1


В PHP 8.1 добавлена поддержка модификатора readonly для свойств класса. Свойство, помеченное таким образом, может быть установлено только один раз. Попытка изменить значение свойства только для чтения после инициализации вызовет ошибку.

«Только для чтения» — довольно расплывчатый термин, имеющий разные значения в отдельных языках программирования. В этом контексте «только для чтения» на самом деле означает «неизменный» — вы можете установить значение свойства, но впоследствии его нельзя будет изменить.

Запись свойств только для чтения

Добавьте модификатор readonly, чтобы сделать свойство доступным только для чтения. Он должен быть помещен между модификатором доступа к свойству и его подсказкой.

class Demo {
 
    public string $Mutable;
 
    public readonly string $Immutable;
 
    public function __construct(
        string $Mutable,
        string $Immutable) {
 
        $this -> Mutable = $Mutable;
        $this -> Immutable = $Immutable;
    }
 
}

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

$demo = new Demo("A", "X");
$demo -> Mutable = "B";

$Immutable немного отличается. Вы по-прежнему можете прочитать его значение, когда захотите, но любые изменения завершатся с ошибкой Error:

// Throws an Error
$demo -> Immutable = "Y";

Модификатор readonly также поддерживается в продвигаемых свойствах конструктора:

class Demo {
    public function __construct(
        public readonly string $Immutable="foobar"
    ) {}
}

Вы по-прежнему можете установить это свойство вручную в своем конструкторе, когда используете продвижение со значением по умолчанию. Значение по умолчанию получает параметр функции, а не экземпляр свойства. Повышение уровня сахара приводит к тому же коду, что и в предыдущем примере.

Предостережения

Свойства только для чтения — это прямое усовершенствование синтаксиса, которое вы можете использовать на досуге. Нет последствий для обратной совместимости, и их использование не является обязательным. Если вы начнете их добавлять, следует помнить о нескольких предостережениях и ограничениях.

В отличие от обычных свойств, свойства только для чтения не могут иметь значение по умолчанию в своем определении:

class Demo {
    protected readonly string $foo = "bar";
}

Этот оператор определяет и инициализирует $foo. Вы не можете изменить его значение после инициализации, поэтому свойство фактически является константой. В результате значения по умолчанию запрещены, и вместо них следует использовать настоящую константу:

class Demo {
    const foo = "bar";
}

Следуя этому ограничению, только для чтения только поддерживается для типизированных свойств. Следующее свойство имеет недопустимое определение:

class Demo {
    protected readonly $foo;
}

Нетипизированное свойство, такое как $foo выше, имеет неявное значение по умолчанию null. Если бы readonly был разрешен, правило «без неявных констант» всплыло бы на поверхность. Типизированные свойства различают «неинициализированные» и «нулевые» состояния, поэтому они существуют без какого-либо значения, пока вы явно не установите его.

Модификатор readonly имеет особые правила при использовании как часть наследования класса. Дочерние классы не могут добавлять или удалять readonly для свойств, определенных их родителями.

Если сделать доступное для записи свойство только для чтения, родительский класс сломается, если его методы изменят значение. Хотя снятие ограничения кажется безобидным, RFC рассматривает только для чтения как «преднамеренное ограничение» возможностей, которые были бы потеряны, если бы было разрешено переопределение наследования. Запрещено, чтобы родительские классы могли утверждать, что дочерние классы не могут вызывать побочные эффекты, изменяя свойства, предназначенные только для чтения.

Свойства только для чтения можно задавать только в той области, в которой они определены. Это означает, что свойства public не могут быть установлены вне класса, даже если они не были предварительно инициализированы:

class Demo {
    public readonly string $foo;
}
 
$d = new Demo();
$d -> foo = "bar";  // illegal

Инициализация должна происходить внутри класса, определяющего свойство. В результате свойства только для чтения ближе к неизменяемым полям других языков программирования, в отличие от ранее существовавших свойств PHP.

Модификатор readonly применяется одинаково ко всем операциям записи. Он не различает внутренний и внешний доступ. У вас не может быть свойства, которое открыто только для чтения, но доступно для записи внутри класса, хотя это может быть разрешено будущим расширением спецификации.

И последний нюанс касается ключевого слова clone. Этот код не будет работать:

class Demo {
    public function __construct(
        public string $foo
    ) {}
}
 
$d = new Demo("bar");
$d2 = clone $d;
$d2 -> foo = "foobar";

Клонирование следует тем же правилам, что и обычный доступ к свойствам. Несмотря на то, что изменение foobar является первым обращением к foo в $d2, свойство уже было инициализировано в процессе клонирования. Во время клонирования происходит неявная инициализация.

Когда использовать свойства только для чтения?

Свойства только для чтения значительно ускорят создание простых классов, представляющих структуры данных. Общепринято писать одноразовые классы для хранения параметров HTTP-запроса, объектов передачи данных и данных ответа. Как правило, они неизменяемы, и не ожидается, что свойства изменятся после создания класса.

Раньше у вас было два непривлекательных варианта при написании структуроподобных классов: использовать общедоступные свойства, ускоряя разработку, но допуская модификацию, или тратить время на ручное добавление методов-получателей для предоставления защищенных свойств.

// Not ideal - Properties could be modified externally
class UserCreationRequest {
    public function __construct(
        public string $Username,
        public string $Password
    ) {}
}
 
// Not ideal either - Lots of boilerplate code
class UserCreationRequest {
    public function __construct(
        protected string $Username,
        protected string $Password
    ) {}
 
    public function getUsername() : string {
        return $this -> Username;
    }
 
    public function getPassword() : string {
        return $this -> Password;
    }
}

Свойства только для чтения, наконец, делают возможным идеальный подход:

class UserCreationRequest {
    public function __construct(
        public readonly string $Username,
        public readonly string $Password
    ) {}
}

Свойства общедоступны, но неизменны. В сочетании с продвижением свойств конструктора свойства только для чтения обещают значительно сократить шаблонный код, позволяя быстрее писать полезные классы.

readonly также повышает удобочитаемость кода и лучше отражает ваши намерения. Любой, кто читает или редактирует класс UserCreationRequest, знает, что его свойства не предназначены для изменения. В первом примере неизвестно, изменяет ли другой код в проекте свойства класса напрямую. Второй пример немного понятнее, но все же может побудить разработчика реализовать избыточные методы setUsername() и setPassword().

Заключение

Модификатор readonly обеспечивает встроенную поддержку неизменяемости свойств класса PHP. Это делает ваш код более понятным и предотвращает непреднамеренное изменение значений, обеспечивая неизменяемость во время выполнения.

Возвращаясь к серии выпусков PHP 7, создав базовый структуроподобный класс, используемый для определения типизированных свойств, написания конструктора, который устанавливает эти свойства, а затем добавления методов получения для предоставления их значений. В PHP 8.1 вы можете свести все это к одной сигнатуре конструктора, комбинируя public readonly и продвижение свойства.

Свойства только для чтения реализованы в последних сборках разработки PHP 8.1. Готовый к производству релиз выйдет в ноябре 2021 года.




Все права защищены. © Linux-Console.net • 2019-2024