PHP 7.3: Головні нововведення

PHP 7.3 у гілці 7.x — реліз шліфування та безпеки: зручніший повсякденний синтаксис (гнучкий heredoc/nowdoc, кінцеві коми в викликах, посилання в list() / деструктуризації []), дрібні, але корисні функції (array_key_first(), array_key_last(), is_countable(), hrtime()) і оновлення платформи (JSON_THROW_ON_ERROR / JsonException, PASSWORD_ARGON2ID, PCRE2, великі зміни в mbstring). Під час міграції варто окремо перевірити розбір рядків (тіло heredoc проти закривальної мітки, класи символів PCRE), семантику посилань (читання з масиву/властивості, рядкові ключі ArrayAccess) і розширення (дроби секунд у MySQL, політика IMAP, імена cookie з 7.3.23).

Зміст


Гнучкий Heredoc і Nowdoc

Закривальну мітку можна відступити від краю рядка; такий самий відступ знімається з усіх рядків тіла. SQL, HTML і довгі тексти стають читабельнішими в коді з відступами.

Міграція: якщо всередині літерала трапляється той самий токен, що й мітка закриття (з урахуванням відступу), PHP може завершити рядок передчасно — обирайте довгі унікальні мітки (SQL_GET_USER, а не SQL).

$query = <<<SQL
    SELECT id, name
    FROM users
    WHERE active = 1
    SQL;

Кінцеві коми у викликах функцій і методів

У виклику після останнього аргумента дозволена зайва кома — зручно для багаторядкових diff і згенерованих списків аргументів:

$this->logger->info(
    'User saved',
    [
        'id' => $user->id,
        'ip' => $request->ip(),
    ],
);

Йдеться саме про виклики; не плутайте з оголошенням параметрів функцій в інших версіях PHP.

Присвоєння за посиланням у list() та []

Деструктуризація підтримує посилання:

[&$head, $middle, &$tail] = $parts;
list(&$a, $b) = $pair;

Використовуйте свідомо, коли треба змінювати спільну структуру; інакше краще звичайні значення.

instanceof з літералами

Зліва може бути літерал — результат завжди false. Це насамперед для узгодженості та статичного аналізу, не для бізнес-логіки.

CompileError

З’явився CompileError; від нього наслідується ParseError. На практиці це помітно в token_get_all($code, TOKEN_PARSE) і в інструментах: частина ситуацій замість fatal може приходити як виняток.

Нові та оновлені помічники ядра

  • array_key_first() / array_key_last() — без обхідних шляхів через reset()/end() (для порожніх масивів див. мануал).
  • is_countable($value)true для масивів і об’єктів з Countable.
  • hrtime($as_number = false) — монотонний час із високою роздільністю; для інтервалів зручніше за «календарний» wall-clock.
  • net_get_interfaces() — перелік мережевих інтерфейсів (залежить від ОС).

JSON: JSON_THROW_ON_ERROR і JsonException

Прапорець JSON_THROW_ON_ERROR для json_encode / json_decode дає JsonException замість ланцюжка json_last_error(). Якщо разом з JSON_PARTIAL_OUTPUT_ON_ERROR, пріоритет у останнього.

$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);

Хешування паролів: Argon2id

За збірки з відповідною libargon2 доступна PASSWORD_ARGON2ID для password_hash() і пов’язаних функцій — реальна наявність залежить від дистрибутива та прапорів збірки.

Важливі зміни розширень і stdlib

  • PCRE: рушій PCRE2 — можливі тонкі відмінності; preg_quote() екранує також #.
  • mbstring: Unicode 11, рядки >2 ГБ, швидші регістронезалежні операції; case-folding; іменовані групи в mb_ereg_* ближче до стилю PCRE (перевірте легасі-код).
  • LDAP: підтримка controls у пошуку/модифікації та ldap_parse_result().
  • MySQLi / PDO MySQL: у результатах native prepare можуть з’явитися дроби секунд для DATETIME(6) / TIMESTAMP(6).
  • FPM: параметри логів; getallheaders() доступна під FPM.
  • Сесії та cookie: session_set_cookie_params(array), INI session.cookie_samesite; setcookie / setrawcookie з масивом опцій, зокрема samesite.
  • Потоки: IPv6 у stream_socket_get_name() у квадратних дужках (наприклад [::1]:1337).
  • SPL autoload: якщо автозавантажувач кидає виняток, наступні не викликаються (раніше ланцюжок поводився інакше).
  • IMAP: rsh/ssh за замовчуванням вимкнено — вмикайте лише свідомо (imap.enable_insecure_rsh).

Практичні рецепти

Перед count()

if (is_countable($rows)) {
    $n = count($rows);
}

Перший і останній ключ

