Pull to refresh

Вышел PHP 7.4! Нюансы перехода на новую версию

Reading time 4 min
Views 27K
Сегодня, наконец, опубликован релиз PHP 7.4!


Его новые фичи уже были многократно описаны, в том числе и на Хабре. Это стрелочные функции, типизированные свойства классов и ещё много всякого синтаксического сахара. Но больше всего мы ждали новый релиз из-за производительности: в версии 7.4 не только появился preload, но и сам PHP стал значительно быстрее.

Плохая (или хорошая?) новость — с выходом PHP 7.4 прекращается активная поддержка PHP 7.2. Его последний релиз запланирован на середину декабря. Мы давно проводим эксперименты с PHP 7.4, а недавно активно занялись переходом на него, так как сейчас мы на уже почти не поддерживаемой версии 7.2.

Поздравляю всех с долгожданным релизом! А ниже расскажу немного о том, как мы переходим на новую версию.

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

Если сильно упростить, то можно выделить несколько шагов:

  • Добиваемся того, что unit-тесты начинают успешно проходить на новой версии.
  • Делаем обязательными прогоны тестов на новой версии для всех изменений кода (чтобы не пришлось повторять пункт 1, так как новый код пишется постоянно, и он может быть снова несовместимым).
  • Переключаем на новую версию девел-площадку, чиним проблемы и живём какое-то время в таком состоянии.
  • Повторяем это для стейджинга.
  • Плавно выкладываем на разные кластеры продакшена.

Наши правки в репозитории PHP


Проблема с preload (фикс)


В этот раз в процессе кое-что изменилось: так как мы очень ждали preload, начали проводить часть работы еще в июле, во время версии 7.4.0beta1. В итоге это вылилось для нас в достаточно большое количество времени, потраченного на отладку, ведь PHP 7.4 тогда был совсем сырым. Но с другой стороны, в результате мы нашли неприятный баг, починили его и отправили фикс в апстрим, чем помогли всему сообществу.

Дальше настало время заняться тестами.

Проблема с доступом к приватным свойствам (фикс)


Для того, чтобы вообще прогнать тесты, нужно обновить PHPUnit, SoftMocks и PHP-Parser как их часть. У нас большая кодовая база, и даже для обновления PHPUnit потребовалось переписать огромное количество тестов.

После того, как нам удалось прогнать тесты, мы увидели очень странное. Было много падений со следующей ошибкой:

PHP Fatal error: Cannot access private property ClassLoader::$classMap in vendor/composer/ClassLoader.php

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

Проблема осложнялась тем, что воспроизводилась нестабильно. Долгий дебаг при помощи gdb показал, что действительно в EG(fake_scope) по каким-то причинам оказывается не тот класс, в рамках которого происходит обращение к свойству, а другой, не связанный с ним.

После того, как мы нашли минимальный репродьюс-кейс (который, на минуточку, требует наличия трёх классов, автолоадера и Reflection), починили причину проблемы и запушили исправление в апстрим, оказалось, что эта проблема существовала ещё со времён PHP 7.3 (скорее всего, после вот этого изменения), весь мир жил с ней год и никому до нас она не мешала.

Правим несовместимости кода Badoo


Сейчас мы правим все несовместимости нашего кода с PHP 7.4. Подавляющее большинство несовместимостей для нас (больше сотни мест, >80% от всех несовместимостей) было вызвано добавлением ошибки «Trying to access array offset on value of type null/bool/int» (соответствующий RFC). Она возникает при использовании синтаксиса обращения к элементу массива на других типах данных. 

Проблему хорошо показывает вот такой пример:

$a = false;
var_dump($a[‘somekey’]);

// PHP 7.3: 
// NULL
// 
// PHP 7.4:
// Notice: Trying to access array offset on value of type bool in Command line code on line 1
// NULL

Навскидку кажется, что такого в реальном коде не должно встречаться, но, как показала практика, это достаточно частый случай: например, функция может возвращать массив в нормальном случае и false/null в случае ошибки, а дальше вверх по стеку информация о false/null теряется, и этот случай не обрабатывается отдельно. 

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

Второе по количеству доставленных проблем обновление — это изменение в работе method_exists(). Кстати, в настоящий момент информации о нём нет в release notes или upgrading guide. Суть его заключается в следующем:


class A1 { 
    private function priv() {} 
} 

class B1 extends A1 {} 

var_dump(method_exists(B1::class, 'priv'));

// PHP 7.3: bool(true)
// PHP 7.4: bool(false)

Эту особенность, опять же, сложно встретить в реальном коде. Но, как оказалось, мы ненамеренно активно эксплуатировали это в тестах.

Конечно, мы в разной степени сталкиваемся и с другими несовместимостями, в том числе с многочисленными изменениями, связанными с рефлексией (пример один, пример два), с изменениями в hexdec() и подобных, запретом array_key_exists() даже для ArrayAccess-объектов, с несовместимостями в различных библиотеках зависимостей, подключаемых через Composer, и даже со всякими экзотическими штуками вроде ставшим обязательным stream_set_option() для stream wrapper’ов при include’ах. Но в сумме затраты на адаптацию ко всем этим изменениям не сравнятся со случаем использования синтаксиса массивов на не-массивах.

В настоящий момент мы закончили работу с юнит-тестами: они полностью проходят на PHP 7.4. Ведём работу над API-тестами и до конца года планируем начать переключение различных кластеров и окружений.

Этой краткой заметкой хочу пригласить к обсуждению: пробовали ли вы уже PHP 7.4? Если да, то каким был ваш опыт? Собираетесь ли переходить?
Tags:
Hubs:
+100
Comments 27
Comments Comments 27

Articles