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:
enable_xdebug
is the internal DDEV equivalent toddev xdebug
and enables Xdebug inside the web container/debugproxy/flow-debugproxy ...
starts the Debug Proxy with these arguments:-vv --debug
"very verbose" and--debug
for a detailed output--xdebug 0.0.0.0:9003
tells the Debug Proxy to listen to the local port 9003 for incoming Xdebug connections.--ide host.docker.internal:9003
tells the Debug Proxy where to find the Xdebug client of your IDE.--localroot /var/www/html
tells the Debug Proxy, where to find your code which is important together with ...--framework flow
and ...--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 inFLOW_CONTEXT
in the previous step.
- The first line
trap disable_xdebug SIGINT
is explained last. This line waits forCtrl + C
and then executesdisable_xdebug
which is the internal DDEV equivalent toddev xdebug off
Using Xdebug break points in your IDE #
Unfortunately, the Debug Proxy cannot translate your clicky-clicky breapoints in your IDE.
But you simply can type xdebug_break()
- which works fine, as long as we have Xdebug enabled.
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.