Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
June 24, 2022 08:22 am GMT

PhpStorm, Docker and Xdebug 3 on PHP 8.1 in 2022 [Tutorial Part 4.2]

This article appeared first on https://www.pascallandau.com/ at PhpStorm, Docker and Xdebug 3 on PHP 8.1 in 2022 [Tutorial Part 4.2]

In this part of the tutorial series on developing PHP on Docker we will setup our local development environment to be used by PhpStorm and Xdebug. We will also ensure that we can run PHPUnit tests from the command line as well as from PhpStorm and throw the tool strace into the mix for debugging long running processes.

All code samples are publicly available in my Docker PHP Tutorial repository on Github. You find the branch for this tutorial at part-4-2-phpstorm-docker-xdebug-3-php-8-1-in-2022

All published parts of the Docker PHP Tutorial are collected under a dedicated page at Docker PHP Tutorial. The previous part was Docker from scratch for PHP 8.1 Applications in 2022 and the following one is Run Laravel 9 on Docker in 2022.

If you want to follow along, please subscribe to the RSS feed or via email to get automatic notifications when the next part comes out :)

Table of contents

  • Introduction
  • Install Tools
    • Install composer
    • Install Xdebug
    • Install PHPUnit
    • Install SSH
  • Setup PhpStorm
    • SSH Configuration
    • PHP Interpreter
    • PHPUnit
    • Debugging
      • Debug code executed via PhpStorm
      • Debug code executed via php-fpm, cli or from a worker
        • php-fpm
        • cli
        • php-workers
  • strace
  • Wrapping up

Introduction

This article is mostly an update of Setting up PhpStorm with Xdebug for local development on Docker but will also cover the "remaining cases" of debugging php-fpm and php worker processes.

We will still rely on an always-running docker setup that we connect to via an SSH Configuration instead of using the built-in docker-compose capabilities as I feel it's closer to what we do in CI / production. However, we will not use SSH keys any longer but simply authenticate via password. This reduces complexity and removes any pesky warnings regarding "SSH keys being exposed in a repository".

Install Tools

Install composer Composer is installed by pulling the official composer docker image and simply "copying" the composer executable over to the base php image. In addition, composer needs the extensions mbstring and phar

# File: .docker/images/php/base/DockerfileARG ALPINE_VERSIONARG COMPOSER_VERSIONFROM composer:${COMPOSER_VERSION} as composerFROM 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

Because we want our build to be deterministic, we "pin" the composer version by adding a COMPOSER_VERSION variable to the .docker/.env file

COMPOSER_VERSION=2.2.5

and using it in .docker/docker-compose/docker-compose-php-base.yml:

services:  php-base:    build:      args:        - COMPOSER_VERSION=${COMPOSER_VERSION?}

Install Xdebug Install the extension via apk (only for the local target):

# File: .docker/images/php/base/DockerfileFROM base as localRUN 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

We also don't want to enable xdebug immediately but only when we need it (due to the decrease in performance when the extension is enabled), hence we remove the default config file and disable the extension in the application .ini file

# File: .docker/images/php/base/conf.d/zz-app-local.ini; Note:; Remove the comment ; to enable debugging;zend_extension=xdebugxdebug.client_host=host.docker.internalxdebug.start_with_request=yesxdebug.mode=debug

See Fix Xdebug on PhpStorm when run from a Docker container for an explanation of the xdebug.client_host=host.docker.internal setting (previously called xdebug.remote_host in xdebug < 3). This will still work out of the box for Docker Desktop, but for Linux users we need to add the host-gateway magic reference
to all PHP containers (we can't add it to the php base image because this is a runtime setting):

services:  service:    extra_hosts:      - host.docker.internal:host-gateway

Finally, we need to add the environment variable PHP_IDE_CONFIG
to all PHP containers. The variable is defined as PHP_IDE_CONFIG=serverName=dofroscra, where "dofroscra" is the name of the server that we will configure later for debugging. Because we need the same value in multiple places, the variable is configured in .docker/.env:

PHP_IDE_CONFIG=serverName=dofroscra

And then added in .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?}

Install PHPUnit PHPUnit will be installed via composer but will not be "baked into the image" for local development. Thus, we must run composer require in the container. To make this more convenient a make target for running arbitrary composer commands is added in .make/01-00-application-setup.mk:

.PHONY: composercomposer: ## Run composer commands. Specify the command e.g. via ARGS="install"    $(EXECUTE_IN_APPLICATION_CONTAINER) composer $(ARGS);

This allows me to run make composer ARGS="install" from the host system to execute composer install in the container. In consequence, composer will use the PHP version and extensions of the application container to install the dependencies, yet I will still see the installed files locally because the codebase is configured as a volume for the container.

Before installing phpunit, we must add the required extensions dom and xml to the container

