Эта статья впервые появилась на сайте https://www.pascallandau.com/ в разделе PhpStorm, Docker и Xdebug 3 на PHP 8.1 в 2022 году [Учебник, часть 3].
В этой части серии уроков по разработке PHP на Docker мы настроим нашу локальную среду разработки для использования PhpStorm и Xdebug. Мы также убедимся, что можем запускать тесты PHPUnit как из командной строки, так и из PhpStorm, и добавим инструмент strace
для отладки долго работающих процессов.
Все примеры кода находятся в открытом доступе в моем репозитории Docker PHP Tutorial на Github. Вы найдете ветку для этого руководства по адресу part-4-2-phpstorm-docker-xdebug-3-php-8-1-in-2022.
Все опубликованные части Docker PHP Tutorial собраны на специальной странице Docker PHP Tutorial. Предыдущая часть – Docker с нуля для приложений PHP 8.1 в 2022 году, следующая – Запуск Laravel 9 на Docker в 2022 году.
Если вы хотите следить за развитием событий, пожалуйста, подпишитесь на RSS-ленту или электронную почту, чтобы получать автоматические уведомления о выходе следующей части 🙂
- Оглавление
- Введение
- Инструменты для установки
- Установка composer Composer устанавливается путем извлечения официального образа composer docker и простого “копирования” исполняемого файла composer в базовый образ php. Кроме того, composer нуждается в расширениях mbstring и phar.
- Установите Xdebug Установите расширение через apk (только для цели local):
- Установка PHPUnit PHPUnit будет установлен через composer, но не будет “запечен в образ” для локальной разработки. Таким образом, мы должны запустить composer require в контейнере. Чтобы сделать это более удобным, в .make/01-00-application-setup.mk добавлена цель make для запуска произвольных команд composer:
- Установка SSH
- Настройка PhpStorm
- SSH-конфигурация
- Интерпретатор PHP
- PHPUnit
- Отладка
- Отладка кода, выполняемого через PhpStorm
- Отладка кода, выполняемого через php-fpm, cli или с рабочего сервера
- strace
- Подведение итогов
Оглавление
- Введение
- Установка инструментов
- Установите composer
- Установите Xdebug
- Установите PHPUnit
- Установите SSH
- Настройка PhpStorm
- Конфигурация SSH
- Интерпретатор PHP
- PHPUnit
- Отладка
- Отладка кода, выполняемого через PhpStorm
- Отладка кода, выполняемого через php-fpm, cli или с рабочего сервера
- php-fpm
- cli
- php-workers
- strace
- Подведение итогов
Введение
Эта статья в основном является обновлением статьи Настройка PhpStorm с Xdebug для локальной разработки на Docker, но мы также рассмотрим “оставшиеся случаи” отладки процессов php-fpm и php worker.
Мы по-прежнему будем полагаться на постоянно работающую установку docker, к которой мы подключаемся через SSH Configuration вместо использования встроенных возможностей docker-compose, поскольку я считаю, что это ближе к тому, что мы делаем в CI / production. Однако мы больше не будем использовать SSH-ключи, а просто будем аутентифицироваться по паролю. Это снижает сложность и устраняет любые назойливые предупреждения о том, что “SSH-ключи могут быть открыты в репозитории”.
Инструменты для установки
Установка composer Composer устанавливается путем извлечения официального образа composer docker и простого “копирования” исполняемого файла composer в базовый образ php. Кроме того, composer нуждается в расширениях mbstring
и phar
.
# File: .docker/images/php/base/Dockerfile
ARG ALPINE_VERSION
ARG COMPOSER_VERSION
FROM composer:${COMPOSER_VERSION} as composer
FROM alpine:${ALPINE_VERSION} as base
# ...
RUN apk add --update --no-cache
php-mbstring~=${TARGET_PHP_VERSION}
php-phar~=${TARGET_PHP_VERSION}
# ...
COPY --from=composer /usr/bin/composer /usr/local/bin/composer
Поскольку мы хотим, чтобы наша сборка была детерминированной, мы “привязываем” версию composer, добавляя переменную COMPOSER_VERSION
в файл .docker/.env
.
COMPOSER_VERSION=2.2.5
и использовать ее в .docker/docker-compose/docker-compose-php-base.yml
:
services:
php-base:
build:
args:
- COMPOSER_VERSION=${COMPOSER_VERSION?}
Установите Xdebug Установите расширение через apk
(только для цели local
):
# File: .docker/images/php/base/Dockerfile
FROM base as local
RUN apk add --no-cache --update
php-xdebug~=${TARGET_PHP_VERSION}
# ensure that xdebug is not enabled by default
&& rm -f /etc/php8/conf.d/00_xdebug.ini
Мы также не хотим включать xdebug
сразу, а только когда нам это нужно (из-за снижения производительности, когда расширение включено), поэтому мы удалим конфигурационный файл по умолчанию и отключим расширение в файле приложения .ini
.
# File: .docker/images/php/base/conf.d/zz-app-local.ini
; Note:
; Remove the comment ; to enable debugging
;zend_extension=xdebug
xdebug.client_host=host.docker.internal
xdebug.start_with_request=yes
xdebug.mode=debug
См. статью Исправление Xdebug в PhpStorm при запуске из контейнера Docker для объяснения настройки xdebug.client_host=host.docker.internal
(ранее называлась xdebug.remote_host
в xdebug < 3). Это будет работать из коробки для Docker Desktop, но для пользователей Linux нам нужно добавить магическую ссылку host-gateway
во все контейнеры PHP (мы добавим ее во все контейнеры PHP).
во все контейнеры PHP (мы не можем добавить ее в базовый образ php, потому что это настройка времени выполнения):
services:
service:
extra_hosts:
- host.docker.internal:host-gateway
Наконец, нам нужно добавить переменную окружения PHP_IDE_CONFIG
во все контейнеры PHP. Переменная определяется как PHP_IDE_CONFIG=serverName=dofroscra
, где “dofroscra” – это имя сервера, который мы позже настроим для отладки. Поскольку нам нужно одно и то же значение в нескольких местах, переменная настраивается в .docker/.env
:
PHP_IDE_CONFIG=serverName=dofroscra
А затем добавляется в .docker/docker-compose/docker-compose.local.yml
.
services:
php-fpm:
environment:
- PHP_IDE_CONFIG=${PHP_IDE_CONFIG?}
php-worker:
environment:
- PHP_IDE_CONFIG=${PHP_IDE_CONFIG?}
application:
environment:
- PHP_IDE_CONFIG=${PHP_IDE_CONFIG?}
Установка PHPUnit PHPUnit будет установлен через composer
, но не будет “запечен в образ” для локальной разработки. Таким образом, мы должны запустить composer require
в контейнере. Чтобы сделать это более удобным, в .make/01-00-application-setup.mk
добавлена цель make для запуска произвольных команд composer:
.PHONY: composer
composer: ## Run composer commands. Specify the command e.g. via ARGS="install"
$(EXECUTE_IN_APPLICATION_CONTAINER) composer $(ARGS);
Это позволяет мне запустить make composer ARGS="install"
с хост-системы, чтобы выполнить composer install
в контейнере. В результате composer
будет использовать версию PHP и расширения контейнера application
для установки зависимостей, но я все равно буду видеть установленные файлы локально, потому что кодовая база настроена как том для контейнера.
Перед установкой phpunit мы должны добавить необходимые расширения dom
и xml
в контейнер
# File: .docker/images/php/base/Dockerfile
# ...
RUN apk add --update --no-cache
php-dom~=${TARGET_PHP_VERSION}
php-xml~=${TARGET_PHP_VERSION}
а также перестроить и перезапустить установку docker с помощью команды
make docker-build
make docker-down
make docker-up
Теперь мы можем добавить phpunit через
make composer ARGS='require "phpunit/phpunit"'
который создаст файл composer.json
и настроит каталог vendor/
:
$ make composer ARGS='require "phpunit/phpunit"'
Using version ^9.5 for phpunit/phpunit
./composer.json has been created
Running composer update phpunit/phpunit
Loading composer repositories with package information
Updating dependencies
...
ВНИМАНИЕ: Если на этом шаге вы столкнулись со следующей ошибкой разрешения, скорее всего, вы используете Linux и не установили переменные APP_USER_ID
и APP_GROUP_ID
, как описано в предыдущей статье в разделе Решение проблем с разрешениями.
make composer ARGS='req phpunit/phpunit' ENV=local TAG=latest DOCKER_REGISTRY=docker.io DOCKER_NAMESPACE=dofroscra APP_USER_NAME=application APP_GROUP_NAME=application docker-compose -p dofroscra_local --env-file ./.docker/.env -f ./.docker/docker-compose/docker-compose.yml -f ./.docker/docker-compose/docker-compose.local.yml exec -T --user application application composer req phpunit/phpunit
./composer.json is not writable.
make: *** [.make/01-00-application-setup.mk:14: composer] Error 1
Я также добавил
- минимальный файл конфигурации
phpunit.xml
- тестовый пример по адресу
tests/SomeTest.php
- и новый Makefile для “всего, что связано с qa” по адресу
.make/01-02-application-qa.mk
:
##@ [Application: QA]
.PHONY: test
test: ## Run the test suite
$(EXECUTE_IN_WORKER_CONTAINER) vendor/bin/phpunit -c phpunit.xml
Таким образом, я могу запускать тесты просто через make test
.
$ make test
ENV=local TAG=latest DOCKER_REGISTRY=docker.io DOCKER_NAMESPACE=dofroscra APP_USER_NAME=application APP_GROUP_NAME=application docker-compose -p dofroscra_local --env-file ./.docker/.env -f ./.docker/docker-compose/docker-compose.yml -f ./.docker/docker-compose/docker-compose.local.yml exec -T --user application php-worker vendor/bin/phpunit
PHPUnit 9.5.13 by Sebastian Bergmann and contributors.
. 1 / 1 (100%)
Time: 00:00.324, Memory: 4.00 MB
OK (1 test, 1 assertion)
Установка SSH
Мы будем выполнять команды из PhpStorm по ssh в контейнере application
. Как уже упоминалось, мы не будем использовать ключевой файл для аутентификации, а просто воспользуемся паролем, который задается переменной APP_SSH_PASSWORD
в .docker/.env
и передается образу в .docker/docker-compose/docker-compose.local.yml
. Кроме того, мы сопоставляем порт 2222
из хост-системы с портом 22
контейнера приложения и убеждаемся, что кодовая база разделяется как том между хостом и контейнером.
application:
build:
args:
- APP_SSH_PASSWORD=${APP_SSH_PASSWORD?}
volumes:
- ${APP_CODE_PATH_HOST?}:${APP_CODE_PATH_CONTAINER?}
ports:
- "${APPLICATION_SSH_HOST_PORT:-2222}:22"
Контейнер уже содержит openssh
и устанавливает пароль
ARG BASE_IMAGE
FROM ${BASE_IMAGE} as base
FROM base as local
RUN apk add --no-cache --update
openssh
ARG APP_SSH_PASSWORD
RUN echo "$APP_USER_NAME:$APP_SSH_PASSWORD" | chpasswd 2>&1
# Required to start sshd, otherwise the container will error out on startup with the message
# "sshd: no hostkeys available -- exiting."
# @see https://stackoverflow.com/a/65348102/413531
RUN ssh-keygen -A
# we use SSH deployment configuration in PhpStorm for local development
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]
Настройка PhpStorm
Мы настроим удаленный интерпретатор PHP, который использует SSH-соединение для выполнения команд в контейнере application
. До этого мы использовали конфигурацию SFTP Deployment configuration
, которая была немного запутанной (“Что здесь делает SFTP?”), поэтому мы будем использовать SSH Configuration вместо этого и настроим сопоставление путей в интерфейсе интерпретатора Cli.
SSH-конфигурация
В File | Settings | Tools | SSH Configurations
создайте новую SSH-конфигурацию под названием “Docker PHP Tutorial” со следующими настройками
- Хост: 127.0.0.1
- Порт: см.
APPLICATION_SSH_HOST_PORT
в.docker/docker-compose/docker-compose.local.yml
- Имя пользователя: см.
APP_USER_NAME
в.make/.env
- Тип аутентификации: Пароль
- Пароль: см.
APP_SSH_PASSWORD
в.docker/.env
Интерпретатор PHP
В File | Settings | PHP
добавьте новый интерпретатор PHP CLI, который использует новую конфигурацию SSH.
Кроме того, мы определяем путь к расширению xdebug, поскольку по умолчанию оно отключено, но PhpStorm может включить его автоматически, если потребуется. Вы можете найти путь в контейнере application
через
root:/var/www/app# php -i | grep extension_dir
extension_dir => /usr/lib/php8/modules => /usr/lib/php8/modules
root:/var/www/app# ll /usr/lib/php8/modules | grep xdebug
-rwxr-xr-x 1 root root 303936 Jan 9 00:21 xdebug.so
Нам все еще нужно закрепить Xdebug на PhpStorm при запуске из контейнера Docker, добавив пользовательскую опцию PHP для xdebug.client_host=host.docker.internal
. Это же значение мы используем в .docker/images/php/base/conf.d/zz-app-local.ini
.
Теперь в обзоре интерпретатора мы должны настроить сопоставление путей, чтобы PhpStorm знал, “какой локальный файл принадлежит какому удаленному”. Удаленная папка определяется в .docker/.env
через
APP_CODE_PATH_CONTAINER=/var/www/app
После этого мы можем установить точку останова, например, в setup.php
и начать отладку:
На скриншоте видно, что PhpStorm добавляет расширение Xdebug, которое мы определили ранее.
PHPUnit
phpunit
настраивается через File | Settings | PHP | Test Frameworks
. Сначала мы выбираем интерпретатор, который мы только что добавили.
Затем мы добавляем пути к скрипту автозагрузки composer и файлу конфигурации phpunit.xml
.
Теперь PhpStorm будет выполнять тесты с использованием интерпретатора PHP в контейнере application
.
Отладка
Прежде всего, если вы еще не сделали этого, пожалуйста, ознакомьтесь с официальной документацией по xdebug. Дерик проделывает огромную работу по подробному объяснению xdebug, включая некоторые полезные видео, например, Xdebug 3: Xdebug с Docker и PhpStorm за 5 минут.
Отладка кода, выполняемого через PhpStorm
Это уже должно работать из коробки. Просто установите точку прерывания, щелкните правой кнопкой мыши на файле и выберите “Debug ‘…'”.
Отладка кода, выполняемого через php-fpm, cli или с рабочего сервера
Для кода, который выполняется “напрямую” контейнером без PhpStorm, сначала нужно включить xdebug
в контейнере, удалив ;
перед расширением в /etc/php8/conf.d/zz-app-local.ini
.
; Note:
; Remove the comment ; to enable debugging
zend_extension=xdebug
Чтобы сделать это немного удобнее, мы используем специальные рецепты make для этих действий в .make/01-01-application-commands.mk
.PHONY: execute-in-container
execute-in-container: ## Execute a command in a container. E.g. via "make execute-in-container DOCKER_SERVICE_NAME=php-fpm COMMAND="echo 'hello'"
@$(if $(DOCKER_SERVICE_NAME),,$(error DOCKER_SERVICE_NAME is undefined))
@$(if $(COMMAND),,$(error COMMAND is undefined))
$(EXECUTE_IN_CONTAINER) $(COMMAND);
.PHONY: enable-xdebug
enable-xdebug: ## Enable xdebug in the given container specified by "DOCKER_SERVICE_NAME". E.g. "make enable-xdebug DOCKER_SERVICE_NAME=php-fpm"
"$(MAKE)" execute-in-container APP_USER_NAME="root" DOCKER_SERVICE_NAME=$(DOCKER_SERVICE_NAME) COMMAND="sed -i 's/.*zend_extension=xdebug/zend_extension=xdebug/' '/etc/php8/conf.d/zz-app-local.ini'"
.PHONY: disable-xdebug
disable-xdebug: ## Disable xdebug in the given container specified by "DOCKER_SERVICE_NAME". E.g. "make enable-xdebug DOCKER_SERVICE_NAME=php-fpm"
"$(MAKE)" execute-in-container APP_USER_NAME="root" DOCKER_SERVICE_NAME=$(DOCKER_SERVICE_NAME) COMMAND="sed -i 's/.*zend_extension=xdebug/;zend_extension=xdebug/' '/etc/php8/conf.d/zz-app-local.ini'"
Чтобы перехватывать входящие запросы, нам нужно заставить PhpStorm прослушивать соединения PHP Debug через Run | Start Listening for PHP Debug Connections
.
Соответствующие порты настраиваются в File | Settings | PHP | Debug
. В Xdebug < 3 порт по умолчанию был 9000
, а в Xdebug 3 это 9003
.
Наконец, нам нужно добавить сервер через File | Settings | PHP | Servers
.
Имя сервера должно соответствовать значению ключа serverName
в переменной окружения PHP_IDE_CONFIG
, которую мы настроили ранее как serverName=dofroscra
.
php-fpm
Для php-fpm
мы должны перезапустить процесс php-fpm
без перезапуска контейнера после активации xdebug
через
kill -USR2 1
Поскольку это трудно запомнить, мы добавляем цель make в .make/01-01-application-commands.mk
.
# @see https://stackoverflow.com/a/43076457
.PHONY: restart-php-fpm
restart-php-fpm: ## Restart the php-fpm service
"$(MAKE)" execute-in-container DOCKER_SERVICE_NAME=$(DOCKER_SERVICE_NAME_PHP_FPM) COMMAND="kill -USR2 1"
Теперь мы можем просто запустить
make enable-xdebug DOCKER_SERVICE_NAME=php-fpm
make restart-php-fpm
Установка точки останова в public/index.php
и открытие http://127.0.0.1/ в браузере или через curl http://127.0.0.1/
остановит выполнение, как и ожидалось.
cli
Вместо того, чтобы запускать PHP-скрипт через HTTP-запрос, мы также можем запускать CLI-скрипты – вспомните, например, цель make setup-db
. Чтобы отладить такие вызовы, нам нужно выполнить те же шаги, что и раньше:
- включить расширение
xdebug
в контейнереapplication
. - “Прослушивание подключений для отладки PHP” из PhpStorm
Выполнение следующих целей make вызовет точку останова в setup.php
:
make enable-xdebug DOCKER_SERVICE_NAME=application
make setup-db
php-workers
И, наконец, то же самое для долго работающих процессов PHP (они же рабочие). Как и раньше:
- включите расширение
xdebug
в контейнереphp-worker
. - “Прослушивание соединений для отладки PHP” из PhpStorm
- перезапустить php workers
Выполнение следующих целей make вызовет точку останова в worker.php
:
make enable-xdebug DOCKER_SERVICE_NAME=php-worker
make restart-workers
strace
strace – это отличный инструмент для отладки долго работающих процессов, который я взял на вооружение после прочтения книги “Что делает PHP?”. Я добавил его в базовый образ php:
RUN apk add --update --no-cache
strace
Вы можете подключиться к любому запущенному процессу через sudo strace -p $processId
– НО это не работает из коробки на docker и приведет к ошибке с сообщением об ошибке
strace: attach: ptrace(PTRACE_SEIZE, 1): Operation not permitted
Это вызвано мерой безопасности docker и может быть обойдено путем добавления
services:
service:
cap_add:
- "SYS_PTRACE"
security_opt:
- "seccomp=unconfined"
в .docker/docker-compose/docker-compose.local.yml
ко всем контейнерам PHP. После восстановления и перезапуска установки docker теперь можно, например, войти в контейнер php-worker
и запустить strace
на рабочем процессе php:
application:/var/www/app# ps aux
PID USER TIME COMMAND
1 applicat 0:00 {supervisord} /usr/bin/python3 /usr/bin/supervisord
7 applicat 0:00 php /var/www/app/worker.php
8 applicat 0:00 php /var/www/app/worker.php
9 applicat 0:00 php /var/www/app/worker.php
10 applicat 0:00 php /var/www/app/worker.php
11 applicat 0:00 bash
20 applicat 0:00 ps aux
application:/var/www/app# sudo strace -p 7
strace: Process 7 attached
restart_syscall(<... resuming interrupted read ...>) = 0
poll([{fd=4, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 0 (Timeout)
sendto(4, "*2rn$4rnRPOPrn$5rnqueuern", 25, MSG_DONTWAIT, NULL, 0) = 25
poll([{fd=4, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 1 ([{fd=4, revents=POLLIN}])
recvfrom(4, "$", 1, MSG_PEEK, NULL, NULL) = 1
Подведение итогов
Поздравляем, у вас все получилось! Если что-то не совсем понятно, не стесняйтесь оставить комментарий. Кроме того, теперь у вас должна быть полностью настроенная система разработки, работающая с PhpStorm в качестве IDE.
В следующей части этого руководства мы будем использовать свежую установку Laravel поверх нашей установки.
Пожалуйста, подпишитесь на RSS канал или по электронной почте, чтобы получать автоматические уведомления о выходе следующей части 🙂