MAATRIX / Блог / Переход на новую мажорную версию PHP без падения сайта

Переход на новую мажорную версию PHP без падения сайта

MAATRIX

Хостер или дистрибутив рано или поздно перестаёт поддерживать вашу версию PHP, и рано или поздно приходит момент переезда на мажорную версию выше. Проблема в том, что мажорный апгрейд PHP — это не «поменял пакет, перезапустил php-fpm» — код, который годами спокойно работал, может начать падать с фатальными ошибками или тихо считать неправильно. Ниже — рабочая методика, которая проходит этот переход предсказуемо: с тестовым окружением, проверкой зависимостей, автоматическим поиском проблемных мест в коде и путём отступления, если что-то пошло не так.

Почему мажорный апгрейд PHP — это не рутинное обновление

Патч-версия (например, 8.2.15 → 8.2.20) почти всегда безопасна: это исправления багов и уязвимостей без изменения поведения языка — обновляется в общем окне обслуживания без отдельного анализа. Мажорная версия (7.4 → 8.0, 8.0 → 8.1, 8.1 → 8.3) — другая история, потому что между такими версиями PHP меняет:

  • Поведение языка. Например, в PHP 8.0 нестрогие сравнения между числом и нечисловой строкой изменились (0 == "foo" теперь false, а не true, как было раньше) — код, который полагался на старое поведение при валидации входных данных, начинает вести себя иначе без единой ошибки в логах.
  • Устаревшие (deprecated) и удалённые функции. create_function() удалена в PHP 8.0, each() удалена там же, $php_errormsg убрана, preg_replace() с модификатором /e не работает начиная с PHP 7.0. Если в проекте есть код пятилетней давности или старая библиотека без поддержки, он просто не запустится — сайт упадёт в белый экран или в 500-ю ошибку.
  • Изменения в стандартной библиотеке и типизации. PHP 8.1 сделал устаревшим передачу null в не-nullable параметры внутренних функций — это не фатальная ошибка, а E_DEPRECATED, но при error_reporting в проде, настроенном на показ всех уровней, лог логов может утонуть в шуме, а где-то это всё же аффектит поведение. PHP 8.2 сделал устаревшим создание динамических свойств у объектов ($obj->undeclaredProp = 1) — частый паттерн в самописных и старых сторонних классах.
  • Совместимость сторонних библиотек и расширений. Composer-пакет, драйвер БД, расширение вроде imagick или mongodb — каждый должен официально заявлять поддержку целевой версии PHP. Если автор библиотеки её не обновлял два года, апгрейд PHP на сервере может сломать именно её, а не ваш код.

Итог: чем старше и «наследственнее» проект, тем выше цена перейти на мажорную версию «на глаз» — и тем нужнее план ниже.

Шаг 1. Инвентаризация: что вообще держится на текущей версии

Прежде чем трогать что-либо, соберите полную картину зависимостей — без неё дальнейшие шаги превращаются в гадание.

# версия PHP и загруженные модули
php -v
php -m

# список прямых и транзитивных зависимостей composer с версиями
composer show -a

# минимальная требуемая версия PHP по каждому пакету
composer show -a | grep -i "php"

# какие пакеты вообще ограничивают версию PHP в require
grep -r '"php"' vendor/*/*/composer.json

Отдельно проверьте composer.json самого проекта — секцию require.php (например, "php": ">=7.4") и платформенные требования CI. Если в проекте используется фреймворк (Laravel, Symfony, WordPress с плагинами), сверьте матрицу поддержки версий фреймворка с версией PHP отдельно — у крупных фреймворков в документации всегда есть таблица «версия фреймворка → поддерживаемые версии PHP», и не любая связка мажорных версий фреймворка и PHP официально поддерживается.

Зафиксируйте результат инвентаризации текстом (файл в репозитории или тикет) — этот список станет чек-листом для следующего шага и точкой сверки, когда апгрейд будет завершён.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Шаг 2. Проверка совместимости каждой существенной зависимости