$firstKey = array_key_first($map);
$lastKey = array_key_last($map);

Суворий JSON

try {
    $payload = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    // обробка або обгортка
}

Інтервал із високою роздільністю

$t0 = hrtime(true);
// ...
$elapsedNs = hrtime(true) - $t0;

Зворотно несумісні зміни (міграційні нотатки)

Ядро

  • Гнучкий heredoc/nowdoc: збіг із міткою закриття всередині тіла — ризик помилки розбору або зміни змісту; перейменуйте мітку або перепишіть текст.
  • continue у switch: тепер попередження (у PHP це еквівалентно break, в інших мовах очікували б continue 2). Перевірте всі switch + continue.
  • ArrayAccess і рядкові «числові» ключі: для літерала "123" немає неявного приведення до int — викликається offsetGet("123"), а не offsetGet(123). Звичайні масиви не змінюються.
  • Статичні властивості та посилання: не можна «розділити» успадковане статичне поле присвоєнням посилання між класами — закрито в реалізації.
  • Посилання з ланцюжків [] / ->: посилання негайно згортаються; проміжні трюки з «підвішеним» посиланням не працюють.
  • Розгортання ...$traversable: для ітераторів із нецілими ключами розгортання не підтримується (у 7.2 частина випадків працювала випадково).
  • Винятки з режиму EH_THROW: не заповнюють error_get_last().
  • TypeError: у тексті типи int / bool, не integer / boolean.
  • compact(): неоголошені імена дають notice.
  • getimagesize() і подібні: BMP — MIME image/bmp, не image/x-ms-bmp.
  • Cookie (з PHP 7.3.23): імена вхідних cookie не URL-декодуються.

BCMath

  • Попередження йдуть через стандартний error handler, а не прямий запис у stderr.
  • bcmul() / bcpow() суворіше дотримуються scale (хвостові нулі можуть відрізнятися від старої поведінки).

IMAP

  • rsh/ssh вимкнено за замовчуванням; з недовіреними іменами скриньок це критично, якщо увімкнете знову.

mbstring regex

  • Іменовані групи змінюють склад $matches і підстановки в mb_ereg_replace().

MySQLi і PDO MySQL

  • Дроби секунд можуть «спливти» в інтеграційних тестах на точне рядкове представлення часу.

Reflection

  • У текстовому виводі — int / bool.

SimpleXML

  • Арифметика з текстовими вузлами обирає int/float адекватніше, ніж раніше.

Інше

  • Підтримку BeOS знято; ext_skel переписано (авторам розширень — оновити скрипти).
  • Формат IPv6 у stream_socket_get_name() з квадратними дужками.

Deprecated (виправити завчасно)

  • Регістронезалежні define() та звернення до константи в іншому регістрі, ніж оголошення.
  • Функція assert() у просторі імен — не оголошуйте.
  • Нерядковий needle у сімействі strpos — явний (string) або chr().
  • fgetss, gzgetss, SplFileObject::fgetss, фільтр string.strip_tags.
  • FILTER_FLAG_SCHEME_REQUIRED / FILTER_FLAG_HOST_REQUIRED для FILTER_VALIDATE_URL.
  • image2wbmp().
  • Normalizer::NONE при ICU ≥ 56.
  • Аліаси mbereg_*mb_ereg_*.
  • INI pdo_odbc.db2_instance_name формально deprecated.

Повний перелік: migration73 deprecated.

Інші зміни та експлуатація

  • Syslog INI: syslog.facility, syslog.filter, syslog.ident; з 7.3.8 фільтр raw відновлює старе агресивніше логування.
  • Збирання циклів (GC): покращення — профілі пам’яті/часу довгоживучих воркерів можуть зміститися.
  • var_export() для stdClass через (object) array (...).
  • FTP: режим за замовчуванням — binary.
  • FILTER_VALIDATE_FLOAT: опція thousand для роздільників тисяч.
  • OpenSSL: min_proto_version / max_proto_version для потоків.
  • PDO SQLite: лише читання через PDO::SQLITE_ATTR_OPEN_FLAGS.
  • XML: значення, що повертає обробник зовнішніх сутностей, враховується при збірці з libxml.
  • cURL: потрібна libcurl ≥ 7.15.5.
  • OPcache: прибрано opcache.inherited_hack.
  • ODBC: прибрано Birdstep / ODBCRouter.

Підсумок

Сфокусуйте рев’ю на continue+switch, compact(, реалізаціях ArrayAccess, heredoc-мітках та ...$iterator. Прогоніть тести з регулярними виразами та датою/часом, далі плануйте 7.4 — там уже typed properties і ширший крок; чим раніше приберете deprecations 7.3, тим спокійніша наступна міграція.