Article
Effortless (or Better!) Bug Detection with PHP Assertions
Here are two more simple examples:
$one = 1;
$three = 3;
assert("$one + $one == $three");
assert('$one + $one == $three');
resulting in
Warning: Assertion "1 + 1 == 3" failed in test.php on line 45
Warning: Assertion "$one + $one == $three"
failed in test.php on line 46
Assertion strings work with single or with double quotes, though the warning messages differ. If you're interested in the names of the variables showing up in the error message, use single quotes; if you would rather see the values themselves, use double quotes (making sure to follow the PHP rules about referencing variables inside double quotes).
So far, the example assertions are not very interesting since they restate the obvious. They have more meaning when they check the actual state of a program.
Here's what an assertion to check the maximum age of a timestamp might look like:
define('MAX_CACHE_AGE', 60 * 60 * 24);
//...
assert('time() - $timestamp <= MAX_CACHE_AGE');
And the assertion to check that the socks' size isn't XL might be:
define('XL', 'Extra Large');
//...
assert('$socks->size() != XL');
You can check that there's a row in the customer table for an entry in the shopping cart by writing a function, say customer_exists, and calling it in an assertion, like so:
assert('customer_exists($cart->get_customer_id())');
Where to Use Assertions
There are many ways and places to use assertions, limited only by your imagination. In general, if you would write a comment about the state of program, you can turn it into an assertion.
Of course, production code can't depend on assertions. It must execute correctly even if all assertions were removed. This means that assertions should not do production-level error checking that requires cleanup or other action, like checking for errors after a database call or validating user input.
To illustrate some good places to use assertions, here are some specific ideas. Please don't assume these are the only possibilities, though!
- Switch with no default.
When a switch statement has no default case, often there is an implicit assumption that all the possibilities have been covered. This can be made explicit with an assertion:switch ($some_variable) {
case 'a':
//...
break;
case 'b':
//...
break;
//...
default:
assert('$some_variable != ' . "$some_variable");
break;
}The assertion will fail if the default case is reached, and the error message will describe the "impossible" value.
There is a similar opportunity when a series of if-else statements is intended to cover all the cases.
- Type checking.
PHP is a loosely and flexibly typed language, which serves it well most of the time. But sometimes it is important that a variable be a string, a number, an object, or of some other type.assert('is_resource($fp)'); - Parameter checking.
Assertions can be used to check the validity of parameters passed to a routine.
function some_function($positive_int_parameter) {
assert('isset($positive_int_parameter)');
assert('is_int($positive_int_parameter)');
assert('$positive_int_parameter > 0');
//...
}It's important not to use assertions to check parameters if you need to do it in the production system. For instance, if the value of
$positive_int_parametercould be whatever the user typed in, use normal methods to check that it is in range and do something like complain or correct the value if the check fails. If we did it with assertions, the complaint/correction would disappear when assertion checking disappeared, which would take functionality away from the production system.Checking function results. You can do sanity checks
$result = process_string($input_string);
assert('strlen($result) <= MAX_RESULT_LEN');or you can actually use an alternate method of computing the function result
$output = expensive_crunching_routine($input);
assert('$output == alt_expensive_crunching_routine($input)');Even if the alternate routine is computationally a little bit expensive, you might want to consider this kind of assertion, since it won't be a performance issue in the production code.
- Multistatement assertions.
Generally speaking, the expressions in assertions should not have side effects. One important exception is when multiple statements are required to make an assertion. For example:
assert(list($january, $ignore) = explode(',', $year_data));
assert('$january > MIN_FIRST_MONTH');The call to explode is wrapped in an assert so that it doesn't execute in the production system. It is guaranteed to succeed since explode only returns false when the separator is the empty string. Since it will always succeed, there is no need to pass the parameter as a string (there will be no error message). The important assertion is, of course, the second one.
PHP does impose limits on this technique. For example, it is not possible to define a function or a class inside an assertion in order to avoid defining them in production code. Still, the performance and convenience penalty for having "helper" functions and classes still in the production code is ordinarily small, so this is not an earthshaking issue.
With practice, you'll spot lots of opportunities for assertions.