Debugging: A Beginner's guide
Everyday the forums see probably hundreds of topics posted where the problem is a fairly simple error. These generally fall in 4 categories:
- Syntactical/parse errors
- Fatal errors, warnings and notices
- Database errors
- Logical errors
As a beginner, it can be difficult to find and solve these errors. By tackling each of these in turn, I hope to teach you some methods of finding and solving them. So we'll get started with syntactical errors first.
Syntax errors. Eugh. We've all been there -- a nasty error message and we don't know what it means.
However, sometimes people don't even get as far as seeing the error message: they have PHP setup so that it won't display them. Handy for a live site; but rubbish for something in development. So turn them on! Either do this through your php.ini file (you're looking for the display_errors setting) or through the ini_set function. You can add this to the top of your code:
<?php ini_set('display_errors','On'); ?>
Note: Unfortunately, if your error is syntactical, then you'll need to make this change to your php.ini. If PHP can't parse the page correctly, then the change to the setting won't be made. (with thanks to philipolson for mentioning this)
This is often the cause of the 'My page shows nothing!' problem, which is seen so often on the forum. That is, there's a syntax error, but it's not being displayed.
Right, now that we're not flying blind and we're actually going to see our errors, we can move onto fixing them.
Syntax Errors -- Debugging
Computers are stupid. You can quite easily understand something with a missing piece of punctuation, but computers? They throw their teddies out the pram. You might, for instance, see this:
Parse error: syntax error, unexpected T_VARIABLE in C:\wamp\www\phpfreaks.php on line 3
Now, when something's unexpected, it means just that. However, it does not always mean that it's not required. For example, the above error message was generated with this code:
<?php $foo ='foo' $bar = 'bar'; ?>
The unexpected element of the above is $bar. But we wanted to define $bar, that's not unexpected. What is unexpected is the lack of semi-colon on the previous line. This leads us to one important aspect of finding and fixing errors: check the previous lines.
Other common causes of syntax errors are:
- Unclosed quotes
- Missing or Extra parentheses (for example, with if statements)
- Unclosed braces -- more on this later
One thing that will help you avoid making syntax errors is the use of a good editor with syntax highlighting. This will make things such as unclosed quotes very obvious. I'd personally recommend PHP Designer or Eclipse, but other people will suggest different editors. There's a long thread here [http://www.phpfreaks.com/forums/index.php/topic,54859.0.html] dedicated to the debate.
That's a wrap on syntax errors so we'll move onto fatal errors, warnings and notices.
Fatal Errors, Warnings and Notices
Now would be a good time to mention another common mistake: the use of error suppression during development. PHP allows you to quietly ignore errors (other than syntactical) by placing the @ symbol at the start of the line. As with the display of errors, this is handy for a production server –- but when you're trying to find out why something doesn't work, it's worse than useless.
Fatal errors are caused when PHP understands what you've written, however what you're asking it to do can't be done. Common examples include trying to use a function or class that hasn't been defined. Fortunately, the error message generated should be nice and obvious:
Fatal error: Call to undefined function foobar() in C:\wamp\www\phpfreaks.php on line 2
Warnings refer to things which PHP can cope with, but it's likely you didn't intend them. Common causes include trying to include a missing file or using the incorrect number of parameters in a function. Again, the error message should help you out.
Probably the most common warning sent is the one that looks something like this:
Warning: Cannot modify header information - headers already sent by (output started at C:\wamp\www\phpfreaks.php:2) in C:\wamp\www\phpfreaks.php on line 3
Or like this:
Warning: session_start() [function.session-start]: Cannot send session cache limiter - headers already sent (output started at C:\wamp\www\phpfreaks.php:2) in C:\wamp\www\phpfreaks.php on line 3
Both of these are caused by the same thing: you cannot change headers -- which includes changing location or setting a cookie/session -- after you have sent any content to the browser (ok smarty-pants so you can with output buffering, but that's a different subject for a different day).
Note: Output includes whitespace. So you can't even send a space or blankline before you send a header. This catches people out sometimes, particularly when including files. You might have some whitespace after your closing PHP tag which is included in another file.
Finally, we have notices. So often ignored, but can actually help you track an awful lot of errors. A notice is generally given for something which is undefined: a variable, constant or element of an array.
Before you see them, however, you'll need to make sure that PHP is setup to report them. This requires a further change of your php.ini. I recommend changing the error_reporting setting to E_ALL. This can also be done at the top of your script with:
<?php error_reporting(E_ALL); ?>
For more on error reporting and the relevant options, see the manual [http://www.php.net/error_reporting].
If you've just turned notices on, you may have just got a truckload of error messages. PHP can cope with notices, hence why your script was working before. But as mentioned, notices are incredibly useful for tracking typos by warning you of an undeclared variable. Common notices come from unquoted strings being used as the key of an array, such as:
<?php $array = array('key'=>'value'); echo $array[key]; ?>
This is because, without quotes, key is a constant and not a string. Whenever PHP encounters undefined constants, it assumes that it's a string, so it can cope. However, best practice is to place quotes around the key if it is a string.
Note: It's perfectly acceptable to use an unquoted string as your key inside double quotes, such as:
<?php $array = array('key'=>'value'); echo "The array contains just one value: $array[key]"; ?>
That about wraps it up for our first two types of error, so we'll move onto database errors.
How often have you seen this?
Warning: mysql_fetch_assoc(): supplied argument is not a valid MySQL result resource in C:\wamp\www\phpfreaks.php on line 5
The error message doesn't tell you a lot, does it? What it does tell you is that there was an error with your query. What we need is way of seeing that error. Handily, mysql_error() does just that. Now, you will see a lot of people on the forum (myself included) suggesting that to test a query you should add an or die statement and echo mysql_error() like so:
$result = mysql_query("SELECT * FROM tbl WHERE ...") or die(mysql_error());
However, better practice would be to use "or trigger_error()". Why? Well, because it will save you troubles should the script go live. By throwing an error, you still get the benefit of seeing the problem whilst you are developing and have display_errors turned on, but can also handle things gracefully should the script go live and you turn off error_reporting. You'll also want to echo your query. It's so much easier to spot a syntax error when you actually look at what is being executed. This is particularly true if the query is complex and contains many variables.
Therefore, you should do this:
<?php $sql = "SELECT * FROM tbl WHERE ..."; $result = mysql_query($sql) or trigger_error(mysql_error().'<br />Query was:'.$sql,E_USER_ERROR); ?>
Notice that we create our query in a string before we execute it; this allows us to echo it.
Now, you might also have a query which does not generate an error, but also doesn't return what you expect. You may, for example, be expecting lots of rows but don't get any. Again, the best thing you can do is to echo the query:
<?php $sql = "SELECT * FROM tbl WHERE ..."; $result = mysql_query($sql) or trigger_error(mysql_error().'<br />Query was:'.$sql,E_USER_ERROR); echo '<br />'.Query was:'.$sql.'<br />'; ?>
9 times out of 10 you will find that a variable in your query doesn't contain what you expected it to and so can trace the error.
Finally, we'll talk a little bit about logical errors on the next page.
So your script doesn't report any errors and the queries are working fine. But it doesn't do what it's supposed to. Chances are, you have an error in your logic.
Unfortunately, logical errors are the hardest to find. You may have been expecting an if statement to be true, when indeed it is false. The best solution to any problem like this is: echo, echo, echo. You must echo out information at strategic places throughout your script to help you narrow down the problem. You might, for example, check that a function has been called by placing an echo inside it. You might echo a variable to check its value; or you might echo something after an if statement to see if it was true or false. Never assume anything. You might have missed something and your best bet is to be patient and track it down.
Logical errors are also often caused by poor code layout. Always indent code between braces. Again, a good editor will help you out, as it will do this for you. It's much easier to keep track of braces and spot missing ones if you properly indent your code. It should also allow you to highlight one brace and find it's partner. The worst error message you can see is this:
Parse error: syntax error, unexpected $end in C:\wamp\www\phpfreaks.php on line 3
This usually means there's an unclosed brace. I've seen plenty of people assume they can just tack a brace onto the end of the script. It's almost never that simple. If I see someone with this problem, they're on their own! Your only option is to carefully walk though your code and make sure every brace is opened and closed in the correct place.
This error would also be generated if you had an unclosed backtick (`). Thanks to 448191 for pointing out that a lot of editors don't pick this up in their highlighting, so it could be tricky to spot.
That's it for logical errors and the whole tutorial. Just sit tight through the summary and you'll be off.
In no particular order, here are my tips for finding your errors:
- Make sure PHP is displaying errors
- Set your error reporting to E_ALL
- Remove error suppression
- Use a good editor
- Indent your code
- Use 'or trigger_error' statements with every query
- Echo out if you're having problems
- Check the previous lines for syntax errors
I should also say that my intention with this tutorial was to provide some general help for a beginner during the development stage. You should note that there are other, more graceful methods of handling errors (an error handling class with exceptions being the best route) in the long run. Even if you don't go the whole hog you should consider the following before your site goes live:
- Turning off display errors. Yeah, I know I told you to turn it on earlier, but that was to help you find the error to start with. Once your site is live, you don't want users seeing nasty error messages.
- Ensuring log_errors is turned on. This allows you to find out if any errors do occur after your site is live.
- Think about your error_reporting level. In PHP 6, E_STRICT errors will become apart of E_ALL. For more on this, see the manual [http://www.php.net/error_reporting].