PHP debugging tricks from the trenches
This blogpost offers tips and tricks for debugging PHP applications. While I worked out the most of these tricks while doing Drupal-development with Phpstorm - the majority should be applicable for debugging most PHP-applications. Just ignore any mentions of "drush" and "Drupal" and you will be fine :)
I’ll get to the tips in just a moment, but first a couple of extra tidbits I ended up writing while describing the tips, that you might find useful.
If you need a brushup on how Xdebug and the IDE interacts take a look at “How Xdebug communicates with the IDE” - You might need it for the last tip.
If you’ve had problems getting Xdebug up and running I have some general links and two specific troubleshooting tips for you. Head over to XDEBUG Y U NO WORK - you might be in luck and this is what you’ve been looking for.
Last but not least, if you are not using Xdebug for your PHP-development, read this.
And now, without any further ado, on to the tricks:
Switching xdebug on and off on the commandline
You usually don’t want debugging to be enabled pr. default as it will slow down PHP-execution. On the web-side we have browser-plugins to help controlling when debugging should be enabled. On the commandline the easiest way to control whether XDebug is enabled is via environment variables.
When starting up xdebug will examine the “XDEBUG_CONFIG” environment-variable for configuration-settings that should override configurations set via php.ini. So if you do this:
$ export XDEBUG_CONFIG="profiler_enable=1"
All PHP-processes executed from the shell will have debugging enabled. The problem here is if you forget that you have enabled debugging, you might then later on run into the issue with eg. simultaneous debugging sessions.
Another approach is to set the environment for just one PHP-execution, eg.
$ XDEBUG_CONFIG="profiler_enable=1" drush status
The main disadvantage here is that you have to remember the exact syntax.
I have ended up using a simple shell-alias:
In my ~/.bashrc I have alias xdb='export XDEBUG_CONFIG="profiler_enable=1"' alias xdb_off='export XDEBUG_CONFIG="profiler_enable=0"'
If I want to enable/disable debugging for the remained of the shells lifetime I can use the aliases by themselves, but what I’ve actually ended up doing is running the alias in a sub-shell together with the command I want to debug like this:
$ (xdb && drush some-command)
The sub-shell demarcated by the parentheses will keep the environment-variable from “escaping” to its parent shell. So, with 9 extra characters to type I now have an easy way to enable the debugger for just one execution.
Step into code located inside phar-files (eg. drush)
Drush is now distributed as a single phar-file which is great as it makes it way easier to install (just rename drush.phar to drush, give it a quick chmod +x, and drop it somewhere in your PATH) but what about debugging and code-completion? Well PhpStorm actually supports phar-files as libraries just fine. Just right click “External libraries” and add a reference to a phar-file. You can either type/paste the full path or navigate to it - if you choose the latter be aware that PhpStorm expects you to choose a directory, so after clicking ok you have to enter the filename manually.
You can now debug any PHP-files inside the phar file including setting breakpoints and stepping into functions.
Finding a specific key or value in a nested array while debugging
It’s a quite common case in Drupal-development that you have to modify an array-entry deeply nested into another array. When the entry is located it's a simple case of using PhpStorms “Copy Path” feature to get at the value (see screenshot) - but actually locating that entry can be hard if the array is big.
If the array is somewhat flat, just expand the array and start typing and PhpStorm will do a quick search for you. Unfortunately, until WI-20520 gets fixed, PhpStorm will only search through expanded entries, which is a problem if we’re dealing with a large array.
My workaround these days is to export the full nested variable to text (I prefer print_r) and then copy-paste that into a buffer where I can then do an ordinary text-search.
It’s a bit of hack, and does require you to visually trace the path back to the root-element when you find the value you are looking for, but nevertheless I’ve found it quite effective.
Have the current debug-session run to completion without stopping at any more breakpoints
You’ve set a break-point in a block of code that iterates a large array, you have seen what you want to see, and now you just want to let the code run to completion without breaking at any of your breakpoints.
First, switch on “Unmute Breakpoints on Session Finish” under settings in the debug pane. Then, the next time you want to have the session run to completion, click the “mute breakpoint” button and then Resume Program. As your breakpoints are now muted, the session will run to completion, and your breakpoints will then be unmuted, and you are now ready for the next session.
XDebug seems to skip breakpoints set at specific locations
A breakpoint is set by the xdebug-client instructing the debugger to stop at a specific line in a specific file. Now, when PHP executes, it is not actually executing PHP-code line by line, instead each PHP-file is compiled to a series of opcodes which are then executed. PHP keeps track of which lines of code produced which opcodes, and that information is used by the debugger to determine when to stop at a breakpoint. But, not all lines of codes will produce opcodes, and not all opcodes can be stopped at.
PhpStorm will keep you from setting breakpoints at the more obvious invalid location such as blank lines and comments, but there are still a lot of places you can set a breakpoint that will never be hit. One I used to run into a lot is that an array-declarations line-number is defined by its first value. So in the following 4 line PHP-script you would have to set your breakpoint at line 3.
1: <?php 2: $array = [ 3: 'This is where you have to set your breakpoint' 4: ];
Another gotcha is that conditionals containing expressions that can be predetermined such as a straight
Is mostly optimized away when PHP is running as PHP does not have to do any actual comptuation on that line to get the value of the variable - it is already known. A breakpoint on line like this can never be reached.
If you want to dig really deep into this I can recommend the “Vulcan Logic Dumper” extension by Derick Rethans. It can dump the opcodes of a given PHP-script and tell you which line-numbers it corresponds to - on a Mac it’s a simple
$ brew install php<version>-vld # eg php56
And for the rest of you it’s also in PECL.
As a final example, I’ve set 26 breakpoints in the following example and simply resume execution each time one is hit. Only 14 of them are actually hit. Notice for instance how the debugger stops at line 11 but not line 16. The explanation for this can be found in the vld output below that shows that line 16 has been optimized into a NOP (no-operation opcode) and thus is and thus is never executed.
That's it for the tricks for this post - keep reading if you want a bit more information on how Xdebug works, why it sometimes does not work, and why you should use it.
How Xdebug communicates with the IDE
Xdebug is a PHP-extension that embeds an interactive debugger into PHP that allows an external client to control the flow of execution and amongst other things to be notified when certain conditions are met such as a pre-defined line being reached.
In other words, it lets PhpStorm remote-control a running PHP-application.
The flow of communication between PhpStorm and Xdebug could be something like this:
(see the XDebug documentation on packet communication for all the details you would ever want on this).
XDEBUG Y U NO WORK
Jetbrains has an excellent guide to how to setup Xdebug and PhpStorm.
And when you’ve completed the setup and your debugger is still not working, Jetbrains has a list of things to check.
Two gotchas I’ve run across a couple of times:
Beware of other processes using port 9000
PhpStorm will attempt to listen on port 9000 when you enable debugging, if it fails you will (in newer versions of PhpStorm) get this warning:
If you use php-fpm for your local PHP-development, there is a good chance that it is the cause of your problems as it also defaults to port 9000 for the communication between the webserver and fpm.
On a mac you can track down the process that is using port 9000 with the following terminal command
$ lsof -n -i:9000 | grep LISTEN
(Stackoverflow and/or Google will tell you how to do this if you’re on another OS)
Should you want to change which port Xdebug and PhpStorm uses it is done via the PHP-configuration “xdebug.remote_port” and the PhpStorm setting “Debug port” displayed below.
Remember that PhpStorm will default to port 9000 for any new project you create, so you will have to go in and change the setting for each project.
Simultaneous PHP-requests hangs without starting a debugging session
This either happens by accident because you forget that you have a debug session running and then attempt to start a new PHP process, or because the script you are debugging actually needs to start a new process eg it uses an api endpoint hosted by the same server as the script itself.
Pr. default PhpStorm will not accept simultaneous connections from xdebug. If you attempt to do so, the subsequent requests will hang the session before it is completed until the server responds.
You can change the configuration in the debug section of PhpStorm settings
Simultaneous connections will show up as tabs in the debug tool-pane in PhpStorm as shown here:
Should I be using a debugger?
That is, unless you’re implementing an '90s one-page guestbook, you should have a debugger ready in your development setup. In particular if you use any kind of framework.
A couple of quick reasons:
When you use a framework, chances are that you are debugging the problem inside out. That is, you have a pretty good idea where you've got a problem (your custom code), but no idea how you ended up in the problematic state. In these situations an overview of the call-stack and the ability to step debug beyond your own code is a lifesaver.
Problems occurs when code is running. “Debugging” by staring at code or var_dumps will only take you so far, if you really want to understand how your application runs and how problems occurs, you have to observe it while it is actually running.
Frameworks are huge, even seasoned developers with years of experience with a framework will regularly run into pieces of the framework they have never seen, or at the very least have never seen running. A step-debugger will let you follow the flow of execution into these pieces of code, and make it much easier to understand what it actually does.
While doing a quick debug_print_backtrace();print_r($myvar);die(“Keeping it oldschool”); is quick, you can be up and running with Xdebug in 15 minutes and the extra options Xdebug will give you will more than make up those lost 15 minutes in no time.