Отладка cli-скриптов на php из docker-контейнера в NetBeans штатными средствами

Этот пост был опубликован мной более года назад. Информация, описанная ниже, уже могла потерять актуальность, но всё ещё может быть полезна.

Привет. Я тут на досуге написал скрипт, которым подменил php-интерпретатор в NetBeans.

Об установке и настройке этой IDE я подробно рассказывал в этой статье: Настройка среды php-разработки с нуля на NetBeans + php + docker + xdebug3. Она тоже будет тебе полезна, часть информации по отладке ты можешь черпануть оттуда. Здесь я буду запускать скрипты штатными средствами.

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

В прошлом посте я придумал простейший скрипт, который в этом немного помог. Вот его исходный вид:

#!/bin/bash
docker exec test-php php \
    -dxdebug.mode=debug \
    -dxdebug.start_with_request=1 \
    `basename ${BASH_ARGV[0]}` \
    "${@:1:$#-1}"

Но он был неуниверсальным и грубым. Он не учитывал много чего, что можно настроить в разделе ‘Run Configuration’ окна ‘Project Properties’:

В итоге у меня получился скрипт-хелпер (враппер, оболочка, прокси, как угодно), который вызывается из IDE будто это настоящий интерпретатор, но внутри она подменяется хитро построенной командой docker exec.

Я сознательно писал всё на баше, чем достиг простоты в реализации, изучении и переиспользовании. Можно было бы написать плагин, но мне лень разбираться в структуре и экосистеме IDE.

Он же есть в репозитории nb-test из прошлой статьи. Вставлять его целиком сюда не буду — он получился слишком большим (в основном, из-за справки и подробных комментариев).

Как настроить хелпер?

Предполагается, что у тебя прямо сейчас уже бежит рабочий контейнер с пыхой и xdebug. Если не — обращайся сюда.

Файл хелпера называется php не просто так: NB не позволяет указывать иной путь до интерпретатора в своих настройках. Поскольку мой нужен для подмены настоящего контейнерным, то и имя у него соответствующее. Учитывай это и не путайся.

Ниже я очень подробно расписал эти шаги, но пугаться не стоит — всё очень легко. Просто текста получилось много.

Вариант 1: локальный

  1. Сохрани файл в директорию nbproject твоего проекта. Например, путь может быть таким:

    /home/user/bla/nbproject/php

    где php — это скачанный тобой файл.
  2. Убедись, что он исполняем. Если не, тогда:

    chmod +x /home/user/bla/nbproject/php
  3. Открой NetBeans. В выпадашке ‘Set project configuration’ тулбара ‘Run’ выбери ‘Customize…’.
  4. Параметр ‘Run as’ выстави в значение ‘Script (run in command line)’.
  5. Отпусти птичку ‘Use default PHP Interpreter’.
  6. В поле ‘PHP Interpreter’ вставь лапками путь из п.1. Кнопка ‘Browse’ не спасёт, ибо окно выбора файла скрывает директорию nbproject.

Если ты изменяешь конфигурацию <default>, то дальнейшие шаги 7-10 необязательно повторять для всех остальных. Тогда конфигурации отличаются только настройками, которые описаны в пп. 4-6.

  1. В поле ‘PHP Options’ впиши следующее:

    --container=<name>

    где <name> — имя бегущего контейнера с пыхой и xdebug. Если в этом поле у тебя другие аргументы, просто добавь к ним этот.

Да, знак = обязателен. Именем контейнера станет всё от знака = до ближайшего пробела или конца строки аргументов. Корректное имя можно увидеть в docker ps или docker-compose.yml твоего проекта.

