Сегодня, наконец, опубликован релиз 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? Если да, то каким был ваш опыт? Собираетесь ли переходить?

Комментарии (24)


  1. OtshelnikFm
    28.11.2019 13:51
    +1

    Спасибо. А замеры производительности были/будут — версия ниже и 7.4?


    1. pmurzakov Автор
      28.11.2019 15:15
      +2

      В этой статье я рассказывал про наши эксперименты. Если коротко, то на тестовом эндпоинте PHP 7.4 давал +10% в сравнении с PHP 7.2, включение preload давало ещё +10% сверху.
      Сколько будет в реальности — узнаем, когда перейдём, и, возможно, поделимся результатами, если они будут интересные. :)


      1. mirypoko
        29.11.2019 12:33

        мне интересно, а делали какие нибудь замеры что было бы, если бы вы перешли на другую платформу, вроде java, asp.net core и тд в плане производительности?


        1. Sannis
          29.11.2019 13:05
          +3

          Основная проблема — как это проверить не переписывая всё на "java, asp.net core и тд".


          1. mirypoko
            29.11.2019 16:35
            -1

            Например, взять пару каких-нибудь прожорливый методов API, переписать на другие технологии и посмотреть стало лучше или нет


            1. fiftin
              01.12.2019 17:37

              А для чего им это делать? Переписывать все на Java они явно не будут, даже если окажется что Java быстрее.


  1. stanislav-belichenko
    28.11.2019 14:03
    +1

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

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

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


  1. avolver
    28.11.2019 14:46
    +2

    пробовали ли вы уже PHP 7.4? Если да, то каким был ваш опыт?

    Перевезли prod, когда вышел 7.4RC6.
    Столкнулись с проблемой утечки памяти в расширении parallel (issue) — ждём реакции krakjoe, автора расширения и разработчика php.

    Также есть проблемы с типизированными свойствами при развороте тестовой БД из Doctrine-фикстур. При получении неинициализированных lazy-объектов сущности из reference-репозитория фикстуры выходит Entity::$property must not be accessed before initialization, при $em->flush().


  1. polyanin
    28.11.2019 15:27
    +1

    Кто-нибудь пробовал в связке с недавно вышедшим Symfony 4.4lts?


  1. youROCK
    28.11.2019 16:04

    Soft Mocks-то свою задачу выполнил, позволил вам обновиться без особых проблем?


    1. pmurzakov Автор
      28.11.2019 16:49

      Если только не считать то, что Soft Mocks сами(и частично работу с ними) пришлось править под 7.4 :)


      1. youROCK
        28.11.2019 16:51

        Да, это в целом понятно :). Вопрос в том, сколько усилий для этого пришлось приложить? Легче ли это чем править расширение runkit/uopz? В github.com/badoo/soft-mocks, к сожалению, не вижу правок под PHP 7.4 пока что (возможно, не туда глядел).


        1. Sannis
          28.11.2019 19:56

          Мы пока в процессе, ещё не публиковали, но уже скоро.
          Хотя зачем они тебе? :-)


          1. youROCK
            28.11.2019 20:00

            Мне они не нужны :). Мне просто любопытно.


  1. GitCodeDestroyer
    28.11.2019 19:34
    +2

    PHP 7.3 был итак уже очень быстрым и удобным. С Laravel — просто конфетка, но 7.4 — ТОРТ. Я уже пробовал эту версию языка на практике и показатели удивляют… Всем советую перейти на новую версию, не пожалеете!


    1. vlreshet
      28.11.2019 19:41

      Такой заметный прирост скорости?


      1. polyanin
        28.11.2019 22:53

        обновился в dev пока окружении, ускорение в среднем миллисекунд на 20. preload пока не использовался


      1. GitCodeDestroyer
        29.11.2019 16:20

        Вот скажем, если в ванильном PHP разница скорости запросов не велика, но в Laravel, когда я с базы экзекютю все данные, разница в скорости — где то 60-76 мс. По моему, в больших проектах каждая миллисекунда на счету. И ещё, форуме medium официально заявили, что до версии PHP-v8.x они будут апгрейдить исключительно для framework-ов. Посмотрим что из этого выйдет…


  1. AlexanderY
    29.11.2019 11:33

    А как у вас происходил именно переход? Вы часть серверов «отключали», обновляли и снова запускали в строй?

    Как обновление происходит у маленьких компаний, кто не располагает кластером серверов, а, допустим, лишь одним php-сервером? Процесс, должно быть, быстрый. Если кто обновлялся «на живую», расскажите.


    1. vlreshet
      29.11.2019 11:45

      Смотря на специфику проекта. Если 100%ый аптайм не требуется — можно ночью на выходных, например, «по-живому» мигрировать. Полежит минуток 20-30 сайт, да и всё. А вообще, начиная с 7-ой версии, PHP стал адекватнее располагаться по директориям, и не перезаписывать себя. То есть сейчас, если не ошибаюсь, можно при работающем 7.2 — поставить 7.4, поставить все екстеншны, а потом просто a2dismode php7.2 && a2enmod php7.4 && service apache2 restart (ну, это всё в случае с апачем). То есть даунтайм будет вообще пару секунд всего.


      1. Tatikoma
        29.11.2019 12:31

        nginx + php-fpm и того проще. Запускаешь отдельный инстанс php-fpm, всё настраиваешь, правишь сокет в конфиге nginx, делаешь systemctl reload nginx. Без простоя совсем.


  1. RC_Cat
    29.11.2019 12:33
    +1

    Как вы поставляете php на сервера? Компилируете? Самосборные пакеты из спеков?


    1. Sannis
      29.11.2019 13:07
      +1

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


  1. zviryatko
    30.11.2019 20:55

    А вы быстро рветесь в бой, предпочитаю подождать версси х.1 :)
    По поводу возвращаемых значений false|null|"" хорошо спасает phpstan, проще один раз отрефакторить но потом легче обновляться будет.