# File: .docker/images/php/base/Dockerfile# ...RUN apk add --update --no-cache  \        php-dom~=${TARGET_PHP_VERSION} \        php-xml~=${TARGET_PHP_VERSION} \

as well as rebuild and restart the docker setup via

make docker-buildmake docker-downmake docker-up

Now we can add phpunit via

make composer ARGS='require "phpunit/phpunit"'

which will create a composer.json file and setup up the vendor/ directory:

$ make composer ARGS='require "phpunit/phpunit"'Using version ^9.5 for phpunit/phpunit./composer.json has been createdRunning composer update phpunit/phpunitLoading composer repositories with package informationUpdating dependencies...

CAUTION: If you run into the following permission error at this step, you are likely using Linux and haven't set the APP_USER_ID and APP_GROUP_ID variables as described in the previous article under Solving permission issues.

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

I have also added

  • a minimal phpunit.xml config file
  • a test case at tests/SomeTest.php
  • and a new Makefile for "anything related to qa" at .make/01-02-application-qa.mk:
##@ [Application: QA].PHONY: testtest: ## Run the test suite     $(EXECUTE_IN_WORKER_CONTAINER) vendor/bin/phpunit -c phpunit.xml

So I can run tests simply via make test

$ make testENV=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/phpunitPHPUnit 9.5.13 by Sebastian Bergmann and contributors..                                                                   1 / 1 (100%)Time: 00:00.324, Memory: 4.00 MBOK (1 test, 1 assertion)

Install SSH

We will execute commands from PhpStorm via ssh in the application container. As mentioned, we won't use a key file for authentication but will instead simply use a password that is configured via the APP_SSH_PASSWORD variable in .docker/.env and passed to the image in .docker/docker-compose/docker-compose.local.yml. In addition, we map port 2222 from the host system to port 22 of the application container and make sure that the codebase is shared as a volume between host and container

  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"

The container already contains openssh and sets the password

ARG BASE_IMAGEFROM ${BASE_IMAGE} as baseFROM base as localRUN apk add --no-cache --update \        opensshARG APP_SSH_PASSWORDRUN 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 developmentEXPOSE 22CMD ["/usr/sbin/sshd", "-D"]

Setup PhpStorm

We will configure a remote PHP interpreter that uses an SSH connection to run commands in the application container. Before, we have been using an SFTP Deployment configuration , which was kinda confusing ("What is SFTP doing here?"), so we will use an SSH Configuration instead and configure the path mappings in the Cli Interpreter interface

SSH Configuration

At File | Settings | Tools | SSH Configurations create a new SSH Configuration named "Docker PHP Tutorial" with the following settings

  • Host: 127.0.0.1
  • Port: see APPLICATION_SSH_HOST_PORT in .docker/docker-compose/docker-compose.local.yml
  • User name: see APP_USER_NAME in .make/.env
  • Authentication type: Password
  • Password: see APP_SSH_PASSWORD in .docker/.env

PhpStorm SSH Configuration

PHP Interpreter

At File | Settings | PHP add a new PHP CLI interpreter that uses the new SSH Configuration

PhpStorm new CLI interpreter

In addition, we define the path to the xdebug extension because it is disabled by default but PhpStorm can enable it automatically if required. You can find the path in the application container via

root:/var/www/app# php -i | grep extension_dirextension_dir => /usr/lib/php8/modules => /usr/lib/php8/modulesroot:/var/www/app# ll /usr/lib/php8/modules | grep xdebug-rwxr-xr-x    1 root     root        303936 Jan  9 00:21 xdebug.so

We still need to Fix Xdebug on PhpStorm when run from a Docker container by adding a custom PHP option for xdebug.client_host=host.docker.internal. That's the same value we use in .docker/images/php/base/conf.d/zz-app-local.ini.

PhpStorm Xdebug settings for the CLI interpreter

In the interpreter overview we must now configure the path mappings so that PhpStorm knows "which local file belongs to which remote one". The remote folder is defined in .docker/.env via

APP_CODE_PATH_CONTAINER=/var/www/app

PhpStorm path mappings for the CLI interpreter

Afterwards we can set a breakpoint e.g. in setup.php and start debugging:

PhpStorm debugging breakpoint

The screenshot shows that PhpStorm adds the Xdebug extension that we defined previously.

PHPUnit

phpunit is configured via File | Settings | PHP | Test Frameworks. First, we select the interpreter that we just added

Set up phpunit in PhpStorm

Then, we add the paths to the composer autoload script and the phpunit.xml configuration file.

phpunit settings in PhpStorm

PhpStorm will now execute tests using the PHP interpreter in the application container

Run a phpunit test in PhpStorm

Debugging

First of all, if you haven't already please also take a look at the official xdebug documentation. Derick is doing a great job at explaining xdebug in detail including some helpful videos like Xdebug 3: Xdebug with Docker and PhpStorm in 5 minutes

