only for RuBoard - do not distribute or recompile Previous Section Next Section

Programming Errors

Regardless of which language you are using, there are three general types of types of program errors:

We will look briefly at each before discussing some tactics for detecting, handling, avoiding, and solving errors.

Syntax Errors

Languages have a set of rules called the syntax of a language, which statements must follow in order to be valid. This applies to both natural languages, such as English, and programming languages, such as PHP. If a statement does not follow the rules of a language, it is said to have a syntax error. Syntax errors are often also called parser errors when discussing interpreted languages, such as PHP, or compiler errors when discussing compiled languages, such as C or Java.

If we break the English language's syntax rules, there is a pretty good chance that people will still know what we intended to say. This usually is not the case with programming languages.

If a script does not follow the rules of PHP's syntax—if it contains syntax errors—the PHP parser will not be able to process some or all of it. People are good at inferring information from partial or conflicting data. Computers are not.

Among many other rules, the syntax of PHP requires that statements end with semi colons, that strings be enclosed in quotes, and that parameters passed to functions be separated with commas and enclosed in parentheses. If we break these rules, our PHP script is unlikely to work, and likely to generate an error message the first time we try to execute it.

One of PHP's great strengths is the useful error messages that it provides when things go wrong. A PHP error message will usually tell you what went wrong, which file the error occurred in, and which line the error was found at.

An error message resembles the following:

						
Parse error: parse error in
/home/book/public_html/chapter23/error.php on line 2

					

