9 PHP Debugging Techniques You Should Be Using

screaming-man2

Isn’t writing new code great? Wouldn’t the world be a better place if all were ever had to do is write software from scratch, not having to worry about methods of classes past? Unfortunately, we all know that this is not the case. In fact, estimates say that we spend around 80% of our programming time maintaining old code. So for this blog post will be trying to tackle that 80%, and I will see what I can do to make it less painful.

Enable Notices (on your development environment!)

The wonderful developers of PHP (no, not PHP developers) bestowed a great gift when they created the language we all know and love, and that was the gift of notices.

What are notices? A notice is a type of error message which is less severe than parse, fatal and warning error messages. A notice may tell you something along the lines of “Hey, you have used that variable without defining it! What gives?”

Notices are very useful tool for avoiding bugs during development as they can catch problems caused by mistyped variable names or non-existent array indexes. This will save you a lot of headaches.

If you have been developing with notices turned off then please go and turn them on. Please! You can do this in one of three ways. Firstly, you can do this in your php.ini file:

error_reporting = E_ALL

Or you can insert the following line at the start of your PHP code:

ini_set(‘error_reporting’, E_ALL);

Lastly, you can set this on a per-virtual host in Apache HTTPD using the following:

php_value error_reporting 8191

If you want to test that notices are turned on you can simply create a file with the following:

<?php
echo "You should see a notice below this line<br />\n";
echo $thisVariableDoesNotExist;
?>

At this point I should stress that you should always ensure that the display_errors ini setting is set to ‘Off’ for any live/production sites. It is a very bad idea to allow the public to view your error messages because a) it looks bad, and b) it can give away a lot of information to potential crackers.

Now that you have turned notices on you may find that your code creates a lot of them. I would start correcting these at the first chance you get as you will never see new notices amid such clutter. You may find empty() and is_set() useful.

See also:

Use a Logging System

A logging system can be very useful in tracking down bugs, especially when they happen in a production environment. Such a system can also be useful in debugging during development but I find it much easier to use an IDE to debug my development environment (more on this shortly).

I feel that it is important to log any significant actions performed (e.g. user created, group deleted, registration email sent) as well as any errors that occur. Some people advocate logging a message every time you enter or leave a function. I find that this method to be a bit too verbose, especially with more complex applications. I prefer to ditch the reams of log messages for a good IDE and debugger.

When you actually come to log your messages I would recommend having a fallback mechanism (you never know, the logger itself could have just broken!) For example, you could try logging to a database, and failing that you can append to a log file, and failing that you can send an email. You may find exceptions useful for this.

See also:

Log Errors

We have to accept, that despite out best efforts, errors can (and do) occur in production environments. When these hiccups do arise we have to ensure that they are dealt with quickly, otherwise users (or even, gasp, clients) get angry.

Some types of errors we cannot do anything about (think parse errors) and we just have to ensure that we have a close eye on our error logs so we notice when they occur. Fortunately, it is these types of bugs that are normally caught very quickly during development and testing.

As for all the other errors, we need to make sure that they are caught and dealt with properly. Make sure the user is shown a nice error page (with a suitably cute ‘oops-back-soon’ picture) and then log, log
everything in sight! I recommend storing the following:

  • The stack current trace (see debug_backtrace() and debug_print_backtrace()).
  • The output of get_defined_vars(). However, this is only useful if you call it at the point the error occurs, not at the point the error is logged. This includes global variables.
  • Any and all information about the remote user (IP address, user agent, session data)
  • All global variable data (which includes the contents of $_COOKIES, $_SERVER etc.)
  • Any other status data which is specific to your application

This information will be invaluable when you come to tracking down errors in production code.

How you choose to actually capture your errors is your own choice. You can use trigger_error and a custom error handler, but I prefer to use a custom exception class which takes care of logging for me.

See also:

Check Function Parameters

Checking function parameters can help you catch a lot of bugs before the erroneous data passes too far through your application. I like to test that the input parameters are of the expected type and are reasonably sane.

To avoid too much clutter I generally only do this on utility methods (as these are used often and in many places) and methods which permanently manipulate data such as files and databases (as errors here have the potential to destroy a lot of data).

See also:

Use an Integrated Development Environment and Debugger

I created my first ever website on a Geocities account. To do this I used was a textarea field in the Geocities admin area. It was OK for the simple HTML I was writing, but I would not like to create an entire PHP application in this way!

I have now moved on to using an Integrated Development Environment (IDE) for the majority of my development, and I highly recommend that you do the same. I use Zend Studio and you only have to look at the feature list to see why I find it completely indispensible, especially for debugging. If you have not used an IDE before I recommend you have a look at one of the applications listed at the end of this section.

