Вышедшая 21 ноября 2024 года версия PHP 8.4 внесла ряд значительных улучшений, открывая новую главу в развитии языка. В этой статье мы рассмотрим ключевые изменения и приведем примеры практического применения.
Хуки свойств
Первым обновлением, которое мы рассмотрим, станут хуки свойств — механизм, позволяющий перехватывать чтение и запись в объектах без необходимости писать геттеры и сеттеры вручную.
Традиционно для установки и получения значений свойств, а также для выполнения дополнительных операций над ними используются публичные методы. Такой подход широко применяется, например, в моделях сущностей популярных фреймворков:
class User extends Model { private string $name; public function getName(): string { return ucfirst($this->name); } public function setName(string $name): void { $this->name = trim($name); } }
С внедрением хуков свойств в PHP 8.4 такие методы (геттеры и сеттеры) могут быть заменены на хуки get и set. Эти хуки автоматически вызываются при обращении к свойству: get
— при чтении значения, set
— при его изменении.
Хуки get
и set
создаются следующим образом. После имени публичного свойства открываются фигурные скобки, в пределах которых вставляются хуки. Хуки также содержат собственные фигурные скобки, где определяется логика при обращении со свойством. После выполнения операции хук get
должен вернуть результирующее значение, а хук set
— обновить значение свойства.
В примере ниже мы перехватываем обращение к свойству $cryptoPair
. При сохранении значения в это свойство мы валидируем новую строку — проверяем, является ли строка криптопарой. Если да, то разбиваем пару на два значения и сохраняем в свойствах $firstCoin
и $secondCoin
. При получении криптопары из свойства $cryptoPair
мы предварительно форматируем значения, используя сохраненные тикеры:
class Crypto { public const AVAILABLE_COINS = ['BTC', 'ETH', 'USDT', 'BNB', 'XRP', 'ADA', 'DOGE', 'SOL']; public function __construct(private string $firstCoin, private string $secondCoin) { } public string $cryptoPair { get { return sprintf('%s/%s', $this->firstCoin, $this->secondCoin); } // Запись хука можно сократить: // get => sprintf('%s/%s', $this->firstCoin, $this->secondCoin); set (string $value) { if (!str_contains($value, '/')) { throw new InvalidArgumentException('The crypto pair is not correct.'); } [$firstCoin, $secondCoin] = explode('/', $value, 2); $isFirstCoinAvailable = in_array(strtoupper($firstCoin), self::AVAILABLE_COINS, true); $isSecondCoinAvailable = in_array(strtoupper($secondCoin), self::AVAILABLE_COINS, true); if (!$isFirstCoinAvailable || !$isSecondCoinAvailable) { throw new InvalidArgumentException('The coin is not available.'); } $this->firstCoin = strtoupper($firstCoin); $this->secondCoin = strtoupper($secondCoin); } } } $crypto = new Crypto('BTC', 'USDT'); echo $crypto->cryptoPair; // BTC/USDT $crypto->cryptoPair = 'eth/xrp'; echo $crypto->cryptoPair; // ETH/XRP
Асимметричная область видимости свойств
Асимметричная видимость свойств позволяет более гибко управлять доступом к свойствам классов. В отличие от традиционной симметричной видимости, где модификаторы доступа (public
, protected
, private
) применяются как для чтения, так и для записи, асимметричная видимость позволяет устанавливать разные уровни доступа для этих операций.
Благодаря этому нововведению, в PHP 8.4 для каждого свойства можно задавать область видимости отдельно как для чтения, так и для записи. Это осуществляется с помощью добавления новых модификаторов записи private(set), protected(set) и public(set).
В примере ниже свойство $author
доступно для чтения из любого места (public
), но его можно изменять только внутри класса Book
(private(set)
):
class Book { public private(set) Author $author; }
Так как по умолчанию область видимости считается public
, пример выше можно записать в сокращенном виде:
class Book { private(set) Author $author; // То же самое, что и public private(set) }
Пример со свойством асимметричной видимости $area
, где демонстрируется вычисление площади круга:
class Circle { private float $radius; public private(set) float $area; public function __construct(float $radius) { $this->radius = $radius; $this->area = pi() * ($this->radius ** 2); // Вычисление площади круга } } $circle = new Circle(5); echo 'Площадь: ' . $circle->area; // Площадь: 78.539816339745 $circle->area = 100; // Ошибка, так как свойство недоступно для записи извне
Атрибут Deprecated
В PHP 8.4 представлен новый атрибут #[Deprecated]
, который позволяет помечать функции, методы и константы классов, а также элементы перечислений как устаревшие. При использовании элемента, помеченного атрибутом #[Deprecated]
, PHP автоматически генерирует сообщение об устаревании каждый раз, когда этот элемент используется.
Атрибут #[Deprecated]
поддерживает два необязательных параметра: $message
и $since
, позволяющие уточнить причину устаревания и версию, с которой элемент считается устаревшим.
Пример класса с устаревшей константой и методом:
class SomeClass { #[Deprecated("use NEW_CONSTANT instead", "8.4")] public const OLD_CONSTANT = 'constant_value'; #[Deprecated("use newMethod() instead", "8.0")] public function oldMethod(): void { echo "This is the old method."; } } echo SomeClass::OLD_CONSTANT; // Deprecated: Constant SomeClass::OLD_CONSTANT is deprecated since 8.4, use NEW_CONSTANT instead in... $instance = new SomeClass(); $instance->oldMethod(); // Deprecated: Method SomeClass::oldMethod() is deprecated since 8.0, use newMethod() instead in…
Новые функции работы с массивами
В PHP 8.4 добавлены новые функции для работы с массивами: array_find, array_find_key, array_any и array_all.
array_find
Функция array_find возвращает значение первого элемента, для которого callback-функция вернёт true
:
$books = [ ['title' => 'Book A', 'year' => 2005], ['title' => 'Book B', 'year' => 2012], ['title' => 'Book C', 'year' => 2008], ['title' => 'Book D', 'year' => 2015], ]; // Поиск первой книги, опубликованной после 2010 года $firstNewBook = array_find($books, fn($book) => $book['year'] > 2010); echo $firstNewBook ? $firstNewBook['title'] : 'No such books found'; // Book B
array_find_key
Функция array_find_key возвращает ключ первого элемента, для которого callback-функция вернёт true
:
$users = [ 'Megan' => ['age' => 27, 'city' => 'New York'], 'Andy' => ['age' => 38, 'city' => 'Los Angeles'], 'Richard' => ['age' => 22, 'city' => 'Chicago'], ]; // Поиск ключа первого пользователя старше 30 лет $firstAdultKey = array_find_key($users, fn($user) => $user['age'] > 30); echo $firstAdultKey ? "$firstAdultKey from {$users[$firstAdultKey]['city']}" : "No users over 30"; // Andy from Los Angeles
array_any
Функция array_any проверяет наличие хотя бы одного элемента в массиве, удовлетворяющего условию callback-функции:
$emails = ['user@example.com', '@example.com', 'user_example.com']; // Проверка наличия хотя бы одного корректного email адреса $result = array_any($emails, fn($email) => filter_var($email, FILTER_VALIDATE_EMAIL)); var_dump($result); // bool(true)
array_all
Функция array_all проверяет, все ли элементы в массиве удовлетворяют условию callback-функции:
$numbers1 = [1, 2, 3]; $numbers2 = [-1, 2, 3]; // Все ли числа массива положительные. $result1 = array_all($numbers1, fn($number) => $number > 0); $result2 = array_all($numbers2, fn($number) => $number > 0); var_dump($result1); // bool(true) var_dump($result2); // bool(false)
Упрощенный синтаксис new выражений
Для создания экземпляров классов с помощью ключевого слова new в PHP 8.4 введен упрощенный синтаксис. Теперь можно обращаться к свойствам и методам только что инициализированного объекта без необходимости оборачивать выражение new
в круглые скобки:
// Раньше $request = (new Request())->withMethod('GET')->withUri('/posts'); // Сейчас $request = new Request()->withMethod('GET')->withUri('/posts');
Парсер HTML5
С выходом PHP 8.4 был представлен новый встроенный класс для работы с HTML — Dom\HTMLDocument
, который обеспечивает полную поддержку HTML5. В отличие от DOMDocument
, основанного на libxml2, новый парсер следует спецификациям WHATWG HTML, что делает его более точным и предсказуемым при разборе современных веб-страниц.
Основные особенности Dom\HTMLDocument
:
- Поддержка HTML5 — новый парсер корректно интерпретирует HTML5-документы, включая современные теги (
main
,article
,section
,dialog
,template
и др.). - Современный API — теперь можно использовать знакомые методы
querySelector
иquerySelectorAll
, аналогичные JavaScript. - Устранение ограничений
DOMDocument
— новый парсер избавлен от проблем, связанных с некорректным разбором HTML5 и зависимостью от libxml2, которая могла приводить к ошибкам.
Класс Dom\HTMLDocument предоставляет три метода для создания документа:
createEmpty
— создает пустой HTML-документ, который можно заполнять вручную;createFromFile
— загружает HTML-документ из файла;createFromString
— создает HTML-документ из строки с HTML-кодом.
Пример парсинга HTML-строки:
$html = <<<HTML <!DOCTYPE html> <html> <head> <title>Новости компании</title> </head> <body> <main> <h1>Последние новости</h1> <p>На следующей неделе состоится конференция, посвященная инновациям в нашей отрасли.</p> </main> </body> </html> HTML; // Создаем объект Dom\HTMLDocument $dom = \Dom\HTMLDocument::createFromString($html); // Используем querySelectorAll для выборки всех заголовков и абзацев $elements = $dom->querySelectorAll('h1, p'); // Выводим содержимое каждого элемента foreach ($elements as $element) { echo $element->textContent . PHP_EOL; }
Новый метод создания объекта даты
До выхода новой версии PHP создавать объекты даты из временных меток UNIX можно было с помощью нескольких подходов, каждый из которых предполагал несколько действий. Можно было использовать конструктор либо метод setTimestamp
объекта, а также статический метод createFromFormat('U')
классов DateTime
и DateTimeImmutable
:
$timestamp = 1739163600; $date = new DateTimeImmutable('@' . $timestamp); echo $date->format('Y-m-d'); // 2025-02-10 $timestamp = 1739163600; $date = new DateTimeImmutable(); $date = $date->setTimestamp($timestamp); echo $date->format('Y-m-d'); // 2025-02-10 $timestamp = 1739163600; $date = DateTimeImmutable::createFromFormat('U', (string) $timestamp); echo $date->format('Y-m-d'); // 2025-02-10
С выходом PHP 8.4 появился новый, более удобный способ — использование специального метода createFromTimestamp
. Этот метод позволяет напрямую создавать объекты даты из временных меток UNIX как целых чисел, так и значений с плавающей запятой:
$date = DateTimeImmutable::createFromTimestamp(1739163600); echo $date->format('Y-m-d H:i'); // 2025-02-10 05:00 $date = DateTimeImmutable::createFromTimestamp(1739163600.628); echo $date->format('Y-m-d H:i:s.v'); // 2025-02-10 05:00:00.628
Ленивые объекты (lazy objects)
В PHP 8.4 появилась долгожданная поддержка ленивых объектов. Ленивый объект — это объект, который не создается сразу после его объявления. Вместо этого его инициализация откладывается до тех пор, пока к нему не будет обращение или не потребуется изменить его состояние. Это позволяет экономить ресурсы системы и улучшает производительность.
Поддерживаются два типа ленивых объектов:
- Ленивые призраки (Ghost Objects) — объекты, которые создаются на месте непосредственно перед использованием. После создания они становятся полноценными экземплярами своего класса и больше неотличимы от обычных.
- Ленивые прокси (Virtual Proxies) — промежуточные слои между кодом клиента и реальным экземпляром класса. Они перенаправляют все операции на реальный экземпляр после его создания.
Для создания ленивых объектов используются методы класса ReflectionClass
: newLazyGhost()
для создания ленивых призраков и newLazyProxy()
для создания ленивых прокси. Оба метода принимают callback-функцию, которая вызывается при необходимости фактической инициализации.
Пример ленивого призрака:
class SomeClass { public function __construct(public string $value) { } } $reflector = new ReflectionClass(SomeClass::class); $lazyObject = $reflector->newLazyGhost(function (SomeClass $object) { // Инициализируем объект позже — по требованию $object->__construct('Some value'); }); // Инициализация ещё не произошла var_dump($lazyObject); // lazy ghost object(SomeClass)#3 (0) { ["value"]=> uninitialized(string) // Запускаем инициализацию echo $lazyObject->value; // Some value // Теперь объект полностью инициализирован var_dump($lazyObject); // object(SomeClass)#3 (1) { ["value"]=> string(10) "Some value" }
Пример ленивого прокси:
class SomeClass { public function __construct(public string $value) { } } $reflector = new ReflectionClass(SomeClass::class); $lazyProxy = $reflector->newLazyProxy(function (SomeClass $proxy): SomeClass { return new SomeClass('Some value'); }); // Ленивая инициализация ещё не произошла var_dump($lazyProxy); // lazy proxy object(SomeClass)#3 (0) { ["value"]=> uninitialized(string) } // Запускаем инициализацию при обращении к методу или свойству echo $lazyProxy->value; // Some value // Теперь прокси работает с реальным объектом var_dump($lazyProxy); // lazy proxy object(SomeClass)#3 (1) { ["instance"]=> object(SomeClass)#4 (1) { ["value"]=> string(10) "Some value" } }
Другие улучшения
Помимо вышеупомянутых изменений, кратко отметим еще некоторые следующие:
- Добавлены функции
mb_trim
,mb_ltrim
иmb_rtrim
для работы с многобайтовыми строками, аналогично функциямtrim
,ltrim
иrtrim
. Также добавленыmb_ucfirst
иmb_lcfirst
для изменения регистра первого символа в строке. - Появилась функция
request_parse_body
, упрощающая обработку HTTP-запросов с методами PUT, DELETE и PATCH. Она считывает тело запроса из потокаphp://input
, разбирает его и возвращает массивы, эквивалентные$_POST
и$_FILES
. - Добавлены новые константы Curl (
CURL_HTTP_VERSION_3
иCURL_HTTP_VERSION_3ONLY
) для поддержки HTTP/3. - Появились новые функции для работы с заголовками HTTP-ответа:
http_get_last_response_headers
иhttp_clear_last_response_headers
, которые позволяют получать и очищать HTTP-заголовки последнего ответа. - Ключевые слова
exit
иdie
теперь являются функциями, но могут вызываться без скобок для обратной совместимости. - Функция
round()
теперь вызывает исключениеValueError
при использовании неправильных режимов округления. - Параметр
cost
алгоритмаPASSWORD_BCRYPT
/PASSWORD_DEFAULT
изменён с 10 на 12 для повышения устойчивости паролей. - Для классов
DateTime
иDateTimeImmutable
добавлены методыgetMicrosecond()
иsetMicrosecond()
, позволяющие получать и устанавливать значение микросекунд в объектах, соответственно. - Добавлена поддержка специфичных для драйвера подклассов PDO.