Отладка PHP в Bugsaur¶
Адаптер: php — собственный DBGp-адаптер Bugsaur.
PHP — единственный язык, который Bugsaur не отдаёт наружу. В комплекте идёт
php-dbgp-adapter, собираемый из того же workspace; он заменяет привычный мост
DAP↔DBGp. Во время отладки участвуют только bugsaur, php-dbgp-adapter и
Xdebug — ни Node.js, ни npm, ни VS Code, ни vscode-php-debug.
PHP устроен иначе, чем остальные языки¶
Это то, что нужно понять прежде всего остального:
Адаптер ничего не запускает — он слушает
Для Rust, Go и Python программу запускает Bugsaur. Для PHP — нет.
php-dbgp-adapter открывает DBGp-порт и ждёт, а PHP-процесс поднимает
кто-то другой: PHP-FPM, Docker или запуск из CLI.
Отсюда два следствия, объясняющие большинство неожиданностей:
cwdиenvне применяются. Нет процесса, которому Bugsaur мог бы задать рабочий каталог и окружение. Задавайте их там, где PHP действительно стартует: вdocker-compose.yml, в конфиге пула FPM, в.envконтейнера.- Из тела DAP-запроса читаются ровно три ключа:
port,pathMappings,sessionMode.
Установка¶
Внешний адаптер ставить не нужно. make build собирает php-dbgp-adapter рядом
с bugsaur; выведите каталог сборки в PATH, чтобы каталог адаптеров его нашёл:
Настроить Xdebug¶
В самой PHP-среде — в контейнере или в локальном php.ini:
xdebug.mode=debug
xdebug.start_with_request=yes
xdebug.client_host=host.docker.internal
xdebug.client_port=9003
xdebug.start_with_request=yes означает, что Xdebug пытается подключиться на
каждый новый PHP-запрос. Если PHP работает на той же машине, а не в контейнере,
вместо host.docker.internal пишется localhost.
Запрос, завершившийся до старта отладки, задним числом не отладить
Xdebug подключается в начале запроса. Если listener в этот момент не работал, запрос просто выполнился без отладки — повторите его после запуска сессии.
Режимы сессии¶
Ключ sessionMode в launch_arguments определяет жизненный цикл:
| Режим | Когда использовать | Порядок действий |
|---|---|---|
server |
PHP-FPM, встроенный сервер или web-контейнер уже работает | docker compose up -d web → запустить сессию → отправить новый запрос |
cli |
одноразовый CLI-скрипт, который стартует и завершается | запустить сессию → docker compose run --rm php |
server — рекомендуемый режим для обычной веб-разработки: контейнер поднимается
один раз, работает как обычно без отладки, а сессия запускается только тогда,
когда нужно разобрать конкретный ответ.
cli существует для случая, когда контейнер сам запускает PHP-команду и
завершается. Там адаптер обязан слушать до старта контейнера, иначе
одноразовый процесс успеет закончиться раньше, чем Xdebug подключится.
Локальный проект¶
version = 1
default = "shop"
[profiles.shop]
adapter = "php"
program = "."
[profiles.shop.launch_arguments]
sessionMode = "server"
port = 9003
PHP в Docker¶
Здесь существенны отображения путей, а не рабочий каталог:
[profiles.docker]
adapter = "php"
program = "."
[profiles.docker.launch_arguments]
sessionMode = "server"
port = 9003
pathMappings = { "/app" = "${root}" } # путь внутри контейнера -> путь на хосте
pathMappings переводит между путями, которые сообщает Xdebug — они существуют
внутри контейнера, — и путями на вашей машине. Без него отладчик остановится в
файле, который нечем показать, а точки останова по путям хоста никогда не
совпадут с путями контейнера.
Полный разбор вместе с настройкой compose: Рецепты → Отладка PHP в Docker.
PHPUnit под курсором¶
Чтобы отладить тест под курсором, добавьте команду, запускающую PHPUnit внутри контейнера:
[profiles.docker.launch_arguments]
sessionMode = "server"
port = 9003
pathMappings = { "/app" = "${root}" }
testCommand = ["docker", "compose", "exec", "-T", "-e", "XDEBUG_TRIGGER=1", "php", "php", "vendor/bin/phpunit"]
:DebugTest распознаёт методы test* и методы с атрибутом #[Test] в файлах
.php. Для метода передаётся --filter Class::method, а если курсор стоит вне
метода, запускается весь файл.
testCommand выполняется после того, как адаптер начал ждать Xdebug, поэтому
к моменту обратного соединения listener уже готов. Переменная XDEBUG_TRIGGER=1
также передаётся внешнему процессу.
Два PHP-проекта одновременно¶
DBGp-порт занимает адаптер, а порт на машине один — по умолчанию 9003. Второй
проект, стартующий одновременно, получит отказ. Дайте каждому проекту свой порт
и пропишите тот же порт в его контейнере:
# a/.bugsaur/config.toml
[profiles.docker.launch_arguments]
port = 9003
pathMappings = { "/app" = "${root}" }
# b/.bugsaur/config.toml
[profiles.docker.launch_arguments]
port = 9004
pathMappings = { "/app" = "${root}" }
Окна при этом разъедутся сами: они именуются по каталогу проекта.
Диагностика¶
| Симптом | Вероятная причина | Что делать |
|---|---|---|
| У точек останова приглушённое кольцо | Xdebug ещё не подключился | до первого запроса это нормально — отправьте запрос |
| Сессия стартовала, но ничего не останавливается | запрос ушёл до старта listener'а или выключен start_with_request |
повторите запрос; проверьте xdebug.mode=debug |
| Порт занят | 9003 держит другой проект |
дайте этому проекту свой порт, см. выше |
| Останавливается, но исходник не показан | pathMappings отсутствует или неверен |
Исходник не найден |
| Xdebug не достучится до хоста | неверный client_host для вашей схемы |
из контейнера host.docker.internal, локально localhost |
cwd / env в профиле ничего не делают |
так задумано — адаптер ничего не запускает | задавайте их там, где стартует PHP |