Wednesday, February 1, 2012

Here is the scenario: You have multiple developers logged into a Linux server which is running Apache and PHP using Vim to write PHP code. They’re using error_log and echo statements to debug their code. It takes forever, it’s tedious and can result in bugs from forgotten debug statements. You stare enviously at .NET programmers with fancy debuggers (while you snicker knowing that you edit 10x faster with Vim anyways). But still, you know there has to be a better way.

There is.

Here’s how it works. You’re coding away in vim. You hit F5; Vim waits for a connection from the PHP server. You refresh the PHP page you’re working on. It attempts to contact Vim — connection successful. You are launched into a debugging session right inside Vim. You can step into, over, and out of statements, eval statements, get all variables in context, get and set properties, remove and set breakpoints, all on the fly. Finally, some real programming tools.

Copy debugger.vim and debugger.py to either one of following directories:

/usr/local/share/vim/vim72/plugin
or
~/.vim/plugin

Try to run vim — if you get no errors, everything should be good. If you get an error, double check :version to make sure +python and +signs are there. If they are, post a comment here with the vi error you get. If they aren’t, the vim compilation/installation didn’t work — go back and try it again.

; This switch controls whether Xdebug should try to contact a debug client which is listening on the host and port as set with the settings xdebug.remote_host and xdebug.remote_port.
xdebug.remote_enable = 0
xdebug.remote_port = 9000
xdebug.remote_host = localhost

; When this setting is set to on, the tracing of function calls will be enabled just before the script is run. This makes it possible to trace code in the auto_prepend_file.
xdebug.auto_trace = 1
xdebug.trace_output_dir = "/tmp/xdebug/log"
xdebug.collect_params = 4

; Controls the protection mechanism for infinite recursion protection. The value of this setting is the maximum level of nested functions that are allowed before the script will be aborted.
xdebug.max_nesting_level = 100

; This setting tells Xdebug to gather information about which variables are used in a certain scope. This analysis can be quite slow as Xdebug has to reverse engineer PHP's opcode arrays. This setting will not record which values the different variables have, for that use xdebug.collect_params. This setting needs to be enabled only if you wish to use xdebug_get_declared_vars().
xdebug.collect_vars = 0

; When set to '1' the trace files will be appended to, instead of being overwritten in subsequent requests.
; Note: this option can be useful if you could not find your function calls anywhere.
xdebug.trace_options = 1

Make sure xdebug is loaded by PHP

<?php
echo phpinfo();
?>

Now, with your site being example.com, go to http://example.com/index.php?XDEBUG_SESSION_START=1 (Add XDEBUG_SESSION_START=1 after your script name). This will set a cookie in your browser which expires in 1 hour which tells the PHP XDebug module to try to make a connection every time a page loads to a debugging client which is listening on port 9000. The cool thing is that if it can’t make a connection, it just keeps loading the page, so there’s no issue just leaving the cookie on.

Now go back to vim and press F5. You should see a message like “waiting for a new connection on port 9000 for 5 seconds…” at the bottom of the screen. You have five seconds to now refresh the PHP page. This will create a connection between the debugger and client. Now you’re debugging. Follow the instructions in the Help window to step into, over and out of code. Press F5 to run the code until a breakpoint (which you can set using :Bp).

When I pressed F1, F2, or F3 keys, it toggles the character to either upper or lower case.

To solve this problem, I changed following line:
map <F2> :python debugger_command('step_into')<cr>

To:
map ^[[12~ :python debugger_command('step_into')<cr>

Note: press ctrl-v F2 to generate ^[[12~.

But what if I have multiple developers on the same machine?

No problem. Simply set g:debuggerPort in each developer’s .vimrc to get the client listening on a different port. So if you wanted one developer to connect on 9001 instead of the standard 9000, you would add this line to their .vimrc:

let g:debuggerPort = 9001

Getting the server to connect on a different port is a little trickier. You need to set a custom php.ini value (xdebug.remote_port) for each user. It works best if you’re using VirtualHost’s in Apache. Just add the following line to the VirtualHost section of your httpd.conf:

php_value xdebug.remote_port 9001

Now restart Apache and if you use that VirtualHost and that vi user, then they should connect successfully.
That’s about it

Please post any questions or suggestions you may have. I hope this helps a few of you out there who want debugging tools but don’t want to give up Vim editing. Also be sure to post any alternate methods, or any patches or improvements to the remote PHP debugger vim script, and I’ll be sure to incorporate them.

[Note: This is the first in a series of posts we'll be doing along the lines of tutorials, tools we've developed, tech commentary, and so on. Please feel free to subscribe, as well as leave any comments, thoughts or suggestions below so we can be sure to improve with each article! Thanks!]