Что нового в PHP 8.4
- Release: https://www.php.net/releases/8.4/ru.php
- Wiki: PHP 8.3
- github: Список изменений PHP 8.3
- https://habr.com/ru/news/776250/
public $foo { set () } — Хуки свойств
Doc: https://www.php.net/manual/ru/migration84.new-features.php
Wiki: https://wiki.php.net/rfc/property-hooks
Хуки свойства позволяют добавить логику при чтении/записи (get/set) свойства — без создания отдельных методов getFoo() и setFoo() (геттеров и сеттеров).
Было:
class User {
private string $name;
public function setName( string $name ): void {
if ( $name === '' ) throw new ValueError('Empty');
$this->name = $name;
}
public function getName(): string {
return ucfirst( $this->name );
}
}
Стало:
class User {
public string $name {
set {
if ( $value === '' ) throw new ValueError('Empty');
$this->name = $value;
}
get => ucfirst( $this->name );
}
public function __construct(string $name) {
$this->name = $name;
}
}
Теперь можно писать $user->name = 'Tim'; и при установки значения будет вызвана set функция:
$user = new User(); $user->name = 'tim'; // проходит через set-хук echo $user->name; // Tim
Поддерживается короткая запись get => expr;:
public string $fullName { get => $this->first . ' ' . $this->last; }
Свойства в интерфесах с get и set:
Раньше интерфейсы могли требовать только методы, а теперь могут требовать свойства — читаемые (get) и/или записываемые (set).
То есть теперь можно сказать:
“Любой класс, реализующий этот интерфейс, обязан иметь $name, которое можно читать (или писать)”
Пример:
interface Named {
public string $name { get; } // обязательное читаемое свойство
}
class User implements Named {
public string $name = 'Tim'; // подходит, get есть по умолчанию
}
class Person implements Named {
public string $name {
get => strtoupper( $this->realName ); // подходит, есть гет хук
}
}
Это делает интерфейсы ближе к «контрактам данных», а не только к наборам методов.
Делает свойства «умными», без лишней магии __get/__set:
public string $slug {
set {
$this->slug = strtolower( trim( $value ) );
}
}
// Без магии и лишнего кода
Итого: Хуки свойств обеспечивают поддержку вычисляемых свойств, которые могут быть понятны IDE и инструментам статического анализа, без необходимости писать DocBlock-комментарии, которые могут не совпадать. Кроме того, они позволяют выполнять надёжную предварительную или последующую обработку значений, без необходимости проверять, существует ли в классе соответствующий геттер или сеттер.
public private(set) string $ver — Разная область видимости свойств
Doc: https://www.php.net/manual/ru/language.oop5.visibility.php#language.oop5.visibility-members-aviz
Wiki: https://wiki.php.net/rfc/asymmetric-visibility-v2
В PHP 8.4 можно задавать асимметричную область видимости свойств — отдельно для чтения и записи.
set-видимость указывается как private(set) или protected(set) сразу после модификатора видимости.
class Book {
public function __construct(
public private(set) string $title,
public protected(set) string $author,
protected private(set) int $year,
) {}
}
class SpecialBook extends Book {
public function update( string $author, int $year ): void {
$this->author = $author; // Всё хорошо
$this->year = $year; // Критическая ошибка
}
}
$b = new Book( 'PHP', 'Peter', 2024 );
echo $b->title; // OK
echo $b->author; // OK
echo $b->pubYear; // Fatal
$b->title = 'How not to PHP'; // Fatal
$b->author = 'Pedro H. Peterson'; // Fatal
$b->pubYear = 2023; // Fatal
Особенности:
-
Get всегда имеет ту же или более широкую область, чем set.
Другими словами ограничение для set должно быть равно или сильнее чем у get:public protected(set) // OK protected public(set) // error
-
private(set)делает свойство финальным - его нельзя переопределить или повторно объявить в дочернем классе. -
Работает только с типизированными свойствами - можно ставить только у свойств с типом - например string, int, array и т.д.:
public private(set) string $title; // OK public private(set) $title; // fatal
-
Получение ссылки на свойство подчиняется видимости set, а не get. Это связано с тем, что ссылка разрешает изменять значение свойства.
Аналогично, попытка записи в массив, который содержится в свойстве, включает в себя как внутреннюю операцию чтения - get, так и операцию записи - set, и поэтому подчинится области видимости set, поскольку ограничение области видимости для записи сильнее.
- Нельзя исползовать пробел:
private( set )- пробел вызовет ошибку.
Наследование
При наследовании можно ослабить доступ для чтения или записи, но private(set) — финальное, переопределить нельзя:
class Book {
protected string $title;
public protected(set) string $author;
protected private(set) int $year; // final
}
class SpecialBook extends Book {
// OK - ограничение на чтение слабее, а на запись – такое же
public protected(set) string $title;
// OK - ограничение на чтение такое же, а на запись – слабее
public string $author;
// Fatal Error - cвойства с private(set) — окончательны!
public protected(set) int $year;
}
new MyClass()->method() — без скобок
Doc: https://www.php.net/manual/ru/migration84.new-features.php#migration84.new-features.core.new-chaining
Wiki: https://wiki.php.net/rfc/new_without_parentheses
К свойствам и методам только что инициализированного объекта теперь можно обращаться, не оборачивая выражение new в круглые скобки.
// Было: $ver = ( new PhpVersion() )->getVersion(); // Стало: $ver = new PhpVersion()->getVersion();
#[\Deprecated] — Атрибут
Doc: https://www.php.net/manual/ru/class.deprecated.php
Wiki: https://wiki.php.net/rfc/deprecated_attribute
Атрибут #[\Deprecated], позволяет объявлять функции, методы, константы и enum-кейсы как устаревшие.
При вызове таких (устаревших) элементов будет выдаваться предупреждение E_USER_DEPRECATED.
Можно указать сообщение $message и версию $since, которые попадут в текст предупреждения. Например:
#[\Deprecated('Use newMethod() instead', since: '2.4')]
function oldMethod() {}
oldMethod(); // Deprecated: Function oldMethod() is deprecated since 2.4, Use newMethod() instead
Еще примеры использования:
#[\Deprecated]
function test() {
}
#[\Deprecated("use test() instead")]
function test2() {
}
#[\Deprecated("use test() instead", since: "2.4")]
function test3() {
}
#[\Deprecated(since: "2024-05-07")]
function test4() {
}
class Clazz {
#[\Deprecated]
public const OLD_WAY = 'foo';
#[\Deprecated]
function test() {
}
#[\Deprecated("use test() instead")]
function test2() {
}
}
enum MyEnum {
#[\Deprecated]
case OldCase;
}
Рефлексия
Метод isDeprecated() классов ReflectionFunctionAbstract и ReflectionClassConstant будет возвращать true:
#[\Deprecated]
function test() {}
$r = new ReflectionFunction('test');
var_dump( $r->isDeprecated() ); // bool(true)
class Clazz {
#[\Deprecated]
public const OLD_WAY = 'foo';
}
$r = new ReflectionClassConstant( Clazz::class, 'OLD_WAY' );
var_dump( $r->isDeprecated() ); // bool(true)
Атрибут #[\Deprecated] в stub-файлах будет работать аналогично док-комментарию /** @deprecated */.
- Старые функции с таким комментарием не получат атрибут автоматически.
- Авторам расширений рекомендуется добавить
#[\Deprecated]вручную для единообразия.
В глобальном пространстве нельзя объявлять класс с именем Deprecated.
—