Содержание
Вместо введения
Что, опять?
Как это работает?
Создаём проект в PHPStorm
Создаём контейнер Docker
Добавляем расширение Xdebug
Настраиваем Xdebug
Что там в логах Xdebug?
Настраиваем PhpStorm
Устанавливаем CLI Interpreter
Устанавливаем сервер, на котором происходит отладка
Проверяем, как работает отладка
Бонусные материалы для самых терпеливых
Run/Debug конфигурация
Использование docker-compose
А на этом всё
Вместо введения
Одним из ключевых аспектов успешной разработки является эффективная отладка кода. Статья посвящена настройке и использованию PhpStorm, Xdebug и Docker для отладки PHP-скриптов в Docker-контейнере. Статья предлагает актуальную информацию (на момент написания) и оформлена в виде подробнейшего пошагового туториала. Информация действительна для ОС Windows. В других ОС возможны варианты.
Цель статьи — не просто создать пошаговой конспект настроек, а объяснить, как всё это взаимодействует. Это поможет избежать затруднений при изменении интерфейса IDE в будущем. Возможно, статья выглядит слишком педантичной. Но гуру всегда смогут прочесть её по диагонали, зато новички в этой теме найдут для себя много полезного.
Что, опять?
Ничего не поделаешь. Я сам удивляюсь, как быстро меняются инструменты и технологии, и то, что было актуально полтора года назад, сегодня выглядит устаревшим. Интерфейс IDE PhpStorm преображается, ключи настоек Xdebug меняются, рабочие рецепты, бездумно скопированные из легаси-туториалов обрастают кучей ненужных зависимостей и противоречий. На днях мне понадобилось отладить php-сценарий в докер-контейнере. Я пробежался вроде бы по не очень древним статьям, но не взлетело. Пришлось садиться и вдумчиво разбираться. Начнём?
Как это работает?
Xdebug подключается модулем к интерпретатору PHP. Когда интерпретатор обрабатывает код, он обращается к модулю Xdebug. А Xdebug подключается к хосту, указанному в настойках, на порт, указанный в настройках. В нашем случае на этом хосту должен быть запущен PhpStorm, который слушает этот порт. Точнее все порты, указанные в настройках. Проверяем: Ctrl+Alt+S
— откроется окно настроек. Слева сверху находим PHP>Debug
Скриншот окна настроек
Видим порты 9003 и 9000. PhpStorm начинает слушать эти порты при выполнении команды Run>Start Listening for PHP Debug Connection
. А также при нажатии кнопок с изображением трубки в старом интерфейсе, или с изображением жука в новом интерфейсе. Лично мне приглянулся новый интерфейс, поэтому далее скриншоты будут из него.
Скриншот жука и телефона
Проверим свободны ли эти порты. Запускаем Windows Terminal. У кого нет, можно взять здесь
Подаём команду: netstat -ano | findstr :900*
. В выводе пусто. Теперь включим прослушивание отладочных соединений. Я нажму на жука. Повторяем команду: netstat -ano | findstr :900*
Результат выполнения команды
Из вывода команды видно, что система слушает (LISTENING) порты 9000 и 9003 и готова принимать подключения с любого IP-адреса и любого порта. А pid процесса-слушателя 27540. Давайте откроем диспетчер задач, вкладку «Подробности» и найдём процесс с pid 27540.
Диспетчер задач
Видим, что порты слушает PhpStorm.
Таким образом, PHP исполняя скрипт запускает Xdebug, тот связывается с PhpStorm, они о чём-то между собою договариваются, и мы наблюдаем процесс отладки в своей любимой IDE. Наша оставшаяся задача — настроить Xdebug и PhpStorm таким образом, чтобы они соединялись и понимали, что вот это вот соединение относится к вот этому проекту, а файлы от проекта лежат вон там-то, их и будем дебажить.
На этом с теорией всё. Перейдём к практике.
ВАЖНО: отключите прослушивание (телефон и жук должны быть не активны) и расширение Xdebug в браузере. Это важно для подачи материала, иначе примеры по ходу статьи будут расходиться с вашими результатами.
Создаём проект в PHPStorm
Создайте каталог проекта. Я использую C:\WWW\debug
. Откройте его в PhpStorm.
Создадим в проекте два файла, которые и будем дебажить: index.php и 1.php
index.php
<?php
phpinfo();
1.php
<?php
$a = 5;
$b = 3;
$c = $a + $b;
echo $c;
Создаём контейнер Docker
Настала пора создать тот самый контейнер Docker, в котором у нас будет проходить отладка. Я взял за основу образ php:7.4-cli и просто-напросто запустил отладочный сервер PHP. Создаём Dockerfile следующего содержания:
Содержимое Dockerfile
# Базовый образ PHP
FROM php:7.4-cli
# Установка рабочего каталога
WORKDIR /var/www/html
# Запуск скрипта PHP со встроенным сервером
CMD ["php", "-S", "0.0.0.0:8080"]
Собирать образ и создавать контейнер очень удобно из самой IDE. Нажимайте на зелёненькие стрелочки и выбирайте New Run Configuration
New Run Configuration
Далее введите имя конфигурации: debug, имя для Image tag: de/bug и имя для контейнера: de-bug
Конфигурация
Настройки контейнера надо чуть-чуть дополнить. Нажмите на Modify и поставьте галочки у Bind ports, Bind mounts и Environment variables
Скриншот окна настроек
Настройте Bind ports. Кликните на символ папки, на плюсик и вбейте значения 8080 для контейнера и хоста. Ключевые элементы интерфейса выделены стрелочками. Эта директива будет перенаправлять все запросы на порт 8080 хоста в контейнер, на порт 8080.
Скриншот окна настроек
Настройте Bind mounts. Настройка происходит аналогично Bind ports. Сопоставьте каталог C:\WWW\debug на хосте с путём /var/www/html в контейнере.
Скриншот окна настроек
Environment variables пока оставьте пустым. Нажмите на кнопку Run. Сначала собирается образ, потом создаётся и запускается контейнер.
За прогрессом удобно наблюдать в Build Log.
Давайте проверим, работает ли наш контейнер. Заходим в браузер и переходим по адресу: http://127.0.0.1:8080
Открылся вывод phpinfo. Рабочий контейнер создан, наши php скрипты обрабатываются.
Добавляем расширение Xdebug
Существует множество способ добавить Xdebug, но мне больше нравится пользоваться скриптом docker-php-extension-installer, созданным итальянским программистом Мишелем Локатти
Мишель Локатти
С описанием скрипта и его возможностями можно ознакомиться здесь
Скрипт установит все необходимые пакеты APT/APK, а в конце выполнения скрипта ненужные пакеты будут удалены, так что docker-образ станет намного меньше.
Добавляем в наш Dockerfile следующие строки после секции FROM:
# Xdebug
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/bin/
RUN install-php-extensions Xdebug
Первая строка копирует скрипт из образа mlocati/php-extension-installer на Docker Hub, вторая его запускает.
Результирующий Dockerfile
# Базовый образ PHP
FROM php:7.4-cli
# Xdebug
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/bin/
RUN install-php-extensions Xdebug
# Установка рабочего каталога
WORKDIR /var/www/html
# Запуск скрипта PHP со встроенным сервером
CMD ["php", "-S", "0.0.0.0:8080"]
Теперь снова нажимаем зелёные стрелочки и просто выбираем Run 'Dockerfile'. Наш контейнер настроен, а интеллектуальный PhpStorm сам остановит предыдущую версию и удалит её, а затем пересоберёт образ, создаст новый контейнер с тем же именем и запустит его.
Зелёные стрелочки
Переходим по адресу: http://127.0.0.1:8080
Изучаем вывод, ищем секцию Xdebug. Секция появилась, видно, что установилась версия Xdebug 3.1.6, но не все параметры по умолчанию нам подходят
Вывод phpinfo()
В частности выключена пошаговая отладка и клиентский хост установлен неверно: localhost внутри контейнера.
Настраиваем Xdebug
Создадим минимальную конфигурацию для Xdebug. Обычно достаточно следующих строк:
zend_extension=Xdebug.so
Xdebug.mode=debug
Xdebug.client_host=host.docker.internal
Xdebug.log=/var/log/Xdebug.log
host.docker.internal
— специальное имя для IP адреса, по которому доступна хост-машина из контейнера Docker. Ходят слухи, что это имя прописывается в файле /etc/hosts контейнера, но на самом деле в Docker Desktop для Windows и Mac это имя автоматически разрешается на уровне DNS, что позволяет контейнерам обращаться к хосту без необходимости явного указания в /etc/hosts. А вот в Linux это имя недоступно. Я не исследовал вопрос, как разрешить IP хоста в Linux, надеюсь, гуру оставят своё мнение в комментариях.
Всё обилие остальных настроек, как правило, кочует из гайда в гайд, и зачастую либо не нужно, либо противоречит друг другу, либо создаёт ненужную нагрузку на сервер, замедляя выполнение сценариев. Если честно, то последняя строка тоже не нужна. Я вставил её для опытов. В дальнейшем её можно безболезненно удалить.
Создадим файл Xdebug.conf и поместим в него эти строки.
Теперь скопируем этот файл с хоста в контейнер. Добавим следующие строки в наш Dockerfile:
# Копирование конфигурационного файла Xdebug
COPY Xdebug.conf /usr/local/etc/php/conf.d/docker-php-ext-Xdebug.ini
PHP в контейнере просматривает эту директорию при запуске и применяет любые дополнительные конфигурационные файлы, которые он находит. Это позволяет настраивать поведение PHP без изменения основного конфигурационного файла php.ini.
Вот так выглядит наш файл Dockerfile теперь:
Результирующий Dockerfile
# Базовый образ PHP
FROM php:7.4-cli
# Xdebug
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/bin/
RUN install-php-extensions Xdebug
# Копирование конфигурационного файла Xdebug
COPY Xdebug.conf /usr/local/etc/php/conf.d/docker-php-ext-Xdebug.ini
# Установка рабочего каталога
WORKDIR /var/www/html
# Запуск скрипта PHP со встроенным сервером
CMD ["php", "-S", "0.0.0.0:8080"]
Пересобираем контейнер. Помните про зелёные стрелочки? Заходим по адресу http://127.0.0.1:8080 и видим следующую картину:
Вывод phpinfo()
Настройки в порядке. Xdebug готов к отладке.
Что там в логах Xdebug?
Давайте заглянем в логи Xdebug. Отключили прослушивание и расширение Xdebug в браузере? Тогда соединимся с контейнером и заглянем, что там у него внутри. PhpStorm тут как всегда на высоте.
Открываем терминал
Выбираем Services, контейнер и жмём терминал. В терминале подаём команду:
tail -f /var/log/Xdebug.log
В ответ получаем следующие сообщения:
root@5c0b8f79fbe9:/var/www/html# tail -f /var/log/Xdebug.log
[1] Log opened at 2024-08-08 17:38:38.254112
[1] [Config] INFO: Trigger value for 'Xdebug_TRIGGER' not found, falling back to 'Xdebug_SESSION'
[1] [Config] INFO: Trigger value for 'Xdebug_SESSION' not found, so not activating
[1] Log closed at 2024-08-08 17:38:38.260438
Логи Xdebug показывают, что он не был активирован, поскольку не было найдено управляющего значения для Xdebug_TRIGGER, а затем и для Xdebug_SESSION.
Xdebug_TRIGGER и Xdebug_SESSION — это способы указать Xdebug, когда он должен начинать отладку. Они могут быть указаны, например, через параметры GET, POST или COOKIE в вашем запросе. Если указанные параметры отсутствуют, Xdebug не будет активирован.
Давайте изменим наш запрос на http://127.0.0.1:8080/index.php?Xdebug_SESSION=start
Смотрим в терминал, появились новые строчки:
[1] Log opened at 2024-08-08 17:52:52.050010
[1] [Config] INFO: Trigger value for 'Xdebug_TRIGGER' not found, falling back to 'Xdebug_SESSION'
[1] [Config] INFO: No shared secret: Activating
[1] [Step Debug] INFO: Connecting to configured address/port: host.docker.internal:9003.
[1] [Step Debug] WARN: Creating socket for 'host.docker.internal:9003', poll success, but error: Operation now in progress (29).
[1] [Step Debug] WARN: Creating socket for 'host.docker.internal:9003', connect: Network is unreachable.
[1] [Step Debug] ERR: Could not connect to debugging client. Tried: host.docker.internal:9003 (through Xdebug.client_host/Xdebug.client_port) :-(
[1] Log closed at 2024-08-08 17:52:52.060655
Видно, что Xdebug был активирован, но не удалось установить соединение на заданный хост и порт.
ERR: Could not connect to debugging client. Tried: host.docker.internal:9003 (through Xdebug.client_host/Xdebug.client_port) :-(
Чтобы избежать этого недоразумения, нажимаем кнопку с жуком (трубкой телефона) или выполняем Run>Start Listening for PHP Debug Connection
.
Обновляем страницу на http://127.0.0.1:8080/index.php?Xdebug_SESSION=start
. Смотрим в терминал. Видим много-много логов. Я не буду тащить их в статью. Но очевидно, что Xdebug очень плотно общался с PhpStorm.
Кто же подставляет все эти Xdebug_SESSION? Этим занимается расширение браузера Xdebug Chrome Extension
Оно вообще делает массу вещей под капотом. Включите это расширение, уберите index.php?Xdebug_SESSION=start
, чтобы осталось http://127.0.0.1:8080/
и обновите страницу.
Секция PHP Variables
А теперь отключите расширение и обновите страницу. Сравните со скриншотом.
Возникает вопрос: раз у нас всё так замечательно, документально подтверждено, что отладка стартует, диалог с PHPStorm есть, так может быть пора ставить точку останова и приступать к отладке?
Пока не выйдет. Просто потому, что PhpStorm пока не знает, с каким проектом связать активный трафик с Xdebug и какие файлы брать за основу для отладки. Будем его этому обучать.
Настраиваем PhpStorm
Устанавливаем CLI Interpreter
Заходим в настройки: Ctrl+Alt+S
Слева выбираем PHP, в CLI Interpreter нажимаем три точки
Скриншот настроек
В появившемся окне нажимаем плюсик, выбираем From Docker, Vagrant…
Скриншот настроек
Выбираем Radiobutton Docker и в Image name в выпадающем списке выбираем наш образ
Скриншот настроек
После того, как PHPStorm немного подумает и в фоне притащит Docker-контейнер phpstorm_helpers, он совершенно верно определит и версию PHP и версию Xdebug в контейнере.
Скриншот настроек
Нажимаем ОК, выходим на уровень вверх и там согласуем версии языка с реальной версией.
Скриншот настроек
С CLI Interpreter закончили.
Устанавливаем сервер, на котором происходит отладка
Заходим в настройки: Ctrl+Alt+S
Слева выбираем PHP>Servers, плюсик, заполняем поля данными, как на скриншоте. Обязательно запомните имя сервера debug
. Оно нам пригодится.
Скриншот настроек
Здесь же обязательно надо замаппить локальное расположение файлов и расположение файлов в контейнере. Для этого вскидываем флажок и в раскрывшиеся поля вбиваем следующую информацию:
Скриншот настроек
Теперь наконец-то дело дошло до Environment variables. Дорабатываем настройку нашего контейнера. Зелёные стрелочки помните?
Зелёные стрелочки > Edit 'Dockerfile', настраиваем переменную как на скриншоте. Вбиваем имя сервера, на котором происходит отладка (я просил его запомнить).
Скриншот настроек
Что это за переменная и для чего она нужна? Вот что написано в документации
Чтобы указать PhpStorm, какая конфигурация сопоставления путей должна использоваться для подключения с определенного компьютера, нужно установить значение переменной окружения PHP_IDE_CONFIG равным serverName=SomeName, где SomeName это имя конфигурации сервера отладки. В нашем случае это debug
.
Проверяем, как работает отладка
Запускаем контейнер. Зелёные стрелочки > Run. В файле 1.php ставим точку останова на последней строке. В браузере включаем расширение Xdebug. В PphStorm включаем прослушивание (жук/трубка/команда). Заходим по адресу: http://http://127.0.0.1:8080/1.php
. Упс! Точка останова сработала!
Отладка работает
Виден стек, значения переменных и т.п.
У кого горит проект, могут приступить к отладке, а самых терпеливых ждут бонусные материалы.
Бонусные материалы для самых терпеливых
Лично мне нравится иная конфигурация, без создания переменной PHP_IDE_CONFIG. Самостоятельно удалите её из опций сборки контейнера. Перезапустите контейнер.
Run/Debug конфигурация
Вместо этого давайте создадим Run/Debug конфигурацию. Для этого сверху справа (в новом интерфейсе) нажмите три точки и выберите Edit.
Скриншот настроек
Далее плюсик, PHP Remote Debug
Скриншот настроек
Заполняем данные, как на скриншоте. Ставим галочку Filter debug connection by IDE key
, из выпадающего списка выбираем сервер, на котором происходит отладка и вводит сам key.
Скриншот настроек
IDE key задаётся в настройках Xdebug Chrome Extension
Xdebug Chrome Extension
Теперь следует включать для прослушки не жука слева, а жука справа. Жук слева может быть выключенным.
Скриншот IDE
Можно пробовать отладку. У меня работает.
Использование docker-compose
Если у вас всего один контейнер на этом можно и остановиться. Но иногда для работы веб-приложения их нужно несколько. Можно, конечно, стартовать контейнеры вручную, но и здесь PhpStorm предлагает изящное решение.
Для начала создадим docker-compose.yml следующего содержания
docker-compose.yml
services:
web:
build: .
ports:
- "8080:8080"
volumes:
- .:/var/www/html
Во многих методичках пишут, что необходимо добавить следующую директиву:
extra_hosts:
- "host.docker.internal:host-gateway"
Директива создаст дополнительную запись в /etc/hosts внутри каждого запущенного контейнера Docker, соответствующую содержимому этой директивы. Эта запись будет устанавливать соответствие между host.docker.internal и IP-адресом хоста Docker, который указан в host-gateway.
Как я уже отмечал, в случае Windows и Mac в этом нет нужды. Но эта директива может оказаться полезной в Linux.
Теперь перейдём к настройкам Cli Interpreter. Мы уже настраивали его, вам должно быть всё знакомо.
Ctrl+Alt+S
— PHP — три точки в строке Cli Interpreter
Скриншот настроек
В открывшемся окне плюсик, выбрать From Docker, Vagrant…
Скриншот настроек
В следующем окне выбираем Radiobutton Docker Compose, файл с конфигурацией определяется автоматически, а службу нужно выбрать в выпадающем списке.
Скриншот настроек
Жмём ОК. Убеждаемся, что версия PHP и XDebug определены верно. Также здесь можно настроить жизненный цикл контейнера. Выставьте по своему усмотрению. Снова ОК.
Скриншот настроек
Давайте запустим docker-compose
Скриншот настроек
После первого запуска, наша конфигурация попадёт в секцию Run/Debug Configuration
и запускать контейнеры можно будет оттуда.
Run/Debug Configuration
Мы же выберем конфигурацию удалённой отладки debug
и инициируем отладочную сессию.
Жмём на правого жука, мы это уже делали. Получаем такую картинку:
Запускаем отладку
Переходим на http://127.0.0.1:8080/1.php и проверяем отладку.
Отладка работает
У меня всё работает.
Замечание: у меня сложилось ощущение, что при использовании docker-compose всегда используются уже собранные образы. Т.е. можно попасть в такую ситуацию, когда в Dockerfile произошли изменения, но при этом будет использоваться прежний образ. Если я заблуждаюсь, отпишитесь в комментариях. Таким образом, данный способ работы хорош для уже отлаженных контейнеров, не требующих изменений. Единственное, что я нашёл, так это возможность настроить удаление образов, после команды down:
Скриншот настроек
А на этом всё
Надеюсь, теперь вам не составит труда адаптировать свои контейнеры для удалённой отладки. Приятной работы!