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 у type hint-ах.
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) природно відповідають параметрам конструктора атрибута.
Як це працює в runtime
- PHP розбирає атрибути під час компіляції й пов’язує їх із reflection-структурою.
- В runtime берете
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();
}
foreach ($reflection->getAttributes() as $attr) {
$name = $attr->getName();
$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 може “зламати” застосунок через суворішу поведінку в runtime та видалення. Нижче — компактний чеклист того, що варто прогнати в CI та на staging.
Мова / ключові слова / видалення
matchтепер reserved keyword.mixedтепер reserved word (не можна використовувати для назв class/interface/trait і заборонено в namespaces).__autoload()видалено. Використовуйтеspl_autoload_register().create_function()видалено. Використовуйте anonymous functions / closures.each()видалено. ВикористовуйтеforeachабоArrayIterator.- Видалено визначення констант без урахування регістру (
define('FOO', 'bar', true)більше не підтримується). - Методи з таким самим іменем, як у класу, більше не трактуються як конструктори (використовуйте
__construct()). - Більше не дозволено викликати non-static методи статично (це також впливає на перевірки на кшталт
is_callable()при використанні імені класу). - Приведення
(real)і(unset)видалено.
Помилки та діагностика (суворіше в runtime)
- Assertion failures тепер за замовчуванням кидають виняток (перегляньте
assert.*, напр.assert.exception). - ini-директиву
track_errorsвидалено (тобтоphp_errormsgбільше недоступна; використовуйтеerror_get_last()). - Оператор
@більше не “глушить” фатальні помилки. Error handlers не мають покладатися наerror_reporting() == 0для визначення suppression. error_reportingза замовчуванням теперE_ALL(включаєE_NOTICEіE_DEPRECATED).display_startup_errorsувімкнено за замовчуванням.- Багато warning-ів стали
Errorexceptions (наприклад: запис у property не-об’єкта, невалідні типи ключів масиву/рядкових offset-ів, unpack не-array/Traversable, доступ до undefined unqualified constants). - Багато notice-ів стали warnings (undefined variables/properties/array keys, array-to-string conversion, невалідні string offsets тощо).
Числові рядки та приведення типів
- Нестрогі порівняння чисел з нечисловими рядками змінилися (див. розділ “Строге порівняння рядків і чисел” вище).
- “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()більше не надають references до аргументів.
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’иться автоматично (security hardening; код, що розраховує на неявний unserialize, треба змінювати).
- Reflection:
ReflectionClass::newInstance(),ReflectionFunction::invoke()іReflectionMethod::invoke()перейшли на variadics (...$args).- методи
Reflection*::export()видалено (кастуйте reflection-об’єкти в string).
- SPL:
SplFixedArrayтеперIteratorAggregate(а неIterator); низку методів ітерації видалено на користьgetIterator().spl_autoload_register()тепер завжди кидаєTypeErrorна невалідних аргументах (параметрdo_throwфактично ігнорується).
- Standard library:
assert()більше не обчислює string-аргументи (використовуйтеassert($a == $b), а неassert('$a == $b'));assert.quiet_eval/ASSERT_QUIET_EVALвидалено.parse_str()більше не можна використовувати без передавання результуючого масиву.- опція
'salt'уpassword_hash()більше не підтримується (ігнорується з warning).
Підсумки (резюме)
Сприймайте PHP 8.0 як оновлення платформи: менше сюрпризів від нестрогих порівнянь, зрозуміліші відмови вбудованих API і типізація, яка нарешті відповідає великим кодовим базам. Виграш — менше багів лише на production і рівніший шлях до 8.1+, особливо якщо прибирати deprecations, доки ще є тести проти 7.4.