Отладка PHP в Docker¶
Самая частая реальная схема для PHP и самая многосоставная: код выполняется в контейнере, отладчик работает на хосте, а пути у них не совпадают.
Предпосылки¶
php-dbgp-adapterв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:. Проверьте,
а не угадывайте:
Запуск¶
- Поднимите контейнер:
- Поставьте точку останова в контроллере и запустите сессию:
- Отправьте запрос:
Что должно произойти¶
Запрос замирает внутри контейнера, отладчик останавливается на строке 45, и
показанный исходник — это ваш локальный файл, а не путь внутри /app.
Несколько монтирований¶
Проекту, у которого вендорный код примонтирован отдельно, нужно отображение на каждое монтирование:
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-порт один на машину. Дайте каждому проекту свой:
Если не работает¶
| Симптом | Причина | Что делать |
|---|---|---|
| Не подключается вообще никогда | контейнер не достучится до хоста | проверьте client_host; на Linux добавьте extra_hosts |
| Ничего не останавливается | запрос ушёл до старта сессии | отправьте ещё один |
| Останавливается, но исходника нет | pathMappings отсутствует или неверен |
сверьте с volumes: и docker compose exec web pwd |
| Точки останова не привязываются | та же причина — пути хоста не совпадают с путями контейнера | см. выше |
| Открывается не тот файл | префикс отображён не в тот каталог | проверьте обе стороны отображения |
| Порт занят | его держит другой проект | свой port каждому проекту |
Более глубокий разбор: Диагностика → Исходник не найден.