Symfony: Обновление основной версии (c 5.4.0 до 6.0.0)

Источник: «Upgrading a Major Version (e.g. 5.4.0 to 6.0.0)»
Каждые два года Symfony выпускает новый релиз основной/мажорной версии (изменяется первый номер). Эти выпуски довольно сложно обновить, поскольку они могут нарушать обратную совместимость. Однако, Symfony максимально упрощает процесс обновления. Это означает, что вы можете обновить большую часть своего кода до того, как основная версия будет выпущена в релиз. Это называется "сделать ваш код совместимым с будущим релизом".

Что бы обновиться до основной версии нужно выполнить несколько шагов:

  1. Избавится от устаревшего кода;
  2. Обновится до новой основной версии через Composer;
  3. Обновить ваш код для работы с новой версией.

1. Избавится от устаревшего кода

В течении жизненного цикла основного/мажорного выпуска добавляются новые функции, изменяются сигнатуры методов и публичный API. Однако, младшие/минорные версии не должны содержать никаких обратно несовместимых изменений. Для этого "старый" код (функции, классы и т.д.) помечается как устаревший (deprecated) - это указывает, что он всё ещё работает, но в ближайшем будущем будет удалён или изменён. Вам следует отказаться от его использования.

Когда выпускается старшая основная/мажорная версия (например, 6.0.0), весь устаревший функционал удаляется. Итак, если вы обновили свой код и прекратили использовать устаревший функционал в последних версиях (например, 5.4.*), вы сможете обновиться без проблем. Это значит, что вы должны обновиться до последней минорной версии (например, 5.4.0), что бы увидеть весь устаревший код.

Чтобы помочь вам найти устаревший функционал, уведомления срабатывают каждый раз, когда вы используете устаревшую функцию Symfony. При запуске приложения в среде разработки в вашем браузере эти уведомления отображаются на панели инструментов разработчика:

Symfony Profiler - Log Messages

В конечном итоге ваша цель прекратить использовать устаревший функционал. Иногда предупреждение может сказать вам, что именно нужно изменить.

Но в некоторых случаях предупреждение может быть неясным: триггер может срабатывать где-то в недрах классов. В этом случае Symfony делает всё возможное, что бы дать чёткое сообщение, но вам может потребоваться более глубокое изучение предупреждения.

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

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

Устаревший код в PHPUnit

Когда вы запускаете свои тесты с PHPUnit, уведомления об устаревшем коде не показываются. Чтобы помочь с этим Symfony предоставляет компонент PHPUnit bridge. Этот компонент покажет вам красивую сводку всех уведомлений об устаревании в конце отчёта о тестировании.

Всё что вам нужно сделать для установки PHPUnit bridge:

composer require --dev symfony/phpunit-bridge

Теперь вы можете приступить к исправлению уведомлений:


$ ./bin/phpunit
...

OK (10 tests, 20 assertions)

Remaining deprecation notices (6)

The "request" service is deprecated and will be removed in 3.0. Add a type-hint for
Symfony\Component\HttpFoundation\Request to your controller parameters to retrieve the
request instead: 6x
3x in PageAdminTest::testPageShow from Symfony\Cmf\SimpleCmsBundle\Tests\WebTest\Admin
2x in PageAdminTest::testPageList from Symfony\Cmf\SimpleCmsBundle\Tests\WebTest\Admin
1x in PageAdminTest::testPageEdit from Symfony\Cmf\SimpleCmsBundle\Tests\WebTest\Admin

Как только вы их все исправите команда завершится с кодом 0 (успешно), и всё готово!

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

Использование режима Weak Deprecation

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

<!-- phpunit.xml.dist -->
<phpunit>
<!-- ... -->

<php>
<env name="SYMFONY_DEPRECATIONS_HELPER" value="max[total]=999999"/>
</php>
</phpunit>

Вы так же можете сделать это с помощью команды:

SYMFONY_DEPRECATIONS_HELPER=max[total]=999999 php ./bin/phpunit

2. Обновится до новой основной версии через Composer

Когда ваш код освободится от устаревшего кода, вы можете обновить библиотеку Symfony через Composer, изменив файл composer.json и изменив все библиотеки, начиная с symfony/, на новую основную версию:

{
"...": "...",

"require": {
- "symfony/cache": "5.4.*",
+ "symfony/cache": "6.0.*",
- "symfony/config": "5.4.*",
+ "symfony/config": "6.0.*",
- "symfony/console": "5.4.*",
+ "symfony/console": "6.0.*",
"...": "...",

"...": "A few libraries starting with
symfony/ follow their own versioning scheme. You
do not need to update these versions: you can
upgrade them independently whenever you want",
"symfony/monolog-bundle": "^3.5",
},
"...": "...",
}

Внизу вашего файла composer.json, в дополнительном блоке, вы можете найти настройки данных для версии Symfony. Обязательно обновите их. Например, обновите, как указано ниже, до версии 6.0.*, что бы перейти на Symfony 6.0:

"extra": {
"symfony": {
"allow-contrib": false,
- "require": "4.4.*"
+ "require": "6.0.*"
}
}

Затем используйте Composer для загрузки новых версий библиотек:

composer update "symfony/*"

Ошибки зависимости

Если вы получаете сообщения об ошибках зависимости, это может означать, что вам также необходимо обновить другие библиотеки, которые являются зависимостями библиотек Symfony. Чтобы разрешить это, установить флаг --with-all-dependencies:

composer update "symfony/*" --with-all-dependencies

