Доброго времени суток, Хабр!

Еще год назад мой процесс отладки кода в PHP заключался в двух строчках:

var_dump($variable);
die();

Периодически, конечно, приходилось использовать более «сложные» конструкции:

console.log(data);

echo json_encode($variable, JSON_UNESCAPED_UNICODE);
exit();

Нет, что вы! Я знал — в наше время не подобает культурному программисту заниматься этим

древним ремеслом
шутка про другое древнейшее ремесло

Но, честно говоря, я всегда боялся того, что не понимаю. В том числе и принтеров xDebug, в особенности, как все это дело настроить. В один прекрасный день у меня получилось это сделать на своей машине и в локальном проекте — радости не было предела. Спустя много месяцев я столкнулся с новой проблемой, как заниматься отладкой в PHPstorm через xDebug, если проект собирается удаленно докером через CI.

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

Для тех, кто не любит воду и хочет перейти непосредственно к сути настройки.

Почему стоит уйти от заплесневевших методов отладки и перейти на адекватные технологии?


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

В какой-то момент, я для себя осознал, что мне уже просто неудобно, и попытался подружить xDebug и PHPstorm при работе над локальным проектом. Беда в том, что большинство документаций и гайдов, которые я нашел, подразумевают, что читающий их человек довольно хорошо разбирается в предметной области и все понимает, в моем случае это было не так и на свою первую настройку xDebug я в сумме потратил 4-5 часов за 2 вечера. Это было довольно тяжело морально, я чувствовал себя бесконечно тупым. Тем не менее, настроить получилось, все работало!

Да, стало удобней, локально-дома, но на основной работе я занимался сайтами удаленно, и чаще всего у меня не было возможности либо выгрузить сайт локально (из-за слабой машины или неудобного процесса развертывания), либо влиять на настройки сервера из-за хостингов, поэтому вносил правки «на живую» и отладкой занимался через html-комментирование print_r (на той работе это было «нормально», хоть и не горжусь этим опытом).

image

Однако, 3 месяца назад я перешел в более крутую компанию и стал заниматься действительно серьезным проектом с высокой нагрузкой. И тут для меня многое изменилось. Инфраструктура и процесс разработки примерно такие: есть свой сервер GitLab, у каждого проекта есть свой репозиторий, в Jira приходят задачи, по номерам задачи создаешь ветку, при создании ветки с помощью CI автоматически создается своя песочница с сайтом, где ты спокойно работаешь, каждый push пересобирает ветку, по окончанию работ отдаешь на код-ревью, вливаешь ветку в мастер.

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

  • Добавляю 2 строчки
  • Пушу коммит
  • Жду 10 секунд
  • Проверяю, смотрю, что не так
  • Repeat

По приблизительным подсчетам, готовая к мержу ветка имела примерно 20% полезных коммитов и 80% коммитов отладки. Допустим, я закончил работу над веткой с 300 коммитами, из них 240 коммитов по сути просто отжирали 40 минут моего рабочего времени (и это только время ожидания сборки ветки, не учитывая те секунды, которые складываются в минуты, на то, чтобы добавить 2 строчки и потом их удалить).

image

В какой-то момент мне это надоело и я решил-таки настроить xDebug, чтобы процесс отладки стал менее затратным. К несчастью, мои нынешние коллеги либо не пользовались этой технологией (жду шутки про «Устроился в крутую компанию, где никто не пользуется xDebug'ом»), либо не знали\не помнили, как подружить IDE с xDebug'ом, в случае когда ветка собирается удаленно через CI, а так как я ни разу не devOps и как я упомянул выше, процесс настройки чего-либо является для меня достаточно мучительным процессом, это вылилось примерно в 6 часов чистого времени, чтобы наконец все заработало, и я понимал процесс, и это было бы достаточно удобно.

Процесс настройки


Я не буду вдаваться в подробности, как прикручивать CI, Docker, в общем, как собрать инфраструктуру, предполагается, что это уже все готово и осталось только настроить свое личное окружение.

Допустим, наш репозиторий имеет примерно такую структуру:

image

Для начала нам нужно проверить, есть ли в текущем образе сам xDebug, для этого можете воспользоваться phpinfo();

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

Настраиваем php.ini


Для того, чтобы в итоге все заработало, нам важны 2 настройки xDebug:

  • xdebug.remote_enable
  • xdebug.remote_host

В итоговой сборке удаленной ветки remote_enable должен быть включен, а в remote_host должен быть присвоен IP вашего компьютера в сети. Давайте включим эти настройки в наш билд.

Для начала нужно узнать где хранятся настройки php, они могут располагаться либо в /usr/local/etc/php/conf.d/php.ini, либо сам файл .ini может быть назван иначе, в моем случае это /usr/local/etc/php/conf.d/php-settings.ini. Узнать это можно из настроек собираемого образа.

Создаем в нашей ветке свои дополнительные настройки через тот же файл php-settings.ini, а расположим его в ./build_env/php/php-settings.ini
Прописываем в нем 2 вышеупомянутые настройки:
xdebug.remote_enable = on
xdebug.remote_host = IP.ВАШЕГО.КОМПЬЮТЕРА.ВСЕТИ


Далее нам нужно добавить этот файл к «родительским» настройкам образа. Я делаю это через volumes путем добавления в ./build_env/docker-compose/docker-compose.tmpl строчки:
- ${PROJECT_DIR}/build_env/php/php-settings.ini:/usr/local/etc/php/conf.d/php-settings.ini

Примерно так в итоге выглядит docker-compose.tmpl в моем проекте:

image

При следующей сборке ветки, можно проверить привязались ли новые настройки через тот же phpinfo();, если да — отлично, если нет — Вам не повезло и придется пройти тот же путь, что проделал я в первый раз :(

Настраиваем маппинги в PHPstorm


Далее нужно настроить сам PHPstorm. Я решил не использовать DBgp Proxy, чтобы не настраивать маппинги во всплывающем окне каждый раз. В моем случае я использую шаблон сервера, который будет содержать в себе необходимые маппинги.

Переходим в Settings / Preferences | Languages & Frameworks | PHP | Servers

  1. Создаем шаблон сервера
  2. Name: BRANCH
  3. host: любой, он не влияет
  4. port: любой, он не влияет
  5. Debugger: xDebug
  6. Ставим галку на Use path mappings
  7. Проставляем соответствующие маппинги, рабочие локальные папки должны соответствовать папкам на сервере, где располагаются собранные ветки, в моем случае собранные билды находятся в папке /var/www/builds/your_namespace/your_project/your_branch

image

Сохраняем эти настройки, менять мы их будем каждый раз, когда работаем с новой веткой. Например, если сегодня работаю с веткой web-2233 то поменяю маппинг на /var/www/builds/путь_до_билда/web-2233

Добавляем новую переменную окружения, чтобы IDE автоматически подтягивала маппинги


Теперь довольно важный и не самый очевидный момент. Когда мы начинаем дебаг, PHPstorm должен понимать, какие локальные файлы соответствуют файлам на удаленном сервере. Если сервер не передал ему конкретную установку, то появится всплывающее окно, в котором нужно проставить соответствия путей вручную. Для того, чтобы PHPstorm сразу брал маппинги от сервера с названием BRANCH нужно добавить в нашу сборку переменную окружения PHP_IDE_CONFIG

В ./build_env/docker-compose/docker-compose.tmpl создаем новую переменную окружения в environment:
PHP_IDE_CONFIG: ${PHP_IDE_CONFIG}

image

В .gitlab-ci.yml задаем эту переменную:
- export PHP_IDE_CONFIG="serverName=BRANCH"

image

Готово!


Нам не нужны расширения для браузера, не нужно передавать в get-параметры URL XDEBUG_SESSION_START=IDE_KEY, не нужно добавлять дополнительные конфигурации.

Достаточно просто включить прослушку image и обновить страницу сайта, как только мы наткнемся на первый брекпоинт, выполнение приложение остановится на нем

image

Спасибо за внимание, надеюсь эта статья будет полезна и кто-нибудь сэкономит время, не наступая на те же грабли, что и я :)

Источники, которые я использовал при первичной настройке:
https://gist.github.com/chadrien/c90927ec2d160ffea9c4
https://confluence.jetbrains.com/display/PhpStorm/Docker+Support+in+PhpStorm#DockerSupportinPhpStorm-DebuggingthePHPwebapplicationrunningintheDockercontainer

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


  1. m03r
    15.09.2018 10:03

    А могли бы просто написать автотесты и/или гонять их через phpdbg…


    1. peresada Автор
      15.09.2018 10:11

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

      Чтобы понимать о каких ньансах и проблемах я говорю:

      • Продакшен имеет версию PHP 5.3, тестовая среда 7.1
      • Кодовая база продакшена сильно отличается от мастер ветки
      • Продакшен не собирается докером
      • В силу специфики проекта и легаси-кода исправление пунктов выше затягиваются


      1. m03r
        15.09.2018 12:40

        Интересная ситуация, действительно. Думаю, краткое описание этих проблем стоит внести в статью, чтобы у читателей сложилось адекватное представление о нетривиальных условиях раобты.


        1. lubezniy
          16.09.2018 08:58

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


          1. m03r
            16.09.2018 10:09

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


            1. lubezniy
              16.09.2018 10:20

              Не спорю. Но на этапе переноса проекта с одной версии на другую бывают и расхождения, особенно если учитывать отличия PHP5 и PHP7.


      1. Fortop
        15.09.2018 22:39

        Когда прод 5.3, а тестовая 7.1, то нужно брать гвозди двухсотки и идти забивать их в голову девопсам и тимлидам.


        Так нельзя работать и разрабатывать.
        В конечном счёте есть Docker, который обещает равное окружение хотя бы на уровне версий пхп.


        1. peresada Автор
          16.09.2018 09:59

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


      1. ALexhha
        16.09.2018 02:27

        Если у вас на проде и дев/стейджинге разные окружения, то что и как вы тестируете?


        1. peresada Автор
          16.09.2018 09:51

          Мы и не тестируем, я как раз и говорю, что к тестам перейдем, когда исправим имеющиеся


          1. ALexhha
            17.09.2018 11:28

            Мы и не тестируем
            т.е. на прод вы выкатываете код вообще без каких либо тестов? Сурово!


            1. peresada Автор
              17.09.2018 12:01

              Имеется ввиду без автотестов, потому что автотесты подразумевают эмуляции в смежных сервисах, так как проект напрямую связан со взаимодействием с биллингом, BRAS, RADIUS


  1. dimoff66
    15.09.2018 17:24

    Беда в том, что большинство документаций и гайдов, которые я нашел, подразумевают, что читающий их человек довольно хорошо разбирается в предметной области и все понимает, в моем случае это было не так и на свою первую настройку xDebug я в сумме потратил 4-5 часов за 2 вечера.


    Для Хрома есть расширение xDebug helper… С ним даже самый тупой потратит не больше 30 минут.


    1. peresada Автор
      15.09.2018 17:35

      Я писал о расширении в самом конце статьи. Единственное, что делает это расширение — позволяет не использовать get-параметры в url, а передает информацию самостоятельно, на работу с удаленными проектами это расширение не влияет. В любом случае, инструкция о другом, рад, что у Вас получилось разобраться так быстро


  1. m0rtis
    15.09.2018 18:45

    Как в тему мне попало. Спасибо, автор, добавлю в закладки:))


  1. rjhdby
    15.09.2018 23:44

    Почему стоит уйти от заплесневевших методов отладки и перейти на адекватные технологии?

    Наверное потому, что они работают. И довольно неплохо. ;)
    Статья хороша, +


  1. egaxegax
    17.09.2018 00:32

    Интересно в какой среде вы работаете Windows или Linux? При работе с git в Linux очень не хватает аналога TortoiseGit. Например в редакторе NetBeans есть хороший клиент для Git в Eclipse худо-бедно можно посмотреть историю, но с ними хорошо, пока все хорошо. Как только не проходят push или pull из-за локальных конфликтов или конфликтов с branch, приходится загружаться под Windows и через TortoiseGit решать проблему.


    1. peresada Автор
      17.09.2018 07:50

      В Linux. Мне более чем хватает функционала работы с git, который предоставляет PHPStorm, решать конфликты, по-крайней мере, более чем удобно.


  1. JekaNS
    17.09.2018 07:40

    Прописываем в нем 2 вышеупомянутые настройки:
    xdebug.remote_enable = on
    xdebug.remote_host = IP.ВАШЕГО.КОМПЬЮТЕРА.ВСЕТИ

    Нам не нужны расширения для браузера, не нужно передавать в get-параметры URL XDEBUG_SESSION_START=IDE_KEY, не нужно добавлять дополнительные конфигурации.

    Достаточно просто включить прослушку


    Недостаточно. Возможно Вы просто забыли скопировать в статью, но для именно такого поведения нужно включить флаг xdebug.remote_autostart в конфигурацию PHP. Иначе xdebug расширение всё ещё будет ждать cookie XDEBUG_SESSION либо query параметр XDEBUG_SESSION_START либо, в случае с CLI, переменную окружения XDEBUG_CONFIG=«idekey=*****»

    И не совсем понятно, Вы это проделываете с настройкой docker каждый раз когда прилетает новая задача (новая ветка)? Если да, то советую всё же воспользоваться браузерными плагинами, убрать xdebug.remote_host/port, отключить xdebug.remote_autostart и включить remote_connect_back. Тогда эти настройки можно положить под контроль версий, откуда ими смогут воспользоваться все.

    Ну и конечно всё это работает пока вам нужно дебажить только один сервер. Иначе, следующие утверждения неверны:
    host: любой, он не влияет
    port: любой, он не влияет


    Всё вышеописанное работает пока сервер в одной сети с вами, в остальных случаях советую использовать DBGp proxy.
    Во первых используя IDE_KEY можно дебажить один и тот же сервер нескольким разработчикам сразу, например общие тестовые сервера. Такое иногда бывает ))

    Во вторых можно дебажить «очень» удалённые сервера без пробрасывания портов на каждой рабочей станции. Нужно только одно плечо DBGp сервера выпустить в мир, то которое работает непосредственно с xdebug расширением. Плечё которое работает с IDE можно оставить за NAT-ом. А имея подключение к офисной сети по VPN, спокойно дебажить все нужные сервера даже не будучи в офисе.

    И всё это не требует каждый раз настраивать xdebug на стороне сервера. Один общий xdebug.ini где выключены xdebug.remote_autostart и xdebug.remote_connect_back и включено соединение с DBGp прокси сервером.
    Пример настройки
    xdebug.remote_connect_back = 0
    xdebug.remote_autostart = 0
    xdebug.remote_enable = 1
    xdebug.remote_handler = "dbgp"
    xdebug.remote_host = "YOUR.DBPP.SERVER.ADDR"
    xdebug.remote_port = 9900
    xdebug.remote_log="/tmp/xdebug.log"


    Здесь 9900 это как раз то плечё которое смотрит в сторону xdebux расширения. В сторону IDE будет стандартный 9000.