Remote PHP Debugging with XDebug, Atom and Homestead
This is my development environment. There are many like it, but this one is mine.
Most of my time these days is spent working on Laravel and Lumen projects. Until a few months ago that was not the case, and until I joined Vehikl about a year ago I hadn’t worked with PHP for anything significant in almost half a decade. Getting my feet wet again has involved a lot of learning, familiarizing myself with common tools and getting caught up with the state of PHP development.
With so much to learn I defaulted to debug-by-dump. It’s still an effective tactic and I don’t expect to ever get away from it completely, but there are times when knowing the state of an application when a problem occurs is deeply valuable. Interactive debugging is a very old concept in software development and a core tool that I’d been ignoring, largely due to laziness.
A week ago or so I stole a few minutes here and there to get interactive debugging up and running in my environment and along the way I noticed that there wasn’t much in the way of guides for my specific development snowflake.
As mentioned, I work with Laravel. Since I work with Laravel almost exclusively, I use Laravel Homestead for my development environment. I find specific version numbers very helpful when determining whether or not any random Internet document is helpful for the problem I’m working on, so for people who came here as a result of a search, I am using:
- laravel/framework 5.3.*
- laravel/homestead ^3.0
- php 7.0.8
- xdebug 2.4.0
- Atom 1.9.6
- Chrome 52.0.2743.116 (64-bit).
If you don’t have exactly these versions of things I hope this information is useful, but what I’ve discovered is that it might not be. In the event that you are not, I’ll also discuss some of my research and troubleshooting efforts in the hopes that you get set on a useful path.
One last caveat: Vehikl is (the best!) client services agency, and as a result of working on any number of client projects (let alone personal ones), my preference is for Per Project Installation of Homestead. In principle, using a global installation shouldn’t change the details, but I haven’t tested.
Because this is a fairly specific use case, I’m assuming at least shallow familiarity with Atom, Homestead and Laravel so I’m likely to gloss over a step or keystroke here and there.
There are three major areas of setup necessary:
1) Installation and configuration of the Homestead box
2) Installation and configuration of the php-debug package for Atom
3) Installation and configuration of an Xdebug extension for your browser.
For the purposes of demonstration I’m going to start with a new project. From inside a terminal:
laravel new debugcd debugcomposer require laravel/homestead — devphp vendor/bin/homestead make
Since it’s a new installation and I’ve got a bunch on my system, I need to change the ip address and I like to create entries in my hosts file so I’m not interacting with applications by ip address once the installation is finished
For the purposes of this tutorial, I’ll add the entry 192.168.100.10 debug.dev to my etc/hosts file.
After doing so, use Atom to open the project folder and open Homestead.yaml. Inside the file, set the map value to `debug.dev`, update the IP address, and on line 31 uncomment the ports: statement and add the following two lines below it:
- send: 9999to: 9000
Save the file, switch to the terminal and boot up the vagrant box:
At this point in the process, you should be able to ping debug.dev and see responses from 192.168.100.10, and you should be able to load http://debug.dev in your browser and see the Laravel 5.3 welcome screen. If not, you’ll need to scroll up through your terminal for any error messages that were missed and investigate and resolve them. You can’t go further with this setup if you can’t ping the vagrant box and load the web application.
vagrant sshsudo vim /etc/php/7.0/fpm/conf.d/20-xdebug.ini
You need to edit the xdebug.ini file with write privileges. I don’t care if you use vim or whatever, this is just how I interact with configuration files. As near as I can tell (with admittedly little experimentation) the minimum lines of configuration required for remote debugging are:
xdebug.remote_enable = 1xdebug.remote_connect_back = 1
(These lines should be added after the existing zend_extension=xdebug.so line.)
Save your changes, and restart php-fpm:
sudo service php7.0-fpm restart
At this point, the server is likely ready to go but I don’t know of anything to do to prove it other than finish getting Atom and Chrome going to test and troubleshoot.
In Atom, install the php-debug package, and click the “Settings” button for it. A lot of the documentation I found instructs you to edit Atom’s config.cson file but in my experience that is no longer necessary and it’s easier to set things up through the package settings. I suspect the ability to do so is newer than most of the bits I read elsewhere.
There’s only one setting that needs to change, the Path Maps setting. We need to map the root path of the project on the remote system (our Homestead box) to that on the local system, which in my case is OS x.
Mine looks like this. The remote path is first, the local second, and they are separated with a semi-colon.
Once that’s set, you can close the preferences dialog.
Now switch to your browser. If you’re using Chrome, you want to add the extension Xdebug Helper, if you’re on a different browser Google for ‘xdebug firefox’ or Bing ‘xdebug edge’ or whatever and look for an extension that allows you to interact with xdebug from your browser. There’s many.
After installed it I had to close the tab I had open to the Debug application and create a new one before I could properly interact with it. After doing so, I clicked on the bug icon and chose “Debug”.
Back in Atom, open the routes/web.php file, right-click on line 15 then select “PHP-Debug > Toggle Breakpoint” to add a breakpoint to the line. You can also use the key combination alt-F9, but note that if you’re on OS X, by default you’ll also need to press the fn button or else you’ll skip to the next track in your current playlist.
Enable PHP-Debug’s debugging mode in Atom. You can do this by right-clicking in Atom and selecting “PHP-Debug > Toggle Debugging”, by clicking the “PHP Debug” button in the Atom status bar, or by using the keystroke ctrl-alt-d.
If everything worked properly, you can now switch to your browser and refresh the page. When you switch back to Atom, line 15 will be highlighted green (if you use the default dark colour scheme) and the buttons in the debug section will be active. You should be able to inspect the stack, step into and over lines of execution, add watchpoints and do all kinds of good old fashioned interactive debugging.
If everything didn’t work it can be very difficult to troubleshoot, especially if like me you only have shallow knowledge of Atom packages and Xdebug.
When I was trying to get everything setup, there were two major issues that blocked me.
First, I wasn’t using the browser extension. At some point I’d followed instructions to set xdebug.remote_enable=1 inside the xdebug.ini, then removed it while I was trying different configuration variations. Since I’m still working on incorporating interactive debugging into my usual workflow and expect to stay good friends with Laravel’s dd() for the forseeable future, I’m going to stick with the browser extension.
The other problem I had was around figuring out the correct ports configuration in Homestead.yml. This originated from a lack of understanding of how vagrant’s port-mapping functionality works, as well as following suggested xdebug configurations that included setting xdebug.remote_host=127.0.0.1 which I’m pretty sure actually only works if you’re connecting from the same machine where xdebug is running.
Along the way there were a couple of tricks I discovered. Php-debug’s connection information is output beside the flow control inputs. With this configuration it should read “Listening on port 9000…” when nothing is happening. When you load a page and Atom and XDebug are talking to each other it should change to Connected. If there’s a breakpoint anywhere in the flow of the requested page it will stay as Connected, otherwise it will flicker back to the Listening state. If the server and Atom are not communicating at all, the message will never change.
You can also set Xdebug to log which may help identify problems with php-debug’s package settings. To do so, add the following line to Xdebug’s configuration file and tail the output.
xdebug.remote_log = /tmp/xdebug.txt
You can set the path to whatever you want, but Xdebug’s logs are quite verbose which is super helpful, but once you do figure everything out you’ll want remove it again. Don’t forget to restart php7.0-fpm every time you change the config file.
Interactive debugging isn’t the be-all and end-all of troubleshooting, and I don’t expect to abandon var_dump, print_r and dd (one of my favourite toolkit additions, courtesy of Laravel) any time soon. It is an extremely helpful tool for understanding though, and critical for interacting with an application while it’s actively running, which is a really effective way of figuring out why it’s behaving in ways you don’t expect.