This error was produced by the following script:

						
<?
   $date = date(m.d.y');
?>

					

You can see that we are attempting to pass a string to the date() function but have accidentally missed the opening quote that would mark the beginning of the string.

Simple syntax errors such as this one are usually the easiest to find. We can make a similar, but harder to find error by forgetting to terminate the string, as shown in this example:

						
<?
   $date = date('m.d.y);
?>

					

This script will generate the following error message:

						
Parse error: parse error in
/home/book/public_html/chapter23/error.php on line 4

					

Obviously, as our script only has three lines, our error is not really on line four. Errors in which you open something, but fail to close it will often show up like this. You can run into this problem with single and double quotes and also with the various forms of parentheses.

The following script will generate a similar syntax error:

						
<?
   if (true)
   {
     echo "error here";
?>

					

These errors can be hard to find if they result from a combination of multiple files. They can also be difficult to find if they occur in a large file. Seeing "parse error on line 1001" of a 1000 line file can be enough to spoil your day.

In general though, syntax errors are the easiest type of error to find. If you make a syntax error, PHP will give you a message telling you where to find your mistake.

Runtime Errors

Runtime errors can be harder to detect and fix. A script either contains a syntax error or it does not. If the script contains a syntax error, the parser will detect it. Runtime errors are not caused solely by the contents of your script. They can rely on interactions between your scripts and other events or conditions.

The following statement

						
include ("filename.php");

					

is a perfectly valid PHP statement. It contains no syntax errors.

This statement might, however, generate a runtime error. If you execute this statement and filename.php does not exist or the user who the script runs as is denied read permission, you will get an error resembling this one:

						
Fatal error: Failed opening required 'filename.php'
(include_path='.:/usr/local/lib/php') in
/home/book/public_html/chapter23/error.php on line 1

					

Although nothing was wrong with our code, because it relies on a file that might or might not exist at different times when the code is run, it can generate a runtime error.

The following three statements are all valid PHP. Unfortunately, in combination, they are attempting to do the impossible—divide by zero.

						
$i = 10;
$j = 0;
$k = $i/$k;

					

This code snippet will generate the following warning:

						
Warning: Division by zero in
/home/book/public_html/chapter23/div0.php on line 3

					

This will make it very easy to correct. Few people would try to write code that attempted to divide by zero on purpose, but neglecting to check user input often results in this type of error.

This is one of many different runtime errors that you might see while testing your code.

Common causes of runtime errors include the following:

We will briefly discuss each.

Calls to Functions That Do Not Exist

It is easy to accidentally call functions that do not exist. The built- in functions are often inconsistently named. Why does strip_tags() have an underscore, whereas stripslashes() does not?

It is also easy to call one of your own functions that does not exist in the current script, but might exist elsewhere. If your code contains a call to a nonexistent function, such as

							
nonexistent_function();

						

you will see an error message similar to this:

							
Fatal error: Call to undefined function: nonexistent_function()
in /home/book/public_html/chapter23/error.php on line 1

						

Similarly, if you call a function that exists, but call it with an incorrect number of parameters, you will receive a warning.

The function strstr() requires two strings: a haystack to search and a needle to find. If instead we call it like this:

							
strstr();

						

We will get the following warning:

							
Warning: Wrong parameter count for strstr() in
/home/book/public_html/chapter23/error.php on line 1

						

As PHP does not allow function overloading, this line will always be wrong, but we might not necessarily always see this warning.

That same statement within the following script is equally wrong:

							
<?
   if($var == 4)
   {
     strstr();
   }
?>

						

but except in the, possibly rare, case in which the variable $var has the value 4, the call to strstr() will not occur, and no warning will be issued.

Calling functions incorrectly is easy to do, but as the resulting error messages identify the exact line and function call that are causing the problem, they are equally easy to fix. They are only difficult to find if your testing process is poor and does not test all conditionally executed code. When you test, one of the goals is to execute every line of code exactly once. Another goal is to test all the boundary conditions and classes of input.

Reading or Writing Files

Although anything can go wrong at some point during your program's useful life, some things are more likely than others. Errors accessing files are likely enough to occur that you need to handle them gracefully. Hard drives fail or fill up, and human error results in directory permissions changing.

Functions such as fopen() that are likely to fail occasionally generally have a return value to signal that an error occurred. For fopen(), a return value of false indicates failure.

For functions that provide failure notification, you need to carefully check the return value of every call and act on failures.

Interaction with MySQL or Other Databases

Connecting to and using MySQL can generate many errors. The function mysql_connect() alone can generate at least the following errors:

As you would probably expect, mysql_connect() provides a return value of false when an error occurs. This means that you can easily trap and handle these types of common errors.

If you do not stop the regular execution of your script and handle these errors, your script will attempt to continue interacting with the database. Trying to run queries and get results without a valid MySQL connection will result in your visitors seeing an unprofessional-looking screen full of error messages.

Many other commonly used MySQL related PHP functions such as mysql_pconnect(), mysql_select_db(), and mysql_query() also return false to indicate that an error occurred.

If an error occurs, you can access the text of the error message using the function mysql_error(),or an error code using the function mysql_errno(). If the last MySQL function did not generate an error, mysql_error() returns an empty string and mysql_errno() returns 0.

For example, assuming that we have connected to the server and selected a database for use, the following code snippet

							
$result = mysql_query( 'select * from does_not_exist' );
echo mysql_errno();
echo '<BR>';
echo mysql_error();

						

might output

							
1146
Table 'dbname.does_not_exist'doesn't exist

						

Note that the output of these functions refers to the last MySQL function executed (other than mysql_error() or mysql_errno()). If you want to know the result of a command, make sure to check it before running others.

Like file interaction failures, database interaction failures will occur. Even after completing development and testing of a service, you will occasionally find that the MySQL daemon (mysqld) has crashed or run out of available connections. If your database runs on another physical machine, you are relying on another set of hardware and software components that could fail—another network connection, network card, routers, and so on between your Web server and the database machine.

You need to remember to check if your database requests succeed before attempting to use the result. There is no point in attempting to run a query after failing to connect to the database and no point in trying to extract and process the results after running a query that failed.

It is important to note at this point that there is a difference between a query failing and a query that merely fails to return any data or affect any rows.

An SQL query that contains SQL syntax errors or refers to databases, tables, or columns that do exist might fail. The following query, for example

							
select * from does_not_exist;

						

will fail because the table name does not exist, and it generates an error number and message retrievable with mysql_errno() and mysql_error().

A SQL query that is syntactically valid, and refers only to databases, tables, and columns that exist will not generally fail. The query might however return no results if it is querying an empty table or searching for data that does not exist. Assuming that you have connected to a database successfully, and have a table called exists and a column called column name, the following query, for example

							
select * from exists where column_name = 'not in database';

						

will succeed but not return any results.

Before you use the result of the query, you will need to check for both failure and no results.

Connections to Network Services

Although devices and other programs on your system will occasionally fail, they should fail rarely unless they are of poor quality. When using a network to connect to other machines and the software on those machines, you will need to accept that some part of the system will fail often. To connect from one machine to another, you are relying on numerous devices and services that are not under your control.

At the risk of being repetitive, you really need to carefully check the return value of functions that attempt to interact with a network service.

A function call such as

							
$sp = fsockopen ( "localhost", 5000 );

						

will not provide an error message if it fails in its attempt to connect to port 5000 on the machine localhost.

Rewriting the call as

							
$sp = fsockopen ( "localhost", 5000, &$errorno, &$errorstr );
if(!$sp)
  echo "ERROR: $errorno: $errorstr";

						

will check the return value to see if an error occurred, and display an error message that might help you solve the problem. In this case, it would produce the output:

							
ERROR: 111: Connection refused

						

Runtime errors are harder to eliminate than syntax errors because the parser cannot signal the error the first time the code is executed. Because runtime errors occur in response to a combination of events, they can be hard to detect and solve. The parser cannot automatically tell you that a particular line will generate an error. Your testing needs to provide one of the situations that create the error.

Handling runtime errors requires a certain amount of forethought; to check for different types of failure that might occur, and then take appropriate action. It also takes careful testing to simulate each class of runtime error that might occur.

This does not mean that you need to attempt to simulate every different error that might occur. MySQL for example can provide one of around 200 different error numbers and messages. You do need to simulate an error in each function call that is likely to result in an error, and an error of each type that is handled by a different block of code.

Failure To Check Input Data

Often we make assumptions about the input data that will be entered by users. If this data does not fit our expectations, it might cause an error, either a runtime error or a logic error (detailed in the following section).

A classic example of a runtime error occurs when we are dealing with user input data and we forget to AddSlashes() to it. This means if we have a user with a name such as O'Grady that contains an apostrophe, we will get an error from the database function.

We will talk more about errors because of assumptions about input data in the next section.

Logic Errors

Logic errors can be the hardest type of error to find and eliminate. This type of error is where perfectly valid code does exactly what it is instructed to do, but that was not what the writer intended.

Logic errors can be caused by a simple typing error, such as:

						
for ( $i = 0; $i < 10; $i++ );
{
  echo "doing something<BR>";
}

					

This snippet of code is perfectly valid. It follows valid PHP syntax. It does not rely on any external services, so it is unlikely to fail at runtime. Unless you looked at it very carefully, it probably will not do what you think it will or what the programmer intended it to do.

At a glance, it looks as if it will iterate through the for loop ten times, echoing "doing something" each time.

The addition of an extraneous semicolon at the end of the first line means that the loop has no effect on the following lines. The for loop will iterate ten times with no result, and then the echo statement will be executed once.

Because this code is a perfectly valid, but inefficient, way to write code to achieve this result, the parser will not complain. Computers are very good at some things, but they do not have any common sense or intelligence. A computer will do exactly as it is told. You need to make sure that what you tell it is exactly what you want.

Logic errors are not caused by any sort of failure of the code, but merely a failure of the programmer to write code that instructs the computer to do exactly what he wanted. As a result, errors cannot be detected automatically. You will not be told that an error has occurred, and you will not be given a line number to look for the problem at. Logic errors will be detected only by proper testing.

A logic error such as the previous trivial example is fairly easy to make, but also easy to correct as the first time your code runs you will see output other than what you expected. Most logic errors are a little more insidious.

Troublesome logic errors usually result from developers'assumptions being wrong. Chapter 22, "Using PHP and MySQL for Large Projects," recommended using other developers to review code to suggest additional test cases, and using people from the target audience rather than developers for testing. Assuming that people will enter only certain types of data is very easy to do, and very easy to leave undetected if you do your own testing.

Let's say that you have an Order Quantity text box on a commerce site. Have you assumed that people will only enter positive numbers? If a visitor enters negative ten, will your software refund his credit card with ten times the price of the item?

Suppose that you have a box to enter a dollar amount. Do you allow people to enter the amount with or without a dollar sign? Do you allow people to enter numbers with thousands separated by commas? Some of these things can be checked at client-side (using, for example, JavaScript) to take a little load off your server.

If you are passing information to another page, has it occurred to you that there might be characters that have special significance in an URL such as spaces in the string you are passing?

An infinite number of logic errors is possible. There is no automated way to check for them. The only solution is first, to try to eliminate assumptions that you have implicitly coded into the script and second, test thoroughly with every type of valid and invalid input possible, ensuring that you get the anticipated result for all.

only for RuBoard - do not distribute or recompile Previous Section Next Section