PHP is fairly easy to debug. for starters, there are PHP's own error messages (turn display_errors on! even on a production server! if there is an error, you should either write a handler for them, and if it's an unexpected error, the sooner you get wind of it is the better). also, there is an error log for PHP, if you have access to it. furthermore, if you're working in an IDE like Eclipse-PHP/PD, or NetBeans, you probably will have access to Zend's xdebugger, but that's not that easy to set up and use...
but what happens when there are no error messages, and all you get is a blank screen? or what happens where there is no error message, but something runs broken in your app?
fear not, the solutions are quite easy.
first, there are three major ways how you can show usable debug data:
the screen (the browser):
if you're positive that your app gets to the state that it at least displays something, you can use the HTML output as a debug screen. you can go about it in the following ways:
- display debug information as-is, with an echo -- a bad idea, only suitable for putting watch points in your script (see below).
- put your debug data in a <pre> tag, using htmlspecialchars() for encoding > and < and these sorts of characters: better, as you don't have to care about putting <br/> elements in your output to make sense.
- put your debug data in html remarks (begin with <!-- and end with -->, and take care not to have the value --> as a vanilla output lest you prematurely end the remark): almost the best, as it allows you to debug in-production sites as well without messing up the output for regular users.
- there is one caveat with the remark output: chrome. since HTML remarks are only visible in source display mode, and given chrome's frustrating solution to give you source view only with reloading the page, if you choose the remarks method, make sure you use firefox, opera or... gah... even IE.
- use console.log() javascript calls in your HTML output to log to your browser's javascript console. not available with vanilla IE.
using the screen is the best method if you use virtual watch points -- simple echo statements that output where you are in your script. an example:
// ... where you want to watch something
echo "before starting cycle";
for($i=0;$i<1;--$i) {
echo "executing iteration {$i}...";
//... your stuff ...
echo "iteration {$i} run...";
}
echo "after cycle";
it's the simplest way of keeping track where you are in your script. (and, by using this, you just detected an infinite loop... whoops! :) ) if you have no error messages (meaning that syntactically and programmatically your code is correct), yet you don't get the expected results, you can pinpoint the location of your problem. and as you can see in this example, you can even insert runtime values in your watch messages so you can see current stati of your system.
the error log
if you have access to your web server's error log, you can log debug messages with
error_log($message); to your primary error log. you can even output watch point messages like that! the upside is that you will see both your messages and errors by the PHP interpreter in one continous stream. the downsides are: a) you may not be able to read your error log, depending on your server/hosting setup; b) a LOT of errors might show up there, making your job quite difficult to discern what you really need; c) if you want to output large amounts of debug data, error logging is not the best solution for that.
debug files
ever since writing files is so simplified with
file_put_contents(), this can be the easiest way of creating debug data.
writing debug data has its own advantages and disadvantages:
- con: native PHP errors don't show up here, so unless you do some error catching by yourself, you'll have to match debug data with displayed/error-logged errors.
- pro: your debug file will only contain data you specifically want to debug.
- pro: you can put in as much data as you want to.
- con: you have to take care of data stamping yourself.
- con: if you don't have write permissions where you want to put your debug log files, that is in itself another bug you'll have to catch. before using this method, it's good practice to test if you can write a file where you want to put your debug files. if not, change the permissions or ask your sysadmin what to do about it.
- pro: you're not tied to single-line logging like in error_log, and you don't have to prepare your data for HTML-compatible output like with the display methods.
if your script is a backend for an AJAX request, especially if it's one that is supposed to yield JSON or XML output (that is, not native HTML/JavaScript), using the error log and debug files might be your
only choice.
what should you output for debugging, and how?
this depends on your needs and the application.
if the application is at a production server, ie. many people use it, make sure you don't disclose sensitive information readily readable on the screen, and you must use the HTML comment/remark technique to hide any such things from regular visitors.
you can safely output your virtual watch point messages as long as they don't contain sensitive information and don't confuse the user. however, if you want to display data sets and structures, simple echos won't do the trick for you.
as for how to create debug output:
consider the following code.
define('MY_DEBUG_ACTIVE',1);
define('MY_DEBUG_FILE',dirname(__FILE__).'/debug.log');
function debugmsg($message,$level=1) {
if ($level<=MY_DEBUG_ACTIVE) {
$msg = date('Y-m-d H:i:s').': '.$message."\n";
file_put_contents(MY_DEBUG_FILE,$msg,FILE_APPEND);
}
}
//... your code... watchpoint comes...
debugmsg("before starting up db");
mysql_connect();
debugmsg("sql started up");
//... etc.
if you use something like this, and put in a lot of
debugmsg() statements in your code, you can thoroughly check your code's run. and, if it is no longer necessary to debug, you just change the
MY_DEBUG_ACTIVE constant to 0. of course, this is not the end of all things: you can create a system which distinguishes between debug levels (usually three levels should suffice: 0-nothing, 1-general debug, 2-detailed debug), and call debugmsg with either 1 or 2 as the debug level. it is prudent to leave this debugging code in production-level scripts as well, because you may never know when you need to use it again, and the performance overhead is minimal when debugging is turned off.
now, what to put in the debug output?
first, watch point information is good, because you'll get a sense where you are in your script, so if debug output suddenly ceases, you'll know where the script breaks in a major way.
second, when you deal with incoming data either from user input or otherwise, you should debug some or all such data as well. when you want to debug an array or object structure in its entirety, you can create a humanly readable form with
var_export($data_to_debug,TRUE); do not use
print_r or
var_dump (as it displays output right into the output buffer, so unless you do some buffering of your own, you'll likely break the output of your script and your debug log stays empty). besides this, you can always put the variables of your choice in the $message string, but take care to identify which variable's value you are putting there.
if you want to catch exceptions, you have to use PHP's try..catch construct, so error messages don't end up on the display or in the error log, but rather you'll have your own chance to jot them down in your debug output. please see
PHP's documentation about this. it's also a safer way to handle errors as such errors don't just go and ruin your output, but give you the opportunity to handle them gracefully. mind that PHP's exception model gives you the possibility to backtrace the error, yielding the complete call stack (basically the route your application took until it ran into the exception), which is immensely useful tracing bugs.
lastly, if you want to have your output included in the debug log, you should read about
PHP's output buffering solutions, because with that you can catch the normal output of your application and have it logged, and then send it to the client afterward. (however, in timed operations, like an AJAX backend that provides timely output, that solution does not work.)
a few more tips regarding PHP debugging:
the worst is when your script dies without output, and not even debug output is done. in that case, use the comment-out technique:
- put in a simple echo "hello world"; line as the first actual line of PHP code in your script, and use the /* ... */ comment type to comment out the rest of your script. try to run it, if not even "hello world" appears in your browser, you have a problem with your PHP setup, so go and correct that (or have it corrected by the sysadmin).
- function definition by function definition, class by class, go one by one and move the beginning /* comment mark lower and lower in your code, and try running your script in each step. if you don't have functions or classes, move 10 lines down each time. don't mind if you run into (even fatal) errors, that's just a sign of PHP's interpreter working properly.
- when after one of the steps the script once again dies without any output, move back to the previous point where your script still worked to some extend, and then go line by line.
- this way, you can isolate the offensive line that killed even PHP's interpreter! :)
another tip: use an IDE (Eclipse-PHP/PDP, NetBeans), because they can help catch syntactical errors right in the editing phase, saving you a lot of hassle afterwards -- but never forget, they're good, but they're not omnipotent! sometimes they fail to catch some errors, and you'll have to use common sense and the debugging tools above to identify your problems.
typical errors and tips:
- when using the heredoc syntax ($something = <<<EOT .... EOT;), use it with care. here's a good guide how. you must remember that when you put the EOT; part in your script, there should be no whitespace or anything else after the ; and before the new line marker; otherwise, be prepared for very misleading error messages.
- when using the == operator to compare things, take care not to write a single = sign, because that's assignment, not a comparison! some sources say the best practice is to use a reverse notation (like 'some string'==$variable) because if you omit the two = signs it will fail with an error, but personally, i find that very confusing. :)
- make sure you know what assigning by value and by reference are, and you know the distinction between the two; and always remember, object are always assigned by reference.
- be careful how you interpret a result from a function/method call; if the method can return a boolean or NULL value for an error/mismatch, and 0 as a valid result, always use the strict comparison operators (=== and !==) to specifically check for NULL and boolean values.
- using undefined indexes and variables throws only an E_NOTICE, but you should watch out for those as well (told you, E_ALL is what you need to write really clean code!). don't just assume they're defined -- check with isset() if they are, before doing anything with them (even comparisons!).
- never trust external input! never! always assume there's a hacker or a script kiddie somewhere out there who'd try and break your application. always check incoming variables for validity, and never, EVER put such values unescaped in: SQL queries, system/exec commands, or eval() statements! NEVER! use mysql_real_escape_string(), preg_quote(), addslashes(), escapeshellarg() or escapeshellcmd() to make sure nothing harmful enters your system.
- try to avoid eval() as much as possible.
- if you gotten comfortable with ereg, eregi, ereg_replace, eregi_replace... well, tough luck. i have (since i've been programming in PHP for 10+ years), but since its UTF8 support is clunky, and the whole library is now obsolete, create your new code with PCRE counterparts, and if you have the time, go back and fix your old code to use that, too. PCRE is faster, more compliant, and what's most important, it won't throw you a zillion E_DEPRECATED messages.
- if you are developing a lot of classes, you have the UnitTest framework to test your classes and its methods. i'll cover that later (much later...).