Миграция с PHP 7 на 8: что ломается и как это чинить
PHP 7 не получает обновлений с ноября 2022 года. Разбираем, какие изменения в PHP 8 роняют рабочий код: TypeError вместо предупреждений, новое приведение чисел к строкам и удалённые функции.

После обновления приложения с PHP 7.4 на 8.1 форма оплаты начинает возвращать TypeError: strlen(): Argument #1 ($string) must be of type string, null given там, где раньше молча работал strlen(null). Это не баг вашего кода — это следствие того, что PHP 8 ужесточил правила, за которые 7.x только ругался в лог. Ниже — конкретные изменения между 7.4 и 8.x, которые чаще всего ломают прод, с текстом ошибок, которые вы реально увидите, и что с каждым делать.
Статья про несовместимости при переходе с ветки 7.x на 8.0–8.3. Настройку окружения, выбор менеджера версий и обновление зависимостей Composer здесь не разбираем — это отдельная тема. Фокус на том, что в неизменном коде поведёт себя иначе.
Почему вообще надо мигрировать
PHP 7.4 — последняя версия ветки 7 — получила финальный security-фикс в ноябре 2022 года. Дыры, найденные после этой даты, в 7.x официально не закрываются. Хостинги постепенно снимают PHP 7 с тарифов, а свежие версии популярных пакетов (Symfony, Laravel, современные драйверы) уже требуют минимум 8.1. Так что вопрос не в том, мигрировать ли, а в том, что сломается в процессе.
Ошибки вместо предупреждений: главная причина падений
Самое болезненное изменение PHP 8.0 — многие ситуации, которые в 7.x давали Warning или Notice и продолжали выполнение, теперь бросают TypeError или ValueError и валят запрос.
Строковые функции и null
Передача null во внутренние функции, ожидающие строку, в 8.0 вызывает deprecation-предупреждение, а начиная с 8.1 это deprecated особенно шумно; в некоторых сборках и конфигурациях — фатальную ошибку. Типовой случай:
// PHP 7.4: strlen(null) вернёт 0 с Warning
// PHP 8.1+: Deprecated: passing null to non-nullable parameter
$len = strlen($_GET['name']); // если параметра нет, там nullЧинится явной проверкой или приведением:
$name = $_GET['name'] ?? '';
$len = strlen($name);Обращение к несуществующему ключу массива
В 7.x $arr['missing'] давал Notice: Undefined index. В 8.0 это стало Warning — сообщение осталось некритичным, но теперь оно громче попадает в логи и может проваливать тесты, настроенные на нулевой вывод ошибок. Оператор ?? закрывает вопрос:
$city = $data['city'] ?? 'не указан';
Изменения в приведении типов
Числа и строки сравниваются иначе
Одно из самых незаметных и опасных изменений 8.0 — новая логика нестрогого сравнения числа и строки. Раньше 0 == 'foo' было true, потому что строка приводилась к числу 0. Теперь число приводится к строке, и 0 == 'foo' стало false.
| Сравнение | PHP 7.4 | PHP 8.0+ |
|---|---|---|
| 0 == "foo" | true | false |
| 0 == "" | true | false |
| "1" == "01" | true | true |
| 0 == "0" | true | true |
| "abc" == 0 | true | false |
Если где-то в коде проверка вида if ($result == 0) использовалась как «пустое или нечисловое», после миграции она начнёт вести себя иначе. Ищите такие места и переводите на строгое сравнение === там, где важен тип.
Конкатенация и математика имеют разный приоритет
В 8.0 изменился приоритет операторов: конкатенация . теперь ниже, чем + и -. Выражение echo "Сумма: " . $a + $b в 7.4 могло дать неожиданный результат из-за одинакового приоритета, а в 8.0 сложение выполнится первым. Если у вас есть такие смешанные выражения без скобок, поведение поменяется. Решение простое — расставляйте скобки явно: "Сумма: " . ($a + $b).
Удалённые и изменённые функции
Часть функций, помеченных deprecated в 7.x, в 8.0 удалена. Если код на них опирается, вы получите Fatal error: Uncaught Error: Call to undefined function.
create_function()— удалена. Замена: анонимные функцииfunction () { ... }или стрелочныеfn() =>.each()— удалена. Замена:foreach.- Магические кавычки (
get_magic_quotes_gpc()и связанные) — удалены ещё в 7.0, но код старше мог их проверять. money_format()— удалена в 8.0. Замена: классNumberFormatterиз intl.- Модификатор
/eвpreg_replace()удалён в 7.0; замена —preg_replace_callback().
Самая коварная категория — не удалённые функции, а те, что теперь бросают исключение вместо возврата false. Проверяйте не только «что исчезло», но и «что стало строже».
Классы и методы стали строже
Приватные методы больше не переопределяются как раньше
В 8.0 сигнатуры приватных методов родителя больше не влияют на потомков — раньше это иногда давало предупреждения при наследовании. Обратная сторона: несовместимые сигнатуры публичных и защищённых методов при наследовании теперь чаще приводят к фатальной ошибке, а не к предупреждению.
Абстрактные методы в трейтах проверяются жёстче
Если трейт объявляет абстрактный метод с типизированной сигнатурой, класс, использующий трейт, обязан ей соответствовать. В 7.x расхождение прощалось, в 8.0 — ошибка.
Как провести миграцию без сюрпризов на проде
Порядок, который снижает риск получить падение в бою:
- Поднимите PHP 8.x в отдельном окружении — контейнер или тестовый сервер, не трогая прод. Версию выбирайте не самую свежую, а ту, что поддерживают все ваши зависимости (часто это 8.1 или 8.2).
- Прогоните статический анализ. Инструменты вроде PHPStan или Psalm ловят несовместимости до запуска. Отдельно есть Rector с наборами правил для апгрейда синтаксиса — он умеет автоматически переписывать часть конструкций под новую версию.
- Включите полное логирование ошибок в тестовом окружении:
error_reporting(E_ALL)иdisplay_errorsв лог, а не на экран. Deprecated-предупреждения покажут проблемные места до того, как они станут фатальными в следующей версии. - Прогоните тесты. Если тестов нет, пройдите вручную ключевые сценарии: авторизацию, оплату, формы, экспорт. Особое внимание — местам с нестрогим сравнением и обработкой пользовательского ввода.
- Выкатывайте постепенно. Если инфраструктура позволяет — на часть трафика, с мониторингом 500-х ответов и роста ошибок в логе.
Если после миграции посыпались 500-е
- Fatal error: Uncaught TypeError ... null given — где-то в строковую функцию попал null. Ищите необработанный
$_GET/$_POST/поле из базы, добавляйте?? ''. - Call to undefined function — вызвана удалённая функция. Смотрите список выше, ищите замену.
- Uncaught ValueError — функция получила аргумент вне допустимого диапазона (например, отрицательную длину в
str_repeatили неверный режим). В 7.x возвращала false, теперь бросает. - Declaration ... must be compatible — несовместимая сигнатура при наследовании. Приведите метод потомка к сигнатуре родителя.
- Логика «поехала», а ошибок нет — почти наверняка изменившееся сравнение число/строка. Проверяйте
==в условиях, где сравниваются потенциально нечисловые строки с нулём.
Что делать дальше
После того как приложение запустилось на 8.x без фаталов, не считайте миграцию завершённой. Оставьте E_ALL в логах на пару недель и следите за deprecated-предупреждениями: то, что в 8.1 всего лишь deprecated, в 8.2 или 8.3 может стать ошибкой, и лучше закрыть эти места заранее. Отдельно посмотрите, какую версию вы выбрали как целевую: если это 8.1, спланируйте следующий шаг на 8.2/8.3, пока разница между ними ещё небольшая. И проверьте актуальность списка изменений на странице UPGRADING в репозитории PHP — там перечислены несовместимости для каждой конкретной версии.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: PHP: Migrating from PHP 7.4.x to PHP 8.0.x — официальное руководство, PHP: Backward Incompatible Changes (migration80.incompatible)
Частые вопросы
На какую версию PHP 8 мигрировать — сразу на самую новую?
У меня старый код без тестов. Как понять, что сломается, не запуская в бой?
А если после обновления сайт работает, но кое-где неправильно считает?
Обязательно ли переписывать код, который вызывает удалённые функции?
Поведение отличается между 8.1, 8.2 и 8.3 — на что смотреть?
Можно ли откатиться на PHP 7, если что-то пойдёт не так?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.