Для каждой библиотеки и расширения из инвентаризации нужно явно проверить: заявляет ли автор поддержку целевой версии PHP.

  • Откройте страницу пакета на Packagist или репозиторий на GitHub, посмотрите composer.json в актуальном релизе — секцию require.php. Если там "php": "^8.0", а вы переходите на PHP 8.3, пакет формально попадёт в диапазон, но стоит проверить changelog на предмет добавленной поддержки именно 8.3 — иногда ограничение снизу не значит проверенную совместимость сверху.
  • Проверьте, не помечен ли пакет как abandoned в выводе composer show (Composer выводит явное предупреждение) — заброшенные пакеты часто и есть источник проблем при мажорном апгрейде PHP, потому что их никто не адаптировал под новые версии.
  • Для PHP-расширений (не composer-пакетов, а .so-модулей вроде imagick, mongodb, redis, xdebug) проверьте страницу расширения в PECL или его репозиторий — там указывается, с какой версии PHP есть поддержка. Расширения компилируются под конкретный ABI PHP, и старая версия .so-файла физически не подключится к новому PHP без пересборки.
  • Если используется CMS с плагинами (WordPress, что часто встречается на VPS с общим хостингом), проверьте требования каждого активного плагина к версии PHP в его описании в каталоге — это отдельный источник несовместимостей, независимый от composer.

Составьте таблицу «библиотека → текущая версия → заявленная поддержка целевого PHP → нужно ли обновлять» — это даёт понятный план действий до апгрейда, а не после падения прода.

ЗависимостьЗаявленная поддержка PHPТребуется действие
guzzlehttp/guzzle 6.xдо PHP 7.4обновить до 7.x перед апгрейдом
doctrine/orm 2.6до PHP 8.0обновить до 2.14+ для PHP 8.1+
ext-imagickзависит от сборкипересобрать/обновить пакет из репозитория ОС
custom-legacy-lib (без composer)не документированоручной аудит кода библиотеки

Шаг 3. Автоматический поиск несовместимых конструкций в коде

Ручной построчный просмотр всего проекта на мажорном апгрейде не масштабируется — вместо этого есть специализированные утилиты статического анализа, которые находят именно проблемы совместимости с целевой версией PHP.

PHPCompatibility — набор правил для PHP_CodeSniffer, который сканирует код и находит использование удалённых функций, изменённого поведения и устаревших конструкций для конкретной версии PHP:

composer require --dev phpcompatibility/php-compatibility
vendor/bin/phpcs --config-set installed_paths vendor/phpcompatibility/php-compatibility
vendor/bin/phpcs -p ./src --standard=PHPCompatibility --runtime-set testVersion 8.3

Результат — список файлов и строк с конкретными несовместимостями («use of deprecated function create_function() removed in PHP 8.0» и так далее), с которым можно системно пройтись перед апгрейдом, а не наткнуться на них по одному в проде.

Rector идёт дальше и умеет не только находить, но и автоматически переписывать код под новую версию по готовым наборам правил:

composer require --dev rector/rector
vendor/bin/rector process src --set php81 --dry-run

Флаг --dry-run показывает diff без применения — так безопаснее оценить объём изменений, прежде чем разрешать автоматическую правку.

Дополнительно стоит прогнать статический анализатор (PHPStan или Psalm) на целевом уровне строгости — он находит логические ошибки типизации, которые в новой версии PHP из warning превращаются в fatal error (например, несовпадение типов в возвращаемом значении при включённом strict_types).

Шаг 4. Тестовое окружение: полный прогон до продакшена

Ни один статический анализатор не заменяет реальный запуск с реальными данными и реальным трафиком запросов — поэтому следующий обязательный шаг — отдельная копия сайта на новой версии PHP, полностью изолированная от прода.

Практическая схема на VPS:

  1. Разверните копию окружения на отдельном сервере или отдельном контейнере с целевой версией PHP. На Ubuntu/Debian это удобно сделать через PPA ondrej/php, которая позволяет ставить несколько версий PHP параллельно:
add-apt-repository ppa:ondrej/php
apt update
apt install php8.3-fpm php8.3-mysql php8.3-curl php8.3-gd php8.3-mbstring
  1. Скопируйте актуальный дамп базы данных и файлы приложения (без реальных пользовательских секретов, если есть регуляторные ограничения).
  2. Подключите тестовый php-fpm пул к копии через отдельный nginx-конфиг с отдельным доменом или поддоменом (staging-php83.example.com), чтобы у команды и у автоматических тестов был реальный URL для проверки.
  3. Прогоните на этой копии: автотесты (unit, functional, при наличии — end-to-end), ручной обход ключевых пользовательских сценариев (оформление заказа, авторизация, отправка форм), и обязательно включите отображение ошибок и логирование на максимальном уровне:
ini_set('display_errors', '1');
error_reporting(E_ALL);
  1. Просмотрите error_log php-fpm и веб-сервера за время прогона — именно тут всплывают deprecated-уведомления и fatal error, которые статический анализ мог пропустить (динамическое поведение, generated-код, eval).

Только после того как тестовое окружение отработало без критических ошибок несколько дней под реалистичной нагрузкой (не пять минут после деплоя), имеет смысл переходить к переносу на продакшен.

