Articles

The month of PHP functions : assertions

  • Ecrit par Damien Seguy
  • lundi 30 avril 2007
Image pour le titre du contenu

Ce document est aussi disponible en français fr 


We already have covered this month some natives functions that helps for debugging PHP scripts. var_dump(), print_r() and echo() are in the top 3 of such tools. Though, they have a major drawback : one tend to forget them during the deoployement.

Who never cursef himself while staring at 'Hello', unexpectedly appearing in the web site, or at this print mysqli_error($mid); that was forgotten? To check oneself, the deployment tools include a grep test to make sur that no var_dump() nor print_r() makes it way to production. Though, print() may be a much trickier guess : by convention, some use print() as deboguing, and echo() for display. Or vice-versa.


It would be really nice to be able to remove all this ugly code when in production, just by utter magic. For PHP, this is actually a little directive that will do that for use : welcome to the world of assertions.

Assertions are a kind of tests that can litter the code. They are composed of a function and a condition. In its simplest form, they look like this code :

<?php
 
 
$mid = mysqli_connect('localhost','user','bad_pass','base');
 
 
assert('is_object($mid)');
 
 
if (!is_object($mid)) {
    die("failed connexion\n");
}
 
 
?>

The result of this script : is

Warning: mysqli_connect(): (28000/1045): Access denied for user 'user'@'localhost'
 (using password: YES) in script.php on line 4
 
 
Warning: assert(): Assertion "is_object($mid)" failed in script.php on line 6
failed connexion


The assert() function has executed the PHP code that was given as first argument. When this code returns TRUE, assert() will stay silent and the script will go on. If this code return FALSE, then the function will produce a warning. In the above code, this is the second warning.

Note that the assert() function is doing exactly the same functionnality than the if condition just below. The condition is simply inversed : assert() checks with is_object() and if checks with !is_object().

Now, all logic test in the code will produce warning, just like any invalid PHP code, instead of producing a message that will be difficult to distinguish with a valid content display. Actually, you may also suppress all those warning by using the error_reporting() function, and log those messages in the system logs.

The best of you will note that the above if() will stop the script, while the assert() will only produce a warning. You may configure the level of the raised message with assert_options().

<?php
 
 
assert_options(ASSERT_BAIL, 1);
$mid = mysqli_connect('localhost','user','bad_pass','base');
 
 
assert('is_object($mid)');
 
 
if (!is_object($mid)) {
    die("failed connexion\n");
}
 
 
?>

With this new script, the result is now :

Warning: mysqli_connect(): (28000/1045): Access denied for user 'user'@'localhost'
 (using password: YES) in script.php on line 4
 
 
Warning: assert(): Assertion "is_object($mid)" failed in script.php on line 6

Note that the if() condition is not executed anymore, because now, assert() stops PHP upon error. When you are developping, this will stop your script at the first error. This will be a good way to reinforce your script : just add a lot of assertions any place in your script, to ensure that all conditions are always met, just like we can do with if.

Until now, we have seen that assert() has about the same functionnalities than a if(). Yet, there are two other functionnalities that will surpass it, and prove that assert() is much better. The first is that assert() accepts a customized handler.

<?php
 
 
function deboguage($file, $line, $condition) {
    $condition = htmlentities($condition);
    echo "\n\nAn assertion failed : 
        in the file \"$file\",
        at line \"$line\",
        the condition \"$condition\"\n\n";
        
        $code = file($fichier);
        $code = array_slice($code, max(0, $ligne - 5), 10);
        print "---- Code ---\n";
        print(join('', $code));
        print "---- Code ---\n";
}
 
 
assert_options(ASSERT_CALLBACK, 'deboguage');
$mid = mysqli_connect('localhost','user','bad_pass','base');
 
 
assert('is_object($mid)');
 
 
if (!is_object($mid)) {
    die("failed connexion\n");
}
 
 
 
?>

This script produces the following result :


Warning: mysqli_connect(): (28000/1045): Access denied for user 'user'@'localhost'
 (using password: YES) in script.php on line 19
 
 
 
An assertion failed : 
        in the file "script.php",
        at line "21",
        the condition "is_object($mid)"
 
 
 
Notice: Undefined variable: ligne in script.php on line 12
---- Code ---
<?php
 
 
function deboguage($file, $line, $condition) {
    $condition = htmlentities($condition);
    echo "\n\nAn assertion failed : 
        in the file \"$file\",
        at line \"$line\",
        the condition \"$condition\"\n\n";
        
---- Code ---
 
 
Warning: assert(): Assertion "is_object($mid)" failed in script.php on line 21
failed connexion

Note that we could have stopped the script just like before with another option ASSERT_BAIL. Here, not only do we get all information bout the failed condition, but also a snippet of the faulty code : this is our first step to stack the bug.

Last, but not least, the major advantage of assertions is that their action is controled by a PHP directive. You may use any of :

assert_options(ASSERT_ACTIVE, 0);
ini_set('assert.active','Off');

in your code, or event the directive assert.active within php.ini to disable totally all assertions. There is no need to hunt for any occurrence in the code : PHP will simply ignore them during execution. This has a small cost in termes of performances, but it will bring you peace of mind : no chance of any unexpected debugging display will be displayed.

And, if performances costs are too dear for you, you may also take another advantage of assertions : no one can mistake them for a print() or a var_dump() fonction, not even grep.

Keep in mind
  • assertions are available since PHP 4
  • if the callback function does not exists, assert() will keep its normal behavior.
  • Assertions are activated by default.
  • Assertions execute PHP code, passed as a string. Keep those tests as static as possible, so as to avoid PHP code injections through concatenated variables.

< Précédent   Suivant >

Commentaires

Vous pouvez ajouter votre commentaire!


Vous devez vous connecter pour commenter