Using Xdebug in Neos CMS with DDEV and PhpStorm

Why did I need that? #

In the company, where I do my daily work, we are working with different PHP software. But all our projects come with a DDEV configuration to make life for developers easier when switching from one to another project.

I'm a huge DDEV fan, because it brings so many features, which can be used out of the box. One of these features is a well configured Xdebug integration, which can easily be enabled or disabled with ddev xdebug respectively ddev xdebug off.

But there is this one PHP software, where it is a bit more complicated: Neos CMS (or to be more precise: "Flow", the framework below Neos). Don't get me wrong, I like Neos CMS. It is a real cool CMS, with a great code base, following awesome concepts and ideas! Despite its steep learning curve, it is a lot of fun to work with. Especially thanks to the friendly and helpful community.

One of these cool concepts is called Aspect-Oriented Programming. If you are interested in details, please follow the link to learn more. But in very short words: all the PHP source code of Neos, Flow or any packages in your project will not be executed directly. Instead, Flow will create new PHP code in a temporary folder. These code is called "proxy classes" and is based on the original PHP code, merged with all the additional aspects.

For that reason, if you set an Xdebug breakpoint in your code, as you are used from other projects, nothing will happen. The PHP interpreter will never reach that part of code, but will execute the dynamically generated proxy class instead.

An alternative could be to not set a simple breakpoint in your IDE, but use xdebug_break() instead. This line of code then is also part of the code and also will be part of the proxy class. If the PHP interpreter will reach this line, Xdebug will stop here the proxy class will be opened for debugging in your IDE. Which is a bit better than seeing nothing, but still not what we really want: Be able to modify our original PHP code and debug it.

How I solved it? #

The good news is: There is a Flow Framework Debug Proxy for Xdebug from Dominique Feyer, who is a well known and long time contributor to Flow and Neos.

My goal was to integrate the Debug Proxy into our Neos projects on DDEV level to make all my colleagues directly profit from it, without the need to setup individually for each of them.

Install Debug Proxy in DDEV project #

I created a file .ddev/web-build/Dockerfile.xdebug with this content:

ARG BASE_IMAGE
FROM $BASE_IMAGE

RUN apt update && \
    apt install golang -y && \
    git clone https://github.com/dfeyer/flow-debugproxy.git /debugproxy && \
    cd /debugproxy && \
    go get && \
    go build

This file ensures that go is installed in the DDEV container and the flow-debugproxy project is cloned and compiled, when the DDEV container is built. The disadvantage of this is, that the first start of your DDEV projects will take a bit longer until this is built. But restarting the DDEV project is not affected, because the previously built containers will be reused until the next DDEV update.

Tell Xdebug to talk to the Debug Proxy #

Per default, Xdebug inside DDEV is configured to directly talk to your IDE outside the DDEV container. So we need to tell Xdebug to talk to the container itself, where the Debug Proxy is installed via client_host=127.0.0.1. I also prefer to set the port to 9001 instead of Xdebug's default 9003 to be compatible with Flow's current sub process configuration.

This all can be done by setting an environment variable inside .ddev/config.yaml:

web_environment:
  - XDEBUG_CONFIG=client_host=127.0.0.1 client_port=9001

Prepare some more environment variables #

There are two more environment variables, we need to be aware of: FLOW_CONTEXT and FLOW_PATH_TEMPORARY_BASE. We do not necessarily need to configure them explicitly, but we need to know what they do and maybe modify them to our needs.

FLOW_CONTEXT can be set to any valid context, for example "Development" (which is the default) or including any subcontext like "Development/Ddev" or "Development/Foo". Whatever we set here, will influence the local storage path of the proxy classes and needs to be set to the Debug Proxy command in the next step.

FLOW_PATH_TEMPORARY_BASE is a configuration option, of the Flow framework to configure an alternative path to save the autogenerated proxy classes. The option must not be defined. You not need to set it to an empty string like in my example. You only must ensure you do not set it to any value anywhere.

web_environment:
  - XDEBUG_CONFIG=client_host=127.0.0.1 client_port=9001
  - FLOW_CONTEXT=Development
  - FLOW_PATH_TEMPORARY_BASE=

Use DDEV command to start Debug Proxy #

In a regular DDEV setup, we can use ddev xdebug to enable Xdebug and ddev xdebug off to disable it again. This does not cover, what we want here with the Debug Proxy. For this reason, I created an own DDEV command. I simply added the file .ddev/commands/web/debugproxy with this content:

#!/bin/bash

## Description: Start xdebug and debugproxy for the flow framework
## Usage: debugproxy

trap disable_xdebug SIGINT
enable_xdebug
/debugproxy/flow-debugproxy --vv --debug --xdebug 0.0.0.0:9001 --ide host.docker.internal:9003 --localroot /var/www/html --framework flow --context Development

When I type ddev debugproxy now, the command does the following steps:

  1. enable_xdebug is the internal DDEV equivalent to ddev xdebug and enables Xdebug inside the web container
  2. /debugproxy/flow-debugproxy ... starts the Debug Proxy with these arguments:
    1. -vv --debug "very verbose" and --debug for a detailed output
    2. --xdebug 0.0.0.0:9003 tells the Debug Proxy to listen to the local port 9003 for incoming Xdebug connections.
    3. --ide host.docker.internal:9003 tells the Debug Proxy where to find the Xdebug client of your IDE.
    4. --localroot /var/www/html tells the Debug Proxy, where to find your code which is important together with ...
    5. --framework flow and ...
    6. --context Development to let the Debug Proxy know where it can find the autogenerated proxy classes. You must define the same string as you configured in FLOW_CONTEXT in the previous step.
  3. The first line trap disable_xdebug SIGINT is explained last. This line waits for Ctrl + C and then executes disable_xdebug which is the internal DDEV equivalent to ddev xdebug off

Using Xdebug break points in your IDE #

Unfortunately, the Debug Proxy cannot translate your clicky-clicky breapoints in your IDE.

Screenshot from PhpStorm with a read circle that marks a breakpoint next to a PHP code line

But you simply can type xdebug_break() - which works fine, as long as we have Xdebug enabled.

Screenshot from PhpStorm with xdebug_break() call above a PHP code line

Additionall configuration #

For some cases and IDEs it's required to configure the environment variable PHP_IDE_CONFIG: serverName=projectname.ddev.site. Although DDEV automatically sets it by default, it does not exist inside all the nexted and proxy'd Flow runs. I tried different ways to set it, but at the moment I neither don't exactly know, when I need it, nor which one is the best way to set it.

The most stable way seems to set it for the web requests, either in the project's .htaccess file.

SetEnv PHP_IDE_CONFIG=serverName=projectname.ddev.site

If this is not possible in your setup, a more ugly way could be to place a line in Web/index.php

putenv('PHP_IDE_CONFIG=serverName=projectname.ddev.site');

I also experimented with Flow's subprocess configuration and set it via YAML, but although that looked, like a good solution, it somehow did not have any effect:

Neos:
  Flow:
    core:
      subRequestEnvironmentVariables:
        PHP_IDE_CONFIG: serverName=projectname.ddev.site

Conclusion #

Once set up, it's very easy to use. You just need to remember these few differences:

goal default DDEV behavior modified behavior
enable Xdebug type ddev xdebug type ddev debugproxy
stop Xdebug type ddev xdebug off press Ctrl + C
set breakpoints in your IDE click on the line number add xdebug_break()

Feedback welcome! #

If you have any feedback about my setup - questions or improvements - feel free to contact me. I'll be happy to read your thoughts.