Это обновит symfony/* и все пакеты, от которых зависят эти пакеты. Используя жёсткое ограничение версий в composer.json, вы можете контролировать, до каких версий обновится каждая библиотека.

Если это по-прежнему не работает, в вашем файле composer.json может быть указана версия библиотеки, которая не совместима с более новой версией Symfony. В этом случае обновление этой библиотеки до более новой версии в composer.json может решить эту проблему.

Или у вас могут быть более глубокие проблемы, когда разные библиотеки зависят от конфликтующих версий других библиотек. Проверьте сообщение об ошибке для отладки.

Другая проблема, которая может возникнуть, заключается в том, что зависимости проекта могут быть установлены на вашем локальном компьютере/сервере, но не на удалённом. Обычно это происходит, когда на разных машинах стоят разные версии PHP. Решение состоит в том, чтобы добавить параметр конфигурации платформы в ваш composer.json, что бы определить самую высокую версию PHP, разрешённую для зависимостей (установить версию PHP, которая стоит на сервере).

Обновление других пакетов

Вы так же можете обновить остальные свои библиотеки. Если вы хорошо поработали с ограничениями версий в composer.json, вы можете сделать это безопасно выполнив:

composer update

Остерегайтесь, если у вас есть некоторые неопределённые ограничения версий в вашем composer.json (например, dev-master), это может привести к обновлению некоторых не Symfony библиотек до новых версий, которые содержат изменения нарушающие обратную совместимость.

3. Обновление рецептов

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

Symfony Flex предоставляет несколько команд, которые помогут обновить ваши рецепты. Обязательно закоммитьте все несвязанные изменения, над которыми вы работаете перед тем как начать.

Команды recipes были введены в Symfony Flex 1.6

# посмотреть список всех установленных рецептов и каким требуются обновления
$ composer recipes

# посмотреть детальную информацию о специфических рецептах
$ composer recipes symfony/framework-bundle

# обновить специфические рецепты
$ composer recipes:install symfony/framework-bundle --force -v

Сложность этого процесса заключается в том, что рецепт update не выполняет никакого интеллектуального "обновления" кода. Вместо этого процесс обновления переустанавливает последнюю версию рецепта, что означает, что ваш пользовательский код будет полностью переопределён. После обновления рецепта, вам нужно тщательно выбрать, какие обновления вы хотите внести, и отменить остальные.

4. Обновить ваш код для работы с новой версией

В некоторых, редких случая следующая основная/мажорная версия может содержать нарушения обратной совместимости. Убедитесь, что вы прочитали UPGRADE-X.0.md (где X - новая основная/мажорная версия) включённый в репозиторий Symfony, что бы узнать о любых нарушениях обратной совместимости, о которых вам нужно знать.

Обновление до Symfony 6: Добавление Нативных Возвращаемых Типов

Symfony 6 будет иметь нативные возвращаемые типы PHP для (почти всех) методов.

В PHP, если у родителя есть объявление возвращаемого типа, любой класс реализующий или переопределяющий метод так же должен иметь возвращаемый тип. Однако, вы можете добавить возвращаемый тип до того, как родитель добавит его. Это означает, что важно добавить нативные возвращаемые типы PHP в свои классы до обновления Symfony 6.0. В противном случае вы получите ошибки "несовместимого объявления".

Когда включён режим отладки (обычно в среде разработки и тестирования), Symfony будет предупреждать о каждом объявлении несовместимого метода. Например, метод UserInterface::getRoles() в Symfony 6 будет иметь возвращаемый тип array. В Symfony 5.4 вы получите уведомление об этом и вам нужно буде добавить объявление возвращаемого типа в свой метод getRoles().

Что бы помочь справиться с этой проблемой Symfony предоставляет скрипт, который может автоматически добавлять возвращаемые типы. Убедитесь, у вас установлен компонент symfony/error-handler. После установки сгенерируйте полную карту классов с помощью composer dump-autoload -o и запустите скрипт для перебора карты классов и исправления любого несовместимого метода ./vendor/bin/patch-type-declarations:

# Make sure "exclude-from-classmap" is not filled in your "composer.json". Then dump the autoloader:

# "-o" is important! This forces Composer to find all classes
$ composer dump-autoload -o

# patch all incompatible method declarations
$ ./vendor/bin/patch-type-declarations

Эта возможность не ограничивается пакетами Symfony. Она также поможет вам добавить типы и подготовится к другим зависимостям в вашем проекте.

Поведение этого сценария можно изменить с помощью переменной окружения SYMFONY_PATCH_TYPE_DECLARATIONS. Значение этой переменной задаётся с помощью url-кодирования (например, param1=value2&param2=value2), доступны следующие параметры:

force- включает исправление возвращаемых типов, значение должно быть одним из:

php - Используемая версия PHP, например 7.1 не генерирует "объектные" типы (которые были введены в php 7.2). По умолчанию используется версия php, используемая при запуске скрипта.

deprecations - Установите 0 для отключения уведомлений об использовании устаревших функций. В противном случае выдаёт сообщение об устаревшем коде когда в дочернем классе пропущен возвращаемый тип, в то время, когда в родительском классе объявлен через аннотацию @return (значение по умолчанию - 1).

Если есть определённые файлы, которые нужно игнорировать, вы можете указать их через переменную окружения SYMFONY_PATCH_TYPE_EXCLUDE как регулярное выражение. Это регулярное выражение должно соответствовать полному пути к классу и каждое совпадение будет проигнорировано (например, SYMFONY_PATCH_TYPE_EXCLUDE="/tests\/Fixtures\//"). Классы в каталоге vendor/ всегда игнорируются.

Скрипт не заботится о стиле кодирования. Запустите средство исправления стиля кода, или PHP CS Fixer с правилами phpdoc_trim_consecutive_blank_line_separation, no_superfluous_phpdoc_tags и ordered_imports rules, после исправления типов.

Исправление типов для разработчиков ПО с открытым исходным кодом

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

  1. Во-первых, создайте минорный релиз (т.е. без нарушения обратной совместимости), добавьте типы, которые можно безопасно ввести и добавьте аннотацию @return в PHPDOC ко всем другим методам:

    # Add type declarations to all internal, final, tests and private methods.
    # Update the "php" parameter to match your minimum required PHP version
    $ SYMFONY_DEPRECATIONS_HELPER="force=1&php=7.4" ./vendor/bin/patch-type-declarations

    # Add PHPDoc to the leftover public and protected methods
    $ SYMFONY_DEPRECATIONS_HELPER="force=phpdoc&php=7.4" ./vendor/bin/patch-type-declarations

    После запуска скриптов проверьте свои классы и добавьте ещё @return в PHPDOC там, где они отсутствуют. Скрипт обновления устаревшего кода работает исключительно на основе информации из PHPDOC. Пользователи этого выпуска получат сообщения об устаревшем коде с указанием добавить в свой код недостающие возвращаемые типы из вашего пакета в свой код.

    Если вам не нужен дополнительный PHPDOC и все объявления методов уже совместимы с Symfony, вы можете безопасно разрешить ^6.0 для зависимостей Symfony. В противном случае следует перейти к следующему шагу.

  2. Создайте новый основной/мажорный релиз (т.е. с нарушением обратной совместимости), в котором добавьте типы ко всем методам:

    # Update the "php" parameter to match your minimum required PHP version
    SYMFONY_DEPRECATIONS_HELPER="force=2&php=7.4" ./vendor/bin/patch-type-declarations

Теперь вы можете безопасно разрешить ^6.0 для зависимостей Symfony.

Дополнительные материалы

Предыдущая Статья

CSS: Соотношение сторон или aspect-ratio

Следующая Статья

Новое в Symfony 5.4: Улучшения компонента Messenger