Шаг 5. Постепенный переход и план быстрого отката

Даже при чистом прогоне на тесте оставьте себе путь назад — тестовое окружение никогда не покрывает продакшен на 100% (реальные крайние случаи данных, редкие сценарии, интеграции с внешними API, которые ведут себя иначе на реальных ключах).

Держите старую версию PHP установленной рядом с новой. Та же PPA ondrej/php позволяет иметь php8.1-fpm и php8.3-fpm на одном сервере одновременно, каждый со своим сокетом:

/run/php/php8.1-fpm.sock
/run/php/php8.3-fpm.sock

Откат в этом случае — правка одной строки в конфиге nginx (fastcgi_pass unix:/run/php/php8.1-fpm.sock; вместо ...php8.3...) и systemctl reload nginx — без переустановки пакетов и без простоя дольше нескольких секунд.

Для крупных или составных проектов — переходите частями, а не всем сайтом сразу. Варианты, что можно перевести первым, чтобы ограничить масштаб возможного ущерба:

  • Второстепенный поддомен или микросервис с низким трафиком, а не основной домен.
  • Один из нескольких API-эндпоинтов, если приложение так устроено.
  • Небольшая доля трафика основного сайта — через split_clients в nginx, направляющий, например, 5% запросов на пул с новой версией PHP по хешу IP или cookie, остальные 95% остаются на старой:
split_clients "${remote_addr}${http_user_agent}" $php_pool {
    5%     new;
    *      old;
}

upstream php_old { server unix:/run/php/php8.1-fpm.sock; }
upstream php_new { server unix:/run/php/php8.3-fpm.sock; }

server {
    location ~ \.php$ {
        fastcgi_pass php_$php_pool;
    }
}

Такая схема даёт реальные продакшен-данные на новой версии при ограниченном риске: если что-то не поймано тестами, страдает малая доля трафика, а не весь сайт, и откат — это просто смена 5% на 0%.

Планируйте по срокам, а не по календарю «когда руки дойдут». У каждой ветки PHP есть период активной поддержки (обычно около двух лет) и последующий период только security-фиксов (ещё около года) — после чего версия не получает даже патчей безопасности. Начинайте проверку совместимости за несколько месяцев до конца активной поддержки текущей версии, а точно не после того, как она перестала получать обновления безопасности вообще — к этому моменту вы уже находитесь в зоне риска по уязвимостям, и на аккуратное тестирование с постепенным переходом времени, скорее всего, не останется, придётся переходить в спешке под давлением.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Можно ли обновить PHP сразу на два мажорных релиза, например с 7.4 на 8.3?

Технически можно, но методика та же, а объём несовместимостей выше — практичнее прогнать всю проверку (совместимость библиотек, статический анализ, тест на копии) сразу на конечной целевой версии, а не поэтапно через каждую промежуточную, если только объём изменений кода не окажется слишком большим для одного цикла тестирования.

Что делать, если критичная сторонняя библиотека не поддерживает новую версию PHP и не обновляется?

Варианты: форкнуть и адаптировать самостоятельно, найти форк сообщества с поддержкой (часто появляется для популярных заброшенных пакетов), заменить на альтернативную библиотеку с похожим функционалом, либо отложить апгрейд именно этого компонента и изолировать его на старой версии PHP через отдельный сервис или контейнер, пока не появится решение.

Нужно ли перекомпилировать PHP-расширения при переходе на новую мажорную версию?

Да, бинарные расширения (.so) собираются под конкретный ABI PHP и не переносятся между мажорными версиями напрямую — их нужно переустановить из репозитория пакетов для новой версии или пересобрать из исходников через PECL.

Сколько времени в среднем занимает мажорный апгрейд PHP для среднего проекта?

Сильно зависит от возраста кода и числа зависимостей — это не измеренная величина, которую стоит закладывать как норматив, но практика показывает, что инвентаризация и статический анализ занимают часы-дни, а вот прогон на тестовом окружении с реалистичной нагрузкой стоит планировать неделями, а не часами, особенно если есть внешние интеграции.

Обязательно ли использовать nginx для постепенного перехода, если сайт на Apache с mod_php?

Идея постепенного перехода через раздельные пулы удобнее всего реализуется именно с php-fpm (nginx или Apache с mod_proxy_fcgi), потому что можно держать несколько версий одновременно с разными сокетами; классический mod_php привязывает единственную версию PHP к процессу Apache, и параллельный запуск двух версий на одном хосте так не работает — в этом случае раздельный проход через тестовый сервер становится ещё важнее, потому что быстрого отката через смену сокета не будет.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →