Что значит «мигрировать 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-план]
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-план]
Практический маршрут миграции PHP: сначала совместимость кода и зависимостей, затем смена runtime и проверка production-поведения.
Quick recall
Почему миграцию PHP нельзя сводить к замене Docker image вроде `php:8.2-fpm` на `php:8.4-fpm`?

Что читать в 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.

Quick recall
В migration guide PHP какие разделы важнее всего читать для production-обновления, если цель — не новые фичи, а снижение риска?

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-reqs

composer prohibits php 8.5 показывает, какие пакеты не дают перейти на целевую версию. composer check-platform-reqs проверяет реальную установленную версию PHP и расширения, причём не полагается на config.platform. Это важно после deploy: lock-файл мог собираться в CI под одной платформой, а production-FPM может жить с другим набором ext-*.

Quick recall
Что показывает команда `composer prohibits php 8.5` при подготовке миграции?

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 и встроенный сервер.

См. также

Sources

  1. PHP: Supported Versions
  2. PHP Manual: Migrating from PHP 8.4.x to PHP 8.5.x
  3. PHP Manual: Migrating from PHP 8.3.x to PHP 8.4.x
  4. Composer CLI: check-platform-reqs
  5. PHP Manual: Deprecated Features in PHP 8.2
  6. PHP Manual: Backward Incompatible Changes in PHP 8.4