Перейти к содержанию

Отладка PHP в Docker

Самая частая реальная схема для PHP и самая многосоставная: код выполняется в контейнере, отладчик работает на хосте, а пути у них не совпадают.

Предпосылки

  • php-dbgp-adapter в PATH:
make build
export PATH="$PWD/target/debug:$PATH"
  • Xdebug, установленный в образе контейнера
  • проект, примонтированный в контейнер

Сторона контейнера

docker-compose.yml:

services:
  web:
    build: .
    ports:
      - "8080:80"
    volumes:
      - .:/app          # хост . -> контейнер /app
    extra_hosts:
      - "host.docker.internal:host-gateway"   # нужно на Linux

Ini Xdebug внутри образа:

xdebug.mode=debug
xdebug.start_with_request=yes
xdebug.client_host=host.docker.internal
xdebug.client_port=9003

host.docker.internal — это способ контейнера достучаться до хоста. В Docker Desktop он есть сразу, на Linux добавляется строкой extra_hosts выше.

Конфиг

version = 1
default = "docker"

[profiles.docker]
adapter = "php"
program = "."

[profiles.docker.launch_arguments]
sessionMode = "server"
port = 9003
pathMappings = { "/app" = "${root}" }

Работает всё это благодаря pathMappings. Xdebug сообщает /app/src/Controller.php, отображение превращает это в <корень>/src/Controller.php, который ваш редактор способен открыть, — и на обратном пути превращает ваши точки останова в пути контейнера.

Левая часть обязана точно совпадать с путём контейнера из volumes:. Проверьте, а не угадывайте:

docker compose exec web pwd
docker compose exec web ls /app

Запуск

  1. Поднимите контейнер:
docker compose up -d web
  1. Поставьте точку останова в контроллере и запустите сессию:
bugsaur gui --project . --break src/Controller/OrderController.php:45
  1. Отправьте запрос:
curl 'http://localhost:8080/orders?id=1'

Что должно произойти

Запрос замирает внутри контейнера, отладчик останавливается на строке 45, и показанный исходник — это ваш локальный файл, а не путь внутри /app.

Несколько монтирований

Проекту, у которого вендорный код примонтирован отдельно, нужно отображение на каждое монтирование:

pathMappings = { "/app" = "${root}", "/vendor-src" = "${root}/vendor" }

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 с курсором внутри тестового метода запускает PHPUnit в контейнере с --filter Class::method. Команда выполняется, когда адаптер уже слушает, поэтому обратное соединение всегда находит его на месте.

Два проекта одновременно

DBGp-порт один на машину. Дайте каждому проекту свой:

# проект B
[profiles.docker.launch_arguments]
port = 9004
pathMappings = { "/app" = "${root}" }
; xdebug.ini проекта B
xdebug.client_port=9004

Если не работает

Симптом Причина Что делать
Не подключается вообще никогда контейнер не достучится до хоста проверьте client_host; на Linux добавьте extra_hosts
Ничего не останавливается запрос ушёл до старта сессии отправьте ещё один
Останавливается, но исходника нет pathMappings отсутствует или неверен сверьте с volumes: и docker compose exec web pwd
Точки останова не привязываются та же причина — пути хоста не совпадают с путями контейнера см. выше
Открывается не тот файл префикс отображён не в тот каталог проверьте обе стороны отображения
Порт занят его держит другой проект свой port каждому проекту

Более глубокий разбор: Диагностика → Исходник не найден.