PHP Debugging with PhpStorm and Xdebug

Here’s how to go about debugging, stepping through, and profiling remote code like a breeze. These are the steps I took to install/enable Xdebug on a remote LAMP stack and debug/profile hosted code using PhpStorm and a Chrome extension. As a bonus I’ll share how I debug cURL requests with Xdebug too.

1. Setup remote hosting with SSH access

I’m using a VMWare development machine with a CentOS LAMP stack to mirror a production RHEL environment. You can use a shared host with SSH access, an AWS EC2 instance, or XAMP/WAMP/MAMP if you like. As long as you can SSH into the server and install Xdebug then you can try this out and speed up your debugging. No more echo "AAAA" statements.

2. Install or compile Xdebug

Xdebug may already be installed and running. You can check first like so.

If it is, then you can move on to the next step. Otherwise, let’s install or compile Xdebug with the official install docs from xdebug.org.

3. Configure Xdebug

First, find out which php.ini file is being parsed. Here is an example from CentOS:

Here are a couple choices of ini files to edit:

  1. You can edit the master php.ini in /etc/php5/apache2/php.ini
  2. You can edit xdebug.ini in /etc/php5/apache2/conf.d/xdebug.ini

If you see a path that has “cli” in it, be aware that may be the command line interface php.ini which will be different than the one Apache PHP uses (the default is mod_php).

I prefer to specify the path to php.ini in one of my Apache config files. For example in httpd.conf I have:

Whichever file you choose to hold your configuration settings, here are the settings I used to enable robust debugging:

Be sure to set xdebug.remote_autostart = Off to prevent your server from crawling on every page request. If it is On that means Xdebug will always try to start a remote debugging session which is probably not what you want.

If you are interested in fine-tuning your Xdebug, here are all the settings you can adjust. If you are for now mainly interested in halting code at breakpoints in PhpStorm, or profiling code, then let’s continue.

I’ve found that if you are using WinCacheGrind on Windows, the profiler output name should start with “cachegrind” instead of “callgrind” because WinCacheGrind only recognizes one type of file.

You may want to ensure the write permissions are set to allow Xdebug to write to “/tmp/xdebug/”. This shouldn’t be a problem out of the box. It’s just a gotcha if you find that no profiling logs are being written.

If you are wondering why xdebug.remote_host is commented out above, here is an interesting point from the official docs that says why you can safely omit it:

xdebug.remote_connect_back
Type: boolean, Default value: 0, Introduced in Xdebug > 2.1
If enabled, the xdebug.remote_host setting is ignored and Xdebug will try to connect to the client that made the HTTP request.

Restart Apache with apachectl restart or systemctl restart httpd.service (CentOS 7), or /etc/init.d/apache2 restart on Debian.

4. Setting the Debugger Configuration in PhpStorm

Set PhpStorm to listen to Xdebug on port 9000
Set PhpStorm to listen to Xdebug on port 9000

JetBrains, the maker of PhpStorm, has detailed instructions on configuring Xdebug in their IDE. There are a few places settings need to be set, so I defer to their help pages. Essentially, you want to set the Xdebug IDE port to 9000 and the IDE key to PHPSTORM.

5. Triggering Xdebug

On a development box it’s sufficient for the server to connect back to port 9000 on the IDE over HTTP. To connect over an SSH tunnel, here is a guide from the makers of PhpStorm: Remote debugging in PhpStorm via SSH.

First, you must tell PhpStorm to listen for Xdebug connections by clicking the “phone” icon like so:

Enable Xdebug listening in PhpStorm
Enable Xdebug listening in PhpStorm

To trigger debugging, it’s necessary to send a special cookie along with each page request you wish to debug: XDEBUG_SESSION=PHPSTORM

To simplify this, Chrome has an extension called Xdebug helper that let’s you toggle setting this cookie or not.

Xdebug helper for Chrome
Xdebug helper for Chrome

With the above settings and the Chrome helper, you can now step through PHP code at breakpoints set in the IDE.

Sample Xdebug breakpoint session
Sample Xdebug breakpoint session

The above is great for debugging simple scripts, but what about scripts called with cURL?

6. Bonus: Xdebugging cURL Requests

If you’re using the settings from step 1, then you have all the tools to Xdebug PHP requests from cURL or wget. It’s actually pretty simple to Xdebug PHP files called from cURL. I’ll present a method for the CLI and from PHP curl.

The method is to set the XDEBUG_SESSION cookie, and to send the connect-back IP in a custom header. With the settings from step 1, Xdebug will examine $_SERVER['HTTP_X_XDEBUG_REMOTE_ADDRESS'] for the IP to connect back to. In order to set that from a custom header you must set the header as

X-Xdebug-Remote-Address

not

HTTP-X-Xdebug-Remote-Address

as you must not add the ‘HTTP-‘ part in front because it will be added automatically.

CLI cURL Xdebug

You can set the cookie and header on the command line as in the above example.

PHP cURL Xdebug

You can set the cookie and header in an options array using curl_setopt() below.

Even better and more convenient is to set the connect-back address dynamically

The above will set the IP address of the caller to the PHP script that initiates the cURL request (i.e. the browser on the same machine as the IDE). I make curl requests to other sites in my web ecosystem, so this is a very convenient way to debug those requests also.

7. CLI scripts and Xdebug

While in the shell you can set an Xdebug configuration environment variable with the IP address of the machine where your IDE is. Before you run your PHP scripts (e.g. >php -dzend.assertions=1 -dassert.exception=1 script.php), you can enable debugging for the life of the shell session with this:

Conclusion

Xdebug is amazing and invaluable. If you start Xdebugging like this, you will never go back to print_r or var_dump or echo 'AAAA' ever again.