Перехід на нові версії мови — це завжди компроміс між бажанням отримати свіжі фічі (JIT, Attributes, Constructor Property Promotion) і страхом зламати те, що роками стабільно працювало на продакшені.

Коли наш проєкт розрісся, а legacy-код почав помітно гальмувати розвиток, стало очевидно: залишатися на PHP 7.2 у 2026 році — це не просто технічний борг, це безпековий ризик. Оновлення до PHP 8.x обіцяло приріст продуктивності, але на практиці підкидало сюрпризи на кожному кроці.

У цій статті я поділюся реальним кейсом міграції, розберу головні граблі, на які я наступив (привіт, PHPExcel та Strict Notices), і дам покроковий чек-лист, який збереже ваші нерви.

Чому PHP 8 — це не просто "чергове оновлення"

Головна відмінність PHP 8 від попередників — курс на сувору типізацію та передбачуваність. Те, на що PHP 7.2 закривав очі, видаючи легкі Notice або Warning, PHP 8 перетворює на фатальні помилки (Fatal Error або TypeError).

Для legacy-проєктів це означає одне: якщо ваш код написаний неідеально, після зміни версії PHP він просто перестане працювати.

Мої головні факапи та як я їх вирішував

1. Смерть PHPExcel та перехід на PhpSpreadsheet

Мабуть, найбільший біль legacy-систем — це генерація звітів. Наш проєкт активно використовував бібліотеку PHPExcel для вивантаження великих масивів даних.

  • Проблема: PHPExcel офіційно мертвий. Він не просто застарів — його внутрішня архітектура ламається на PHP 8 через зміну логіки роботи з масивами та рядками. Спроба запустити генерацію звіту повертала білу сторінку або Fatal Error.

  • Рішення: Повна заміна на PhpOffice\PhpSpreadsheet. Хоча PhpSpreadsheet є прямим послідовником, це не була міграція «в один клік». Довелося переписати всі сервіси, які відповідали за експорт/імпорт, оскільки змінилися простори імен (Namespaces) та назви багатьох методів. Це навіть не десятки звітів. Це — сотні!

  • Порада: Якщо у вас багато кастомних звітів, закладайте на цей етап мінімум 50% всього часу міграції.

2. Війна з неоголошеними змінними та динамічними властивостями

У PHP 7.2 конструкції на кшталт такого коду були звичною справою:

// В PHP 7.2 це прощалося
$userStatus = $data['status']; // Якщо 'status' немає в масиві -> Notice: Undefined index
$this->newProperty = 'value';  // Динамічна властивість об'єкта -> Створиться автоматично

У PHP 8.0+ правила гри кардинально змінилися:

  • Undefined variables / indexes: Тепер спроба звернутися до неіснуючого індексу масиву викликає серйозний Warning. Якщо у вас увімкнено суворий режим обробки помилок, застосунок впаде. Нам довелося масово впроваджувати Null Coalescing Operator (??) та функцію isset().

  • Deprecated dynamic properties: Спроба присвоїти значення властивості класу, яка не була явно оголошена (наприклад, $this->customCache = ...), у PHP 8.2 викликає Deprecated, а в PHP 9 це буде Fatal Error. Ми пройшлися по всіх контролерах та моделях, щоб явно задекларувати властивості (public, protected або private).

3. Зміна логіки порівняння рядків та чисел (String to Number Comparison)

Це прихована бомба, яка може зламати бізнес-логіку непомітно для тестувальників. В PHP 7.2 порівняння 0 == 'foobar' повертало true, тому що PHP намагався привести рядок до числа, отримував 0 і казав, що все збігається.

В PHP 8.x логіку виправили: тепер, якщо рядок не схожий на число, число приводиться до рядка. Тобто 0 == 'foobar' тепер повертає false. Мені довелося перевірити сотні умов, де використовувалося нестроге порівняння ==, і замінити його на строге ===.

Покроковий план міграції, який спрацював у нас

Якщо ви плануєте оновлювати свій проєкт, рекомендую діяти за наступним алгоритмом:

Крок 1: Аудит залежностей (Composer)

Перед оновленням самого PHP запустіть composer outdated. Перевірте, які з ваших пакетів підтримують PHP 8. Якщо якісь бібліотеки закинуті розробниками (як у нашому випадку з PHPExcel), шукайте альтернативи заздалегідь.

Крок 2: Статичний аналіз коду

Не намагайтеся знайти всі помилки вручну. Використовуйте інструменти статичного аналізу. Вони підсвітять 90% потенційних проблем до того, як ви переключите версію PHP:

  • PHPStan або Psalm (налаштуйте хоча б 1-2 рівень суворості).

  • Rector — інструмент, який може автоматично переписати застарілий код під синтаксис PHP 8 (наприклад, замінити старі конструкції на match чи додати типізацію).

    Але, звісно, так звані "ніжні" блоки логіки я все одно змінював руками.

Крок 3: Налаштування логування

Переключіть локальне середовище на PHP 8, але у файлі php.ini увімкніть максимальне логування помилок:

Ini, TOML

error_reporting = E_ALL
display_errors = On

Покривайте кодом критичні шляхи (авторизація, оплата, замовлення) і дивіться в логи. Кожен Notice — це потенційна проблема в майбутньому.

Крок 4: Контейнеризація (Docker)

Я проводив всю міграцію в Docker. Це дозволило мені мати дві паралельні збірки проєкту: одну на PHP 7.2 для поточної підтримки, іншу на PHP 8.2 — для тестування міграції. Жодного конфлікту версій на локальних машинах.

Що ми отримали в результаті?

Окрім того, що код став чистішим і рефакторинг змусив нас позбутися тон legacy-сміття, я зафіксували реальні технічні покращення:

  1. Швидкість роботи: Час відповіді сервера (Time to First Byte) зменшився в середньому на 20-25% суто за рахунок оптимізації рушія PHP 8 та оновленого OPcache.

  2. Споживання пам'яті: Скрипти, що обробляють фонові завдання (cron), почали споживати менше оперативної пам'яті.

  3. Задоволення від розробки: Нові фічі (наприклад, іменовані аргументи та оператор ?->) дозволяють писати значно менше шаблонного коду.

Висновок

Міграція з PHP 7.2 на PHP 8 — це не легка прогулянка. Це повноцінний технічний проєкт, який вимагає уважності до деталей. Проте, цей крок необхідний для того, щоб ваш продукт залишався безпечним, швидким та привабливим для розробників.