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).
Атрибути срещу PHPDoc
| | PHPDoc (@route, @deprecated) | Атрибути (#[Route]) |
|---|-----------------------------------|------------------------|
| Парсване | Низ в коментар; нужни са парсери | Синтаксис на езика; без regex по коментари |
| Типизация | Неформална; лесно се разминава с кода | Аргументите на конструктора са реални PHP стойности |
| Инструменти | Интеграция в IDE е различна | Reflection API е стабилен и бърз |
PHPDoc оставете за човешка документация; атрибутите ползвайте, когато метаданните реално се четат от код (routing, validation hints, сериализация, 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-структурата.
- Взимате
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().
Краен пример (идея за минимален router)
#[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 — път и методи към контролери.
- Валидация и сериализация — правила върху свойства за хидратор/сериализатор.
- 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)
Разрешено е да оставяте висяща запетая в списъците с параметри на функции, методи и closure-и. Това прави commit-ите в 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).
// Вече може да хвърляте изключения директно в тернарни оператори или при null coalescing
$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('Нещо се обърка');
}
Type грешки за вградените функции
Повечето вградени функции на 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.
Управление на паметта: новите обекти се унищожават автоматично от garbage collector-а. Функции като 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()са премахнати (използвайте cast на 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.