Что значит «мигрировать PHP»
Миграция между версиями PHP — это не просто поменять php:8.2-fpm на php:8.4-fpm в Dockerfile. В проекте одновременно меняются язык, стандартная библиотека, расширения, Composer-зависимости, настройки рантайма и иногда поведение фреймворка. Поэтому обновление PHP ближе к controlled rollout, чем к обычному апдейту пакета.
На 15 июня 2026 года поддерживаемые ветки PHP — 8.2, 8.3, 8.4 и 8.5. При этом 8.2 уже находится в security support, 8.4 ещё в active support, а 8.5 — самая новая стабильная ветка. Это важно для выбора цели: обновляться «на ближайшую возможную» версию иногда разумно, но для нового production-плана лучше смотреть на ветку с нормальным горизонтом поддержки. Жизненный цикл версий подробно связан с Версии PHP и режимы выполнения, а техническая часть почти всегда упирается в composer.lock, install/update и audit.
flowchart TD
A[Выбрать целевую версию PHP] --> B[Прочитать migration guide]
B --> C[Проверить composer.json и composer.lock]
C --> D[Обновить dev-инструменты]
D --> E[Запустить CI matrix]
E --> F[Разобрать deprecations и несовместимости]
F --> G[Проверить расширения и SAPI]
G --> H[Staging smoke tests]
H --> I[Production rollout]
I --> J[Логи, healthchecks, rollback-план]Что читать в migration guide
Официальные migration guides PHP обычно делят изменения на несколько групп: new features, new functions/classes/constants, backward incompatible changes, deprecated features, removed extensions и other changes. Для обновления production-кода самые важные разделы — не новые фичи, а несовместимости и deprecations.
Backward incompatible change — это изменение, из-за которого старый код может начать вести себя иначе или падать. Например, в PHP 8.4 часть ошибок стала выбрасываться как ValueError или TypeError, некоторые resource-значения были переведены в objects, а exit() стал вести себя ближе к функции и учитывать strict_types. Такой код не всегда ломается на этапе composer install; он может проявиться только в тестах или в редком runtime-сценарии. Поэтому статья Ошибки, исключения и Throwable здесь не теоретическая: при миграции надо понимать, где раньше был warning или fatal error, а теперь прилетает исключение.
Deprecation — предупреждение о будущем удалении или изменении поведения. Хороший пример из PHP 8.2 — динамические свойства объектов: создание $object->newProperty без объявления свойства стало deprecated, если класс явно не разрешил такое поведение. В легаси-коде это часто всплывает в DTO, ORM-моделях, старых hydration-слоях и тестовых double-объектах. Правильный фикс обычно не «заглушить warning», а объявить свойство, заменить структуру данных или осознанно использовать #[\AllowDynamicProperties]. Это напрямую касается Классы, объекты и видимость и Атрибуты и Reflection.
Composer как первый фильтр
Перед запуском приложения на новой версии PHP надо спросить Composer, вообще возможна ли такая платформа. Минимум — проверить constraints в composer.json:
{
"require": {
"php": "^8.2 || ^8.3 || ^8.4",
"ext-json": "*",
"ext-mbstring": "*"
}
}Если проект хочет перейти на PHP 8.5, а constraint разрешает только 8.4.* или >=8.4 <8.5, Composer честно заблокирует установку или обновление. Это не баг Composer, а защита от непроверенного рантайма. При этом ^8.4 сам по себе PHP 8.5 не блокирует: для Composer это диапазон >=8.4 <9.0. Иногда команда временно использует --ignore-platform-req=php+, чтобы проверить совместимость зависимостей с верхней границей, но это диагностический ход, а не production-практика.
Полезные команды:
composer validate --strict
composer outdated --direct
composer prohibits php 8.5
composer update --dry-run
composer check-platform-reqscomposer prohibits php 8.5 показывает, какие пакеты не дают перейти на целевую версию. composer check-platform-reqs проверяет реальную установленную версию PHP и расширения, причём не полагается на config.platform. Это важно после deploy: lock-файл мог собираться в CI под одной платформой, а production-FPM может жить с другим набором ext-*.
Lock-файл и стратегия зависимостей
Миграция PHP почти никогда не должна начинаться с хаотичного composer update всего проекта. Без плана вы одновременно меняете PHP, framework, десятки библиотек и dev-инструменты. Когда после этого падают тесты, непонятно, кто виноват.
Более управляемая схема:
1. Зафиксировать текущий composer.lock и убедиться, что composer install воспроизводим.
2. Обновить dev-инструменты, которые должны понимать новую версию PHP: PHPUnit, PHPStan или Psalm, PHP-CS-Fixer/PHP_CodeSniffer.
3. Найти blockers через composer prohibits php <version>.
4. Обновить только необходимые runtime-зависимости.
5. Запустить полный pipeline из CI, линтеры и автоматические проверки.
6. Прогнать приложение на новой версии PHP в staging или отдельном окружении.
Если пакет больше не поддерживается, Composer может показать это через audit/outdated-процессы, но решение всё равно архитектурное: заменить пакет, поднять major-версию с миграцией API или оставить проект на старой ветке PHP до рефакторинга.
Что ломается чаще всего
Первый класс проблем — типы. Новые версии PHP постепенно делают стандартные функции строже: там, где раньше был warning, может появиться ValueError; там, где значение молча приводилось, теперь важен TypeError. Код с точными сигнатурами, strict_types, PHPDoc и статическим анализом обычно переносится легче. Поэтому Типы и strict_types и PHPDoc, generics и статический анализ — практическая база для миграций, а не «красота типов ради типов».
Второй класс — расширения. Проект может быть совместим с PHP 8.4 на уровне языка, но не иметь готового ext-imagick, ext-redis, ext-swoole или драйвера БД для нужного образа. Особенно это видно в Docker и CI: PHP обновили, а PECL-расширение не собрано или требует другой системной библиотеки. Проверять надо не только php -v, но и php -m, composer check-platform-reqs, smoke tests и реальные entrypoints: CLI, FPM, queue workers, cron.
Третий класс — долгоживущие процессы. Классический request-response под PHP-FPM часто прощает больше: процесс обработал запрос и умер или вернулся в пул. В RoadRunner, Swoole, очередях и воркерах состояние живёт дольше, поэтому миграция может вскрыть проблемы с singletons, кешами, статическими переменными и обработкой исключений. Это смыкается с PHP-FPM, RoadRunner и долгоживущие воркеры и Очереди, фоновые задачи и воркеры.
Как ловить deprecations до production
На staging и в CI полезно включать максимально шумный error reporting, но не показывать ошибки пользователю:
error_reporting = E_ALL
display_errors = Off
log_errors = OnВ тестах deprecations часто превращают в отдельный quality gate. Например, Symfony/PHPUnit-экосистемы умеют считать deprecation notices; в другом проекте можно начать проще: логировать E_DEPRECATED, выгружать их из CI artifacts и заводить issue на самые частые места. Главное — не ждать момента, когда deprecation станет fatal behavior change в следующей версии.
Пример типичного фикса PHP 8.2:
// Было: свойство создаётся на лету и даёт deprecation.
final class UserView
{
}
$user = new UserView();
$user->name = 'Ada';
// Лучше: состояние явно описано в классе.
final class UserView
{
public function __construct(
public string $name,
) {}
}
$user = new UserView('Ada');Такой фикс улучшает не только совместимость. Он помогает автодополнению, статическому анализу, сериализации и тестам. Хорошая миграция часто оставляет код понятнее, чем он был до неё.
Production-план
Безопасный rollout обычно идёт в две фазы. Сначала проект становится совместимым со старой и новой версиями PHP: зависимости обновлены, deprecations разобраны, CI гоняется на matrix, например 8.3 и 8.4. Потом меняется runtime: Docker image, серверный пакет PHP, FPM pool, CLI для cron и worker-сервисов.
Пример matrix в GitHub Actions:
strategy:
matrix:
php-version: ['8.3', '8.4']
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
coverage: none
- run: composer install --no-interaction --prefer-dist
- run: composer checkПосле переключения версии PHP надо проверять не только web-запрос. Нужны php -v, composer check-platform-reqs --no-dev, healthcheck, авторизация, запись в БД, загрузка файлов, queue worker, cron и команды CLI. Разные SAPI могут смотреть на разные php.ini, поэтому CLI «зелёный» не доказывает, что FPM настроен так же. Это связано с SAPI и суперглобалы и CLI и встроенный сервер.
См. также
- Версии PHP и режимы выполнения — жизненный цикл веток PHP, CLI/FPM/CGI и выбор runtime.
- composer.lock, install/update и audit — как lock-файл влияет на воспроизводимость миграции.
- CI, линтеры и автоматические проверки — где закрепить matrix, static analysis, tests и audit.
- PHPDoc, generics и статический анализ — как находить несовместимости до runtime.
- Ошибки, исключения и Throwable — почему новые
TypeErrorиValueErrorважны для миграций. - PHP-FPM, RoadRunner и долгоживущие воркеры — почему смена версии PHP по-разному влияет на request-response и long-running процессы.