PHP 8.0: Главные нововведения
С PHP 7.4: бенчмарки JIT, типы и типичные breaking changes из практики приложений.
Содержание (Оглавление)
- Named Arguments (Именованные аргументы)
- Match Expression
- Nullsafe Operator (?->)
- Constructor Property Promotion
- Эволюция системы типов (Union, mixed, static)
- Attributes / Аннотации
- Новые строковые функции
- Weak Maps (Слабые карты)
- Throw Expression и Обработка ошибок
- JIT-компилятор и Производительность
- Изменения в модулях и ядре
- Синтаксические улучшения (::class, запятые)
- Строгое сравнение строк и чисел
- Обратно несовместимые изменения (миграционные заметки)
PHP 8.0 открывает ветку 8.x: в ядре появляется JIT, расширяется система типов (union, mixed, static), в синтаксисе — именованные аргументы, match, nullsafe-оператор и атрибуты, а рантайм заметно менее терпим к «тихим» ошибкам. Здесь акцент на том, что меняется в реальных проектах (фреймворки, легаси, расширения), а не только в списке фич.
Named Arguments (Именованные аргументы)
Передача аргументов в функцию по имени избавляет от необходимости помнить их порядок и позволяет пропускать необязательные параметры. Это делает код самодокументируемым.
// Раньше: приходилось указывать все параметры по порядку
setcookie('test', '', time() + 60 * 60 * 2, '/', '', false, true);
// PHP 8.0: указываем только нужное
setcookie(
name: 'test',
expires: time() + 60 * 60 * 2,
httponly: true
);
Match Expression
Более строгая и лаконичная альтернатива оператору switch. Возвращает значение и использует строгое сравнение (===), избавляя от неожиданных багов с приведением типов.
$statusCode = 200;
$statusMessage = match ($statusCode) {
200, 300 => 'Успех или Редирект',
400, 404 => 'Ошибка клиента',
500 => 'Ошибка сервера',
default => 'Неизвестный статус',
};
Nullsafe Operator (?->)
Позволяет читать свойства и вызывать методы в цепочке. Если один из элементов null, вся цепочка возвращает null без выброса фатальной ошибки.
// PHP 7.4
$country = null;
if ($session !== null) {
$user = $session->user;
if ($user !== null) {
$country = $user->getAddress()->country;
}
}
// PHP 8.0
$country = $session?->user?->getAddress()?->country;
Constructor Property Promotion (Продвижение свойств в конструкторе)
Раньше для создания простых DTO (Data Transfer Objects) или Value Objects приходилось писать много однотипного кода: объявлять свойства, передавать их в конструктор и присваивать. PHP 8.0 объединяет все эти три шага в один.
// PHP 7.4: Классический (и многословный) подход
class UserDTO
{
public string $name;
public string $email;
protected int $age;
public function __construct(string $name, string $email, int $age)
{
$this->name = $name;
$this->email = $email;
$this->age = $age;
}
}
// PHP 8.0: Элегантно и лаконично
class UserDTO
{
public function __construct(
public string $name,
public string $email,
protected int $age,
) {}
}
Эволюция системы типов
В PHP 8.0 система типизации стала значительно строже и выразительнее.
Union Types (Объединенные типы)
До PHP 8.0, если переменная могла принимать несколько типов данных (например, int или float), мы полагались на PHPDoc. Теперь PHP поддерживает это нативно.
class Calculator
{
private int|float $number;
public function setNumber(int|float $number): void
{
$this->number = $number;
}
}
Псевдотип mixed
Новый тип mixed решает проблему legacy-кода. Он эквивалентен array|bool|callable|int|float|null|object|resource|string. Обратите внимание: mixed уже включает в себя null, поэтому писать ?mixed или mixed|null нельзя — это вызовет фатальную ошибку.
Возвращаемый тип static
Для реализации паттернов "Позднее статическое связывание" (Late Static Binding) и текучих интерфейсов (Fluent Interfaces) добавлен тип возвращаемого значения static (раньше был только self).
class BaseFactory {
public function create(): static {
return new static();
}
}
Интерфейс Stringable
Если класс реализует магический метод __toString(), PHP 8.0 автоматически (неявно) назначает ему интерфейс Stringable. Это позволяет использовать string|Stringable в тайпхинтах.
Attributes / Аннотации (Глубокий разбор)
Атрибуты (PHP 8.0) — это структурированные метаданные, которые навешиваются на классы, методы, свойства, параметры и константы. Движок сохраняет их в байткоде; читаются они через Reflection. Это не магия: ничего само не выполняется, пока ваш фреймворк, роутер или инструмент их не прочитает — по смыслу близко к аннотациям в Java/C#, но нативно в PHP.
Атрибуты vs PHPDoc
| | PHPDoc (@route, @deprecated) | Атрибуты (#[Route]) |
|---|----------------------------------|------------------------|
| Разбор | Строка в комментарии; нужны свои парсеры | Синтаксис языка; не нужно парсить комментарии regex’ом |
| Типизация | Неформальная; легко разъехаться с кодом | Аргументы конструктора — настоящие значения PHP |
| Инструменты | Поддержка в IDE разная | Reflection API стабильный и быстрый |
PHPDoc оставьте для человеческой документации; атрибуты используйте там, где метаданные реально читает код (маршрутизация, подсказки валидации, сериализация, codegen).
Объявление класса-атрибута
Любой класс может быть атрибутом, если помечен встроенным #[\Attribute]. Битовая маска ограничивает, где атрибут допустим (Attribute::TARGET_*). Если цели не указать, по умолчанию разрешены все цели — обычно лучше указать явно.
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
final class Route
{
public function __construct(
public string $path,
public array $methods = ['GET'],
) {}
}
- Часто делают класс
final, чтобы его не наследовали «случайно». - Параметры конструктора — это аргументы в месте использования:
#[Route('/x', methods: [...])].
Один и тот же элемент можно пометить несколькими экземплярами одного атрибута, если в маску добавить Attribute::IS_REPEATABLE — см. класс Attribute в мануале.
Синтаксис применения
Атрибуты ставятся перед объявлением; можно несколько подряд:
#[Route('/api/users', methods: ['GET', 'POST'])]
final class UserController {}
#[Route('/items')]
final class ItemController
{
#[Route('/items/{id}', methods: ['GET'])]
public function show(int $id): array
{
return ['id' => $id];
}
}
Именованные аргументы (PHP 8.0) естественно читаются: methods: ['GET'] попадает в параметр конструктора атрибута.
Как это работает в рантайме
- PHP разбирает атрибуты при компиляции и связывает их с reflection-структурой.
- В рантайме берёте
ReflectionClass,ReflectionMethod,ReflectionPropertyи т.д. - Вызываете
getAttributes()→ получаетеReflectionAttribute, затемnewInstance()— экземпляр вашего класса атрибута с аргументами из исходника.
Ничего «само» не вызывается: bootstrap, DI-контейнер или роутер должны сами дернуть Reflection (или библиотеку-обёртку).
Чтение атрибутов через Reflection
$reflection = new ReflectionClass(UserController::class);
// Только атрибуты заданного класса (удобнее всего)
foreach ($reflection->getAttributes(Route::class) as $attr) {
/** @var Route $route */
$route = $attr->newInstance();
// $route->path, $route->methods
}
// Все атрибуты класса (фильтруете сами)
foreach ($reflection->getAttributes() as $attr) {
$name = $attr->getName(); // например Route::class
$args = $attr->getArguments(); // сырые аргументы конструктора
}
Для маршрутов на методах — ReflectionMethod::getAttributes(), для валидации параметров — ReflectionParameter::getAttributes() и т.д.
Сквозной пример (идея минимального «роутера»)
#[Route('/api/users', methods: ['GET', 'POST'])]
class UserController {}
$rc = new ReflectionClass(UserController::class);
$routeAttrs = $rc->getAttributes(Route::class);
if ($routeAttrs === []) {
throw new RuntimeException('No Route on ' . UserController::class);
}
$route = $routeAttrs[0]->newInstance();
// зарегистрировать $route->path в диспетчере...
Где атрибуты особенно уместны
- HTTP routing / middleware — путь и метод у контроллера (как метаданные в духе Laravel/Symfony).
- Валидация и сериализация — правила на свойствах для гидратора или сериализатора.
- DI — подсказки для автоподстановки зависимостей (часто вместе с другими атрибутами).
- Тесты и внутренние инструменты — группы фикстур, свои маркеры deprecation, которые читает CI.
Не стоит вкладывать бизнес-логику только в атрибуты: держите их декларативными; сложную логику — в обычных классах и сервисах.
Строгое сравнение строк и чисел
Это одно из самых коварных изменений, ломающих обратную совместимость. Раньше при нестрогом сравнении числа со строкой, PHP конвертировал строку в число. В PHP 8.0, если строка не является числовой, числа сравниваются как строки[cite: 227, 228].
// PHP 7.4
0 == 'foobar' // true [cite: 224]
// PHP 8.0
0 == 'foobar' // false [cite: 226]
Синтаксические улучшения
Разрешение ::class на объектах
Теперь можно получить имя класса прямо из переменной объекта с помощью ::class. Раньше для этого использовалась функция get_class().
$object = new \App\Models\User();
// PHP 7: get_class($object);
echo $object::class; // Выведет "App\Models\User"
Висячие запятые (Trailing commas)
Разрешено оставлять висячую запятую в списках параметров функций, методов и замыканий. Это делает коммиты в Git более чистыми.
public function makeRequest(
string $url,
array $data,
array $headers, // <-- висячая запятая теперь легальна [cite: 286]
) { ... }
Новые строковые функции
Вместо strpos() и проверок на !== false, добавлены три функции, возвращающие строгий bool:
$str = "DevSense is awesome";
str_starts_with($str, "DevSense"); // true
str_contains($str, "awesome"); // true
str_ends_with($str, "awesome"); // true
Практический рецепт: префикс/суффикс без strpos
if (!str_starts_with($path, '/var/www/app/')) {
throw new InvalidArgumentException('Path escapes allowed root');
}
if (str_ends_with($filename, '.php')) {
// обработка загрузок скриптов и т.п.
}
Функция get_debug_type()
Новая функция get_debug_type() возвращает полезный тип переменной (например, App\Models\User вместо просто object, или int вместо integer). Она идеально подходит для составления понятных сообщений об ошибках.
Weak Maps (Слабые карты)
Архитектурный прорыв для ORM и кэширования. WeakMap позволяет создавать связи между объектами так, чтобы эти связи не мешали сборщику мусора (Garbage Collector) удалять объекты из памяти.
class Cache {
private WeakMap $cache;
public function __construct() {
$this->cache = new WeakMap();
}
public function getMetadata(object $obj): array {
if (!isset($this->cache[$obj])) {
$this->cache[$obj] = $this->computeExpensiveData($obj);
}
return $this->cache[$obj];
}
}
Как только объект $obj уничтожается в приложении, он автоматически исчезает и из WeakMap, освобождая память.
Throw Expression и Обработка ошибок
Оператор throw из инструкции (statement) превратился в выражение (expression).
// Теперь можно выбрасывать исключения прямо в тернарных операторах или при слиянии
$user = $request->get('user') ?? throw new InvalidArgumentException('User is required');
$callable = fn() => throw new Exception('This should not run');
Глобальное изменение строгости: В PHP 8.0 оператор подавления ошибок @ больше не скрывает фатальные ошибки. Кроме того, большинство предупреждений ядра (Engine Warnings) были преобразованы в Error исключения (например, попытка получить свойство не-объекта теперь "роняет" скрипт, а не просто пишет Warning в логи).
Non-capturing catches (Анонимный перехват исключений)
Если вам нужно перехватить исключение, но сам объект исключения вам не нужен, переменную теперь можно опустить.
// Раньше: мы обязаны были объявить переменную $e
try {
// код
} catch (Exception $e) {
Log::error('Что-то пошло не так');
}
// PHP 8.0:
try {
// код
} catch (Exception) {
Log::error('Что-то пошло не так');
}
Ошибки типов для встроенных функций
Большинство встроенных функций PHP теперь выбрасывают строгие исключения TypeError или ValueError в случае передачи неверных параметров, вместо того чтобы выдавать Warning и возвращать null.
// PHP 7.4
strlen([]); // Warning: strlen() expects parameter 1 to be string, array given [cite: 231]
// PHP 8.0
strlen([]); // TypeError: strlen(): Argument #1 ($str) must be of type string, array given [cite: 237]
array_chunk([], -1); // ValueError: array_chunk(): Argument #2 ($length) must be greater than 0
JIT-компилятор и Производительность
Самое фундаментальное архитектурное изменение под капотом PHP 8.0 — внедрение JIT (Just-In-Time) компилятора.
OPcache (До PHP 8): Исходный код парсился в OpCodes и виртуальная машина Zend выполняла их построчно.
JIT (PHP 8.0+): Анализирует OpCodes и компилирует "горячие участки" напрямую в машинный код процессора (x86/ARM).
Для типичных I/O-зависимых веб-приложений прирост составляет около 1-5%. Но для вычислений (CPU Bound) — от 300% до 500%.
Изменения в модулях и ресурсах
Продолжается очистка ядра от непрозрачных типов resource. Они заменяются на объекты:
cURL: curl_init() возвращает класс CurlHandle.
GD: imagecreate() возвращает GdImage.
Sockets: socket_create() возвращает Socket.
Очистка памяти: Новые объекты автоматически уничтожаются сборщиком мусора. Функции вроде curl_close() больше не несут смысловой нагрузки.
Другие изменения расширений:
JSON теперь встроен в ядро намертво (нельзя отключить ключом --disable-json).
XML-RPC перенесен из ядра в PECL.
Добавлена математическая функция fdiv(), которая разрешает деление на ноль (возвращает INF, -INF или NAN вместо ошибки).
Продолжается очистка ядра от непрозрачных типов resource. Они заменяются на объекты:
- cURL:
curl_init()возвращаетCurlHandle. - GD:
imagecreate()возвращаетGdImage. - Sockets:
socket_create()возвращаетSocket. - Также затронуты расширения OpenSSL, XMLWriter и функции XML.
Обратно несовместимые изменения (миграционные заметки)
Даже если вы не используете новый синтаксис, переход на PHP 8.0 может “сломать” приложение из‑за повышенной строгости рантайма и удалений. Ниже — компактный чеклист того, что стоит явно прогнать в CI и на staging.
Язык / ключевые слова / удаления
matchтеперь зарезервированное ключевое слово.mixedтеперь зарезервированное слово (нельзя использовать для имен классов/интерфейсов/trait и запрещено в namespaces).__autoload()удален. Используйтеspl_autoload_register().create_function()удалена. Используйте анонимные функции / closures.each()удалена. ИспользуйтеforeachилиArrayIterator.- Удалены определения констант без учета регистра (
define('FOO', 'bar', true)больше не поддерживается). - Методы с именем, совпадающим с именем класса, больше не считаются конструкторами (используйте
__construct()). - Больше нельзя вызывать non-static методы статически (это также влияет на проверки вроде
is_callable()при использовании имени класса). - Касты
(real)и(unset)удалены.
Обработка ошибок и диагностика (строже в runtime)
- Ошибки assert теперь по умолчанию бросают исключение (проверьте
assert.*, напримерassert.exception). - ini-директива
track_errorsудалена (значитphp_errormsgбольше недоступна; используйтеerror_get_last()). - Оператор
@больше не скрывает фатальные ошибки. Обработчики ошибок не должны полагаться наerror_reporting() == 0для определения подавления. - Уровень
error_reportingпо умолчанию теперьE_ALL(включаетE_NOTICEиE_DEPRECATED). display_startup_errorsвключен по умолчанию.- Многие warning стали
Error-исключениями (например: запись в свойство не-объекта, невалидные типы ключей массива/строковых оффсетов, unpack не-массива/Traversable, доступ к неопределенным не квалифицированным константам). - Многие notice стали warnings (неопределенные переменные/свойства/ключи массива, преобразование массива в строку, невалидные строковые оффсеты и т. п.).
Числовые строки и приведение типов
- Нестрогие сравнения чисел с нечисловыми строками изменились (см. раздел «Строгое сравнение строк и чисел» выше).
- “Saner numeric strings”: некоторые операции, которые раньше выдавали warning/notice, теперь выдают warning или бросают
TypeError(в первую очередь арифметические/битовые операции с нечисловыми строками; поведение стало строже и более консистентным). - Приведение float к string теперь не зависит от locale.
Массивы / ключи / вызовы / reflection
array_key_exists()больше не работает с объектами (используйтеisset()илиproperty_exists()).- Невалидные типы ключей для
array_key_exists()обрабатываются более строго и могут бросатьTypeError. - Ключи массива в
call_user_func_array()теперь интерпретируются как имена параметров (поведение может измениться, если вы случайно передаете ассоциативные массивы). debug_backtrace()иException::getTrace()больше не предоставляют ссылки на аргументы.
OOP edge cases
- Использование
parentвнутри класса без родителя теперь приводит к фатальной compile-time ошибке. - Сигнатуры magic methods теперь валидируются, если они объявлены (несоответствия могут ломать код).
Extensions: notable BC breaks
- cURL:
CURLOPT_POSTFIELDS(и другие опции, принимающие массивы) больше не принимает объекты “как массив”. Если вы на это рассчитывали — делайте явный каст(array). - Date/Time:
mktime()иgmmktime()теперь требуют хотя бы один аргумент. - DOM: удален ряд не реализованных DOM-классов и методов (если вы использовали их как заглушки/тестовые сущности — код упадет).
- Exif:
read_exif_data()удалена; используйтеexif_read_data(). - Filter:
FILTER_FLAG_SCHEME_REQUIREDиFILTER_FLAG_HOST_REQUIREDудалены (дляFILTER_VALIDATE_URLscheme/host и так всегда обязательны).INPUT_REQUESTиINPUT_SESSIONудалены из источниковfilter_input().
- mbstring:
mbstring.func_overloadудалена (и связанные константыMB_OVERLOAD_*/ записи вmb_get_info()).mb_parse_str()больше нельзя вызывать без указания результирующего массива.- модификатор
eдляmb_ereg_replace()удален; используйтеmb_ereg_replace_callback().
- OpenSSL:
openssl_seal()иopenssl_open()теперь требуют аргументmethod(прежний default"RC4"считается небезопасным). - PCRE (Regular Expressions): невалидные escape-последовательности больше не интерпретируются как литералы; модификатор
Xтеперь игнорируется. - PDO:
- default error mode изменен с silent на exceptions.
- изменились сигнатуры некоторых методов (в частности
PDO::query()иPDOStatement::setFetchMode()).
- Phar: metadata в phar больше не unserialize’ится автоматически (hardening безопасности; код, завязанный на неявный unserialize, нужно менять).
- Reflection:
ReflectionClass::newInstance(),ReflectionFunction::invoke()иReflectionMethod::invoke()перешли на variadics (...$args).- методы
Reflection*::export()удалены (используйте приведение reflection-объектов к строке).
- SPL:
SplFixedArrayтеперьIteratorAggregate(а неIterator); ряд методов итерации удален в пользуgetIterator().spl_autoload_register()теперь всегда бросаетTypeErrorна невалидных аргументах (параметрdo_throwпо сути игнорируется).
- Standard library:
assert()больше не вычисляет строковые аргументы (используйтеassert($a == $b), а неassert('$a == $b'));assert.quiet_eval/ASSERT_QUIET_EVALудалены.parse_str()больше нельзя вызывать без указания результирующего массива.- опция
'salt'уpassword_hash()больше не поддерживается (игнорируется с warning).
Итоги (Резюме)
Считайте PHP 8.0 сменой платформы: меньше сюрпризов от нестрогих сравнений, предсказуемее поведение встроенных API и типизация, которая наконец совпадает со стилем крупных кодовых баз. Выигрыш — меньше багов «только на проде» и более ровный путь к 8.1+, особенно если параллельно убрать deprecations, пока ещё есть тесты против 7.4.