Нет, это не аргумент php. Это аргумент, который обрабатывается хелпером для формирования команды docker exec. Он обязателен, но больше он ни для чего не нужен.

  1. Чтобы форсировать подключение xdebug из контейнера к NB, добавь на шаге 7 ещё такие аргументы:

    -dxdebug.mode=debug
    -dxdebug.start_with_request=1

    Описание параметров xdebug доступно здесь.
  2. Если в твоём проекте есть вложенная директория с php-исходниками проекта и в контейнер проброшена именно она, а не весь проект целиком, тогда добавь на шаге 7 ещё такой аргумент:

    --map=<local_path>:<docker_path>

    где:
    • <local_path> — абсолютный путь до локальной директории с php-исходниками (нужным php-скриптом) на хосте;
    • <docker_path> — абсолютный путь до тех же исходников внутри контейнера.

Да, знак = обязателен. Значением станет всё от знака = до ближайшего пробела или конца строки аргументов.

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

Нет, это не аргумент php. Это аргумент, который обрабатывается хелпером для формирования команды docker exec. Он НЕобязателен и больше он ни для чего не нужен.

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

    --quiet

Нет, никакие ошибки или другой вывод таким образом не будут заглушены.

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

  1. По необходимости укажи другие параметры и сохрани конфигурацию.

Вариант 2: глобальный

  1. Сохрани файл в любую понравившуюся тебе директорию. Например, путь может быть таким:

    /home/user/php

    где php — это скачанный тобой файл.
  2. Убедись, что он исполняем. Если не, тогда:

    chmod +x /home/user/php
  3. Открой ‘Tools’ > ‘Settings’ > ‘PHP’ > ‘General’.
  4. В параметре ‘PHP Interpreter’ укажи полный путь из шага 1.
  5. Повтори шаги 3-4 и 7-11 из варианта 1.

Как ловить точки останова?

  1. Убедись, что при настройке конфигурации запуска ты выполнил шаг 8 из варианта 1.
  2. Выбери настроенную конфигурацию запуска.
  3. Поставь бряк в любом месте того php-кода, который отработает при запуске настроенного скрипта.

Я всё настроил!

На тулбаре ‘Run’ есть пимпа ‘Debug Project’ — трогни её. NB сразу запустит скрипт, который инициирует подключение xdebug из контейнера со скриптом, и начнёт слушать порт xdebug, ожидая это входящее подключение.


Как работает хелпер?

Вот снова скриншот окна для управления конфигурациями запуска проекта:

Цифрами на скриншоте выделены:

  1. Тип запуска php-скрипта (установлен запуск в качестве cli-команды).
  2. Выключенный чекбокс позволяет указать явно конкретный интерпретатор php для запуска в п. 3.
  3. Путь до интерпретатора при выключенном чекбоксе ‘Use Default PHP Interpreter’. Обязан быть корректным и оканчиваться на php. Именно поэтому хелпер называется php. То же ограничение в настройках самого NB.
  4. Путь до запускаемого скрипта (php-файла) относительно корня проекта.
  5. Аргументы для этого скрипта.
  6. Рабочая директория. Вообще, при вызове php этот путь будет текущим pwd в рамках процесса, но в нашем контексте это бессмысленно, безполезно, не учитывается и не рассматривается.
  7. Аргументы php-интерпретатора (не php-скрипта!). Здесь можно (пере)определить настройки php.ini.

Вот команда, которая построена IDE и вызывается для конфигурации со скриншота (я порвал её по строкам для удобства):

"/home/anthony/projects/nb-test/nbproject/php" \
     "--container=test-php" \
     "--map=/home/anthony/projects/nb-test/app:/var/www" \
     "-dxdebug.mode=debug" \
     "-dxdebug.start_with_request=1" \
     "/home/anthony/projects/nb-test/app/index.php" \
     "arg1" \
     "arg2"