I also use a remote debugger (ZendDebugger), which ties into the IDE. The remote debugger is a PHP module that allows you to debug code on your server using the IDE on your local machine. You can set breakpoints, inspect variables, examine stack traces, profile code and all the other benefits you would expect from a debugger. And no, Zend does not sponsor me.

See also:

bugs2

Unit Testing

Unit testing may not be everyone’s idea of fun, but I can be very effective for developing larger projects. It can give you confidence when you have to make significant changes to the code base, as well as point out problems before your code goes into production.

There are two catches with unit tests, the biggest of which is that you have to actually write the Unit tests themselves. Although this should save time in the long run (or at least lead to a more robust product), it is hard to avoid thinking that you could be spending the time developing functionality.

The second catch is that it often forces you to refactor your code into more test-friendly chunks. This is probably a good thing but it will take more time. The best approach would be to write unit tests from the very start of the project or, for an existing project, you can write a unit test for every bug that is fixed.

If you are using unit tests you should also be aware of the concept of code coverage. This is a metric which shows what percentage of your code is run during the testing process. The higher value for this indicates a more robust set of unit tests. You can calculate your code coverage using a debugger, as was discussed in the previous section.

See also:

No Magic! (Or, Avoid Side Effects)

A side effect can be described as a non-obvious effect that was caused by performing an action (see Wikipedia for a more technical description). For example:

So what is going on here? You can see that we start with a $radius and $area variable which we use to show the area of the circle. We then want to display the area of a square with the same dimensions, so we call getSquareArea. Although this function does what its name implies, it also alters the $radius variable (intentionally or not). This is defiantly non-obvious in the rest of our code and can cause severe headaches when it comes to debugging, especially in more complex applications. Of course, you should also avoid global variables for similar reasons.

This also applies to modifying function parameters (which were passed by reference). If you find yourself doing this then you should probably refactor your code. To do this you can either return the parameter rather than modifying it, or you can split the function into several smaller functions. Also, don’t forget that objects are always passed by reference in PHP 5.

Use Manual Redirects When Debugging

Many developers (including myself) will make use of redirects when developing web applications. To refresh your memory, here is how you do a redirect in PHP:

This technique can be very useful for sending the user to the correct page, but it can also be very problematic for debugging. For example, do you keep getting sent off to a bizarre area of your application? Do you know if it is just one redirect sending you there or many? What do you do when you get trapped in an infinite redirect loop?

My answer to these problems was to introduce the concept of manual redirects which would only be used for debugging. Rather than sending a header to the client, I would send a link to the target page as well as a stack trace. This would allow me to monitor the redirects that were happening in my application and clearly see what was happening if the application went wrong.

The code I use looks something like this:

<?php
function redirect($url, $debug = false) {
	//If manual page redirects have been enabled then print some html

	if ($debug) {
		echo "<b>Redirect:</b> You are being redirected to: <br /><a href='$url'>$url</a><br />\n";
		echo "Backtrace: <br /><pre>\n";
		debug_print_backtrace ();
		echo "</pre>";
	} else {
		header ( "Location: $url" );
	}

	exit ( 0 );

}
?>

You may find it useful to pull the $debug value from your configuration system of choice rather than having to pass it for each function
call, but it works in this example.

Keep Things Simple

I think this rule probably exists for every profession out there, so it should be no surprise that it applies to software development.

It is good practice to write software using a clear structure and using standard design patterns, but this is only a high level approach to keeping things simple. We also need to keep your individual algorithms as simple as possible as this will make your code easier to understand in six months when it needs fixing, and will also make it easier to fix.

Here are some ways you can achieve this:

  1. Keep an eye on functions that are growing. You may find that you can split the code into several smaller functions.
  2. Functions that are only called in one place may be too specific. You can either bring the code inline, or generalise using several smaller functions. You can always keep the specific function and just use that to call and aggregate the new, smaller, functions.
  3. Watch out for functions with very long names or lots of arguments. This can be a sign that the function could be split into several smaller functions, or it could even be replaced with a class.
  4. Use built in functions where possible. This will help avoid spurious amounts of PHP code and there is a good chance the internal function will be faster as it is written in C (and by the pros!) Some of the most underappreciated internal functions are the array functions.
  5. If you really must have long and complex sections of code, then make sure you add some documentation. You and your fellow developers will be thankful of this when it comes to debugging.

Most of these points are about splitting large functions into smaller ones. It is also important to ensure you do not end up with lots of tiny functions, but I feel this is a much more unusual problem.

See also:

Conclusion

This blog post has, once again, become much longer than I expected! However, you have probably had a good overview of each of the techniques mentioned. If you have any other good ideas then I would love to hear them, so please leave a comment!

No comments yet

Leave a reply