Debugging PHP With Vim


I’ve always intended to carve out a little spot for myself in cyberspace, however it was this discovery with Vim that really motivated me to do it. Since switching over from a traditional IDE to Vim, I’d pretty much forgotten what a debugger was. I used gdb on a C project, Power Sharing, but that had been the last time that I’d ever debugged code. I became a master of dump & die and debug log formatting which has worked good for many years.

Last month, however, I decided to jump off the deep-end one Saturday afternoon and attempt to figure out a working debugger solution with Vim. I only wish I had done it sooner! I came across a wonderful plugin called Vdebug, that I’ve turned to numerous times in the last couple of weeks. In this post, I’m going to show how you can make use of this awesome piece of tech, as well!

For this guide, I’m on Ubuntu 18.04 and using Vim 8.0, PHP 7.2, Xdebug 2.6.1, the stable branch of Vdebug, and I serve it all up with Nginx 1.15.0.

Setting Up Vdebug

First, you need a version of Vim that was compiled with Python3 and signs. You can check this by running:

~$ vim --version

and verifying that you have  +python3 and +signs in the output. Any version of Vim greater than 7.0 should have these.

Vdebug works great on the command line or via a web application, and I’ll discuss both of those methods. But first, we have to install it. I use vim-plug for this as it allows on-demanding loading of plugins, but any plugin manager, or even a manual installation, will work. Then, there are a few Vdebug options that you’ll probably want to define in your .vimrc.

:help VdebugOptions

does a wonderful job of explaining what all of the options do and what each one defaults to. Whatever you don’t set in your .vimrc will be set to the default, and this is what I use in my .vimrc.


if !exists('g:vdebug_options')
    let g:vdebug_options = {}
endif
let g:vdebug_options.break_on_open = 0
let g:vdebug_options.ide_key = 'VIM'
let g:vdebug_options.watch_window_style = 'compact'
let g:vdebug_options.port = 9001

if !exists('g:vdebug_features')
    let g:vdebug_features = {}
endif
let g:vdebug_features.max_children = 128

I’ve made a few changes to the defaults. First, I set break_on_open to ‘0’, since I only want my scripts to break at the points I’ve set. I use the ide_key of ‘VIM’, but really you can use anything. It’s just a key that Xdebug uses to differentiate incoming connections and will only trigger on connections that have the same key. I’ve had the watch_window_style set to ‘compact’, but I just realized that I haven’t actually experimented with the default here, so that’ll be something to play around with on Monday. And I use port 9001 just to be different. vdebug_features are options that are passed on to the debugger, Xdebug, and max_children is the number of the array elements that will be displayed in Vdebug’s watch window.

Finally, the vdebug_keymap global allows you to customize the mappings that control Vdebug’s behavior, such as setting break points and stepping into or out of function calls.

:help VdebugKeys

will tell you more about the mappings and their defaults. If you’re curious, this is how I have mine set up:


let g:vdebug_keymap = {
    \    "run" : "<Leader>/",
    \    "run_to_cursor" : "<Down>",
    \    "step_over" : "<Up>",
    \    "step_into" : "<Right>",
    \    "step_out" : "<Left>",
    \    "close" : "Q",
    \    "detach" : "<F9>",
    \    "set_breakpoint" : "<Leader>br",
    \    "eval_visual" : "<Leader>v"
}

Installing and Configuring Xdebug

If you haven’t figured it out by now, Vdebug uses the Xdebug debugging engine, so we must install that next. I used Xdeubg’s helpful wizard to determine the correct download and installation method and it was relatively painless. Once you have Xdebug installed and working, which you can verify from the output of phpinfo() or php -i, you can begin configuring it to work with Vdebug.

I’ll start off talking about the CLI and then I’ll discuss how to make it work with a web application, as I use both in my daily workflow.

First, determine which version of PHP you’re using. This can be done with:

~$ php --version

For me, this produces:


jake@:dev-box:~$ php --version
PHP 7.2.10-0ubuntu0.18.04.1 (cli) (built: Sep 13 2018 13:45:02) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.2.10-0ubuntu0.18.04.1, Copyright (c) 1999-2018, by Zend Technologies
    with Xdebug v2.6.1, Copyright (c) 2002-2018, by Derick Rethans
    with blackfire v1.20.0~linux-x64-non_zts72, https://blackfire.io, by Blackfire

You can see that I’m using PHP 7.2 with Xdebug 2.6.1. You should replace the 7.2‘s with your version of PHP.

Now, head on over to /etc/php/7.2/cli. On a Debian derivative, such as Ubuntu, and when PHP is compiled with --with-config-file-scan-dir, which in my experience it always has been, we can just add our custom config into /etc/php/7.2/mods_available and create a symlink to it in /etc/php/7.2/cli/conf.d. This is nice because we can also just symlink to the same .ini file for the fpm version. So go ahead and run:

~$ sudo vim /etc/php/7.2/mods-available/xdebug.ini

and add this to it:


 zend_extension="/usr/lib/php/20170718/xdebug.so"
 xdebug.coverage_enable=1
 xdebug.default_enable=0
 xdebug.remote_enable=1
 xdebug.remote_connect_back=0
 xdebug.remote_host=127.0.0.1
 xdebug.remote_port=9001
 xdebug.remote_handler=dbgp
 xdebug.remote_log=/tmp/xdebug.log
 xdebug.remote_autostart=1
 xdebug.idekey=VIM
 xdebug.max_nesting_level=256

Here, remote_port and idekey should match what you used in your Vdebug settings. You will also need to make sure zend_extension is pointing to the correct object file for your installed version of Xdebug.

At this point, it’s important to note that I’m using Vim locally on my dev vm, so I set remote host to be 127.0.0.1. If you’re running Vim or gVim on your host machine, you may need to build an ssh tunnel to your dev box so that Vdebug can talk to Xdebug.

Now, we just need to symlink it.

~$ sudo phpenmod -s cli xdebug

This command will create a symlink in /etc/php/7.2/cli/conf.d pointing to the .ini in mods_available. For example:

jake@dev-box:~$ ls -l /etc/php/7.2/cli/conf.d/
total 0
.
.
.
lrwxrwxrwx 1 root root 38 Feb 25 00:53 20-xdebug.ini -> /etc/php/7.2/mods-available/xdebug.ini
.
.
.

Debugging a CLI Script

With everything properly installed and configured, you can start debugging a PHP script by setting a breakpoint (ie. pressing <F10> if you’re using the defaults), starting up Vdebug, <F5>, and running the PHP script.

Vdebug should stop at the breakpoint that you set and display a screen that should look similar to this:

The left window is the script that’s running with the red line indicating the breakpoint that the execution is paused on. The upper right window is the watch window, which will display all the locally scoped variables, PHP’s superglobals, and any define()‘s that you’ve defined. The window just below that is the stack trace, in this case it’s pretty short. The bottom window is the status window, indicating that the script is stopped on a break point and that Vdebug is listening on port 9001. You can also see in the status window that my custom mappings are being shown. For example <Leader>/ will run the script to the next break point and ‘Q’ will close Vdebug and halt the execution of the script where it’s at. At this point, I can step over or into the breakpoint, continue the script execution to the next breakpoint, or halt the execution completely.

Debugging a Web Application

The first thing we must do is to make sure php-fpm loads the xdeubg.ini. I use the same configuration for fpm as I do for cli, although you could have different settings if you choose. Assuming that you’re using the same .ini, we need to add the symlink to /etc/php/7.2/fpm/conf.d by running the command:

~$ sudo phpenmod -s fpm xdebug

This will create the symlink in fpm’s conf.d directory, as we did above with cli. One difference with the fpm configuration is that the service must be restarted for the configuration to take affect. PHP on the command line will load all of the .ini files that it needs every time it’s ran. In fpm mode, they are only loaded with the daemon is started.

~$ sudo systemctl restart php7.2-fpm.service

will restart the fpm service and load the new configuration.

In order to debug a PHP script via a web browser, we need to add XDEBUG_SESSION_START either as a GET or POST parameter or as a session cookie. However, rather than updating my front-end logic to send this parameter every time I want to debug something, I use a Chrome extension call Xdebug Helper, which sets this parameter automatically when activated.

With the Xdebug Helper extension, triggering a debug session via Chrome is pretty straight forward. After installing the extension, set your IDE Key in the options to whatever you have it set to in Vdebug and Xdebug. Then, enable debugging in the extension and refresh the page. In Vim, set a break point and start Vdebug. With Vdebug listening for a connection, you just need to hit a route, via the browser, into your web app to trigger the script that you want to debug. At that point, Vdebug should stop at the break point and display a screen similar to the image above where you can examine, step over, or into various sections of your code.

Conclusions

I hope you’ve enjoyed this post as much as I’ve enjoyed being able to debug in Vim. You might have to tweak some of the configuration based on your own setup, but the documentation for Vdebug and Xdebug is pretty thorough. As for myself, I’m still learning about this plugin, as is evident in my realization that I haven’t even seen what the default watch window display looks like, and look forward to a future with less dump and dies or error_log() calls! If you find any features of that I missed, please leave a comment! I’d also love to hear about your experiences debugging with Vim, as well!


2 responses to “Debugging PHP With Vim”

Leave a Reply

Your email address will not be published.