Debug code executed via PhpStorm

This should already work out of the box. Simply set a break point, right-click on a file and choose "Debug '...'"

PhpStorm debugging breakpoint

Debug code executed via php-fpm, cli or from a worker

For code that is executed "directly" by a container without PhpStorm, we first need to enable xdebug in the container by removing the ; in front of the extension in /etc/php8/conf.d/zz-app-local.ini

; Note:; Remove the comment ; to enable debuggingzend_extension=xdebug

To make this a little more convenient, we use dedicated make recipes for those actions in .make/01-01-application-commands.mk

.PHONY: execute-in-containerexecute-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-xdebugenable-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-xdebugdisable-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'"

To capture incoming requests, we need to make PhpStorm listen for PHP Debug connections via Run | Start Listening for PHP Debug Connections.

PhpStorm: Start Listening for PHP Debug Connections

The corresponding ports are configured at File | Settings | PHP | Debug. In Xdebug < 3 the default port was 9000 and in Xdebug 3 it is 9003

PhpStorm: configure xdebug ports

Finally, we need to add a server via File | Settings | PHP | Servers

PhpStorm: configure a server

The name of the server must match the value of the serverName key in the environment variable PHP_IDE_CONFIG that we configured previously as serverName=dofroscra.

php-fpm

For php-fpm we must restart the php-fpm process without restarting the container after we have activated xdebug via

kill -USR2 1

Since this is a pain to remember, we add a make target in .make/01-01-application-commands.mk

# @see https://stackoverflow.com/a/43076457.PHONY: restart-php-fpmrestart-php-fpm: ## Restart the php-fpm service    "$(MAKE)" execute-in-container DOCKER_SERVICE_NAME=$(DOCKER_SERVICE_NAME_PHP_FPM) COMMAND="kill -USR2 1"

So we can now simply run

make enable-xdebug DOCKER_SERVICE_NAME=php-fpmmake restart-php-fpm

Setting a breakpoint in public/index.php and opening http://127.0.0.1/ in a browser or via curl http://127.0.0.1/ will halt the execution as expected.

PhpStorm debugging breakpoint for php-fpm

cli

Instead of triggering a PHP script via HTTP request, we can also run CLI scripts - think of the make setup-db target for instance. To debug such invocations, we need to follow the same steps as before:

  • enable the xdebug extension in the application container
  • "Listening for PHP Debug Connections" from PhpStorm

Running the following make targets will trigger a breakpoint in setup.php:

make enable-xdebug DOCKER_SERVICE_NAME=applicationmake setup-db

PhpStorm debugging breakpoint for cli

php-workers

And finally the same thing for long running PHP processes (aka workers). Just as before:

  • enable the xdebug extension in the php-worker container
  • "Listening for PHP Debug Connections" from PhpStorm
  • restart the php workers

Running the following make targets will trigger a breakpoint in worker.php:

make enable-xdebug DOCKER_SERVICE_NAME=php-workermake restart-workers

PhpStorm debugging breakpoint for php-workers

strace

strace is a great tool for debugging long running processes that I've adopted after reading What is PHP doing?. I've added it to the php base image:

RUN apk add --update --no-cache \        strace

You can attach to any running process via sudo strace -p $processId - BUT that doesn't work out of the box on docker and will fail with the error message

strace: attach: ptrace(PTRACE_SEIZE, 1): Operation not permitted

This is caused by a security measure from docker and can be circumvented by adding

services:  service:    cap_add:      - "SYS_PTRACE"    security_opt:      - "seccomp=unconfined"

in .docker/docker-compose/docker-compose.local.yml to all PHP containers. After rebuilding and restarting the docker setup, you can now e.g. log in the php-worker container and run strace on a php worker process:

application:/var/www/app# ps auxPID   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 auxapplication:/var/www/app# sudo strace -p 7strace: Process 7 attachedrestart_syscall(<... resuming interrupted read ...>) = 0poll([{fd=4, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 0 (Timeout)sendto(4, "*2\r
$4\r
RPOP\r
$5\r
queue\r
", 25, MSG_DONTWAIT, NULL, 0) = 25poll([{fd=4, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 1 ([{fd=4, revents=POLLIN}])recvfrom(4, "$", 1, MSG_PEEK, NULL, NULL) = 1

Wrapping up

Congratulations, you made it! If some things are not completely clear by now, don't hesitate to leave a comment. Apart from that, you should now have a fully configured development setup that works with PhpStorm as your IDE.

In the next part of this tutorial, we will use a fresh installation of Laravel on top of our setup.

Please subscribe to the RSS feed or via email to get automatic notifications when this next part comes out :)


Original Link: https://dev.to/pascallandau/phpstorm-docker-and-xdebug-3-on-php-81-in-2022-tutorial-part-42-37m7

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To