PHP 8.4 вышел в ноябре 2024 года и стал одним из самых заметных обновлений последних лет. В нём появились property hooks, которые меняют работу со свойствами классов, раздельные права на чтение и запись свойства, новые функции для массивов и DOM API, корректно понимающий HTML5. Разберём эти нововведения на простых примерах и обсудим, на что обратить внимание перед переводом сайта на хостинге на 8.4.
Property hooks: проверки без геттеров и сеттеров
Раньше, чтобы проверять значение при записи в свойство, его делали закрытым и писали отдельные методы getName() и setName(). Это порождало много однотипного кода. В PHP 8.4 логику проверки и преобразования можно привязать прямо к свойству. Внешний код работает с обычным свойством, но при каждом чтении или записи выполняется ваш код.
Возьмём класс товара для небольшого интернет-магазина в Ташкенте. Лишние пробелы в названии должны удаляться автоматически, цена не может быть отрицательной, а полное название собирается из имени и цены:
class Product
{
public string $name {
set(string $value) {
$this->name = trim($value);
}
}
public int $price {
set(int $value) {
if ($value < 0) {
throw new InvalidArgumentException('Цена не может быть отрицательной');
}
$this->price = $value;
}
}
// Виртуальное свойство: только чтение, в памяти не хранится
public string $fullName {
get => $this->name . ' - ' . number_format($this->price, 0, '.', ' ') . ' сум';
}
}
$p = new Product();
$p->name = ' Чайник ';
$p->price = 85000;
echo $p->fullName; // Чайник - 85 000 сум
Свойство $fullName виртуальное: в него ничего не записывается, значение вычисляется при каждом чтении. Хуки можно объявлять и в интерфейсах, то есть интерфейс теперь может требовать не только методы, но и свойства. В больших проектах это заметно очищает код.
Асимметричная видимость: читать снаружи, писать изнутри
Часто свойство должно быть доступно для чтения всем, а изменять его может только сам класс. Например, статус заказа виден внешнему коду, но меняется лишь через специальный метод. В PHP 8.4 право записи указывается отдельно:
class Order
{
public private(set) string $status = 'new';
public function pay(): void
{
$this->status = 'paid';
}
}
$o = new Order();
echo $o->status; // new
$o->pay();
echo $o->status; // paid
$o->status = 'cancelled'; // Error: запись снаружи запрещена
Раньше для этого требовались закрытое свойство и отдельный геттер, теперь хватает одной строки. Вариант protected(set) разрешает запись ещё и наследникам.
Новые функции для массивов
Добавлены четыре функции для поиска по условию: array_find(), array_find_key(), array_any() и array_all(). Они заметно сокращают код, который раньше писали через foreach или array_filter(). Пример из системы записи на приём в клинике в Самарканде:
$appointments = [
['id' => 1, 'doctor' => 'Karimov', 'confirmed' => true],
['id' => 2, 'doctor' => 'Aliyeva', 'confirmed' => false],
['id' => 3, 'doctor' => 'Karimov', 'confirmed' => true],
];
// Первая неподтверждённая запись
$pending = array_find($appointments, fn($a) => !$a['confirmed']);
// Есть ли хотя бы одна неподтверждённая?
$hasPending = array_any($appointments, fn($a) => !$a['confirmed']);
// Все ли подтверждены?
$allConfirmed = array_all($appointments, fn($a) => $a['confirmed']);
Небольшие, но полезные изменения
Чтобы создать объект и сразу вызвать его метод, больше не нужны скобки вокруг new. Вместо (new DateTimeImmutable())->format('Y-m-d') теперь достаточно new DateTimeImmutable()->format('Y-m-d'). Мелочь, но код читается легче.
Для многоязычных сайтов появились mb_trim(), mb_ltrim(), mb_rtrim(), mb_ucfirst() и mb_lcfirst(). Они правильно работают с кириллицей и другими многобайтовыми символами, что удобно при обработке текстов на русском и узбекском. Ещё одно новшество — атрибут #[\Deprecated], которым можно помечать устаревшие функции в собственном коде, чтобы при их вызове выводилось предупреждение.
В расширении DOM появился класс Dom\HTMLDocument, который разбирает HTML5 так же, как браузеры, и поддерживает поиск элементов по CSS-селекторам. Также добавлены ленивые объекты (lazy objects): объект создаётся только тогда, когда он действительно нужен. Этот механизм в первую очередь рассчитан на фреймворки и библиотеки.
Устаревшие возможности и удалённые расширения
Перед переходом важно знать, что объявлено устаревшим, потому что это обычно приводит к росту предупреждений в журнале. Чаще всего встречается неявный nullable-тип: запись function save(Product $p = null) теперь считается устаревшей, правильно писать ?Product $p = null. В старых плагинах таких мест много, поэтому после перехода на 8.4 в журнале может появиться много сообщений Deprecated.
- Расширения IMAP, OCI8, PDO_OCI и pspell убраны из ядра PHP и перенесены в отдельные пакеты PECL. Если сайт читает почтовый ящик через функции
imap_*, проверьте это заранее. - Стандартная стоимость bcrypt в
password_hash()повышена с 10 до 12: безопасность выше, но проверка пароля занимает чуть больше времени. - Константа
E_STRICTобъявлена устаревшей.
Как перевести сайт на хостинге на 8.4
Новые возможности приносят пользу при написании нового кода, а для существующего сайта главная цель перехода — скорость и более долгая поддержка безопасности. Сначала проверьте, поддерживают ли 8.4 ваша CMS, тема и плагины. Затем сделайте резервную копию файлов и базы данных и по возможности протестируйте сайт на копии.
На хостинге sayt.uz версия PHP выбирается для каждого сайта отдельно в панели ISPmanager, поэтому перевод одного сайта на 8.4 не затрагивает остальные. После перехода следите за журналом ошибок пару дней. Если появится Fatal error, верните прежнюю версию и спокойно исправьте проблему. Если есть только предупреждения Deprecated, сайт продолжит работать, но помните, что в следующих версиях они могут превратиться в ошибки.