Здесь, построчно:

  1. Путь до моего хелпера. Настраивается явно в конфигурации (как на скриншоте; «локально», в рамках конфигурации текущего проекта) или в настройках NB (глобально).
  2. Аргумент хелпера. Здесь указывается имя контейнера, в котором существует (или из коего доступен) скрипт для запуска.
  3. Аргумент хелпера. Содержит проброс путей php-исходников с хоста в контейнер. Так хелпер понимает, что в контейнере не весь проект, а только его вложенная директория с php-исходниками. Значит, вызывать скрипт надо по другому пути, а не относительно текущего WORKDIR контейнера.
  4. Опция xdebug, включающая отладку кода (точки останова).
  5. Опция xdebug, включающая автоматическое подключение к порту хоста при начале работы php.
  6. Скорректированный абсолютный путь до файла php-скрипта, который будет запущен интерпретатором.
  7. Тестовый аргумент для php-скрипта.
  8. Тестовый аргумент для php-скрипта.

Обрати внимание на порядок кусков команды: интерпретатор => его аргументы => путь до скрипта => его аргументы.

Эту строку формирует и вызывает NB на локальном компе. А вот, что мне надо вызвать:

docker exec \
    <имя_контейнера> \
    php \
    [<аргументы_php>] \
    <php_скрипт> \
    [<аргументы_скрипта>]

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

Таааааак, дальше пошли нюансики!

Нюансик 0. У нас в IDE уже есть готовое окно, в котором можно установить всё необходимое для старта отладки cli-скрипта в докере. Оно на скриншотах выше. Мы будем использовать это окно вполне штатно.

Нюансик 1. Нам нельзя просто взять и запустить любой скрипт в каком-то контейнере. То есть, вообще, технически, можно, однако контейнер для начала надо (создать и) запустить.

Здесь же предполагается, что над php-проектом уже идёт работа в докере. Поэтому контейнер обязан существовать и быть запущенным. Для указания его имени я ввёл аргумент --container, который вводится в ‘PHP Options’.

Нюансик 2. Сам php-скрипт должен быть доступен внутри контейнера: либо находясь внутри вольюма (volume), либо прокидываясь вольюмом с диска хоста. Поэтому нам требуется преобразовать путь к php-скрипту: тот абсолютный (хостовый), что выдал NB, внутри контейнера будет некорректным. Нам нужен относительный.

По умолчанию предполагается, что этот путь относителен текущего WORKDIR контейнера. Тогда я вырезаю из абсолютного кусок от корня до текущей директории проекта. Она определяется как абсолютный путь до хелпера минус два куска справа. Таким образом,

/home/anthony/nb-test/nbproject/php

преобразовывается в

/home/anthony/nb-test

и это вырезается из абсолютного пути к файлу php-скрипта.

Другой сценарий, когда в ‘PHP Options’ ты указываешь `—map`. Тогда из абсолютного пути к скрипту вырезается левая часть мапа и конкатенируется с правой. В итоге получается абсолютный путь к файлу уже внутри контейнера.

Нюансик 3. Интерпретатор в NB можно настроить как в рамках одной конфигурации запуска проекта, так и в глобальных настройках. Отсюда закономерно родились два способа использования хелпера: глобальный и локальный. При глобальном хелпер лежит где угодно на хосте, при локальном — в директории nbproject проекта. Эта директория — единственный признак, по которому хелпер может найти папку с исходниками.

Нюансик 4. Я тут уже упомянул рабочую директорию. По умолчанию, это директория проекта, но в окне настройки конфигурации запуска её можно явно переопределить. Тогда, перед стартом скрипта, указанный путь станет pwd для пыхи.

Как показали мои изыскания, это стоит заигнорить и никогда не трогать. А жаль.


В итоге, собрав в горсть все собранные параметры и расставив их все по местам, получаем команду:

docker exec \
    test-php \
    php \
    -dxdebug.mode=debug \
    -dxdebug.start_with_request=1 \
    /var/www/index.php \
    arg1 \
    arg2

Изучив код, можно изрядно повонять. А по-моему, получилось неплохо. Вот результат:

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

Если этот скрипт будет полезен кому-то — огонь. Алоха.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *