PHP assertions and their usagePHP assertions and their usage

PHP has a clever native debugging tool : the PHP assertions. In a nutshell, assertions are a functioncall to assert(), that triggers an error when a condition is not satisfied.

<?php

$a = 1;
assert($a === 1, '$a is not 1');
assert($a === 2, '$a is not 1');

?>

Unlike debugging conditions, the assert() syntax has to be read in a positive way. When the condition, aka as the first argument, is satisfied, all is fine, and process goes on; when the condition is not satisfied, then an error is triggered: the message of the error is the second argument.

If you have a long life of PHP programming, with code littered with this :

<?php

$a = 1;
if ($a !== 1) {
    die('$a is not 1');
}

?>

Then, you should consider migrating to assertions, for several reasons.

Assertions are positive

The condition in the assertion has to be positive, not negative. So, the test must be read as ‘This is what I want’ and not ‘this is what I don’t want’. You can easliy understand while reading the two examples above.

It also takes a very different mindset to write. In fact, one of the main pitfall of migrating to assertions from hand-made assertions, is to toggle the condition.

Assertions can be configured

A critical problem with hard-coded conditions is their tendency to stay in teh code until production. It is not good to check values in production, let alone to kill the whole script if anything happens. So, that code must be removed, just like any debugging code.

This is where assertions shine : by simply setting ASSERT_ACTIVE set to false, assertions are ignored during execution.

<?php

assert_options(ASSERT_ACTIVE, 0);
$a = 1;
// This is now totally hidden
assert($a === 2, '$a is not 1');

?>

zero-cost assertions

Actually, they are also called ‘zero-cost’ assertions. This means that when assertions are inactive, even the arguments are not executed. For example, the script below calls foo() to make the error message better, and log it at the same time.

<?php

$a = 1;
// This is now totally hidden
assert($a === 2, foo('a').' is not 1');

function foo($name) {
    print __FUNCTION__.PHP_EOL;
    
    // Log info
    Log::LogWarning($name.' failed');
    
    // beautifying the message...
    return 'The variable named '.$name;
}

?>

In a normal function call, foo() has to be called first, and it has to be called everytime : PHP has to build the value for the error message, then pass it to assert(). Thus, the debugging information, collected for the error message, is always costly, will slow the application.

When disabling assertions, both arguments are skipped, leading to no detour. What was tested is now completely ignored, and the code is not slowed at all.

This is a significant advantage : all assertions may be removed from all the code by a simple directive switch. No code review, no grep, just disabled and never seen again.

A la carte : die or warn

It is even better when you consider that assertions may yield a fatal error or a simple warning. Instead of an hard-coded exit() in the code, you may simply configure the assertions.

  • ASSERT_WARNING : PHP emits a warning, but keeps on executing
  • ASSERT_BAIL : PHP ends the execution of the script when an assertion fails.

This is very useful : some conditions may be critical, and should stop the whole code. Others are simple unexepected situations, that should be reported for later review.

What to test with assertions

Assertions are great to check parts of the code. In one line, you can make sure that an expression fulfill some criteria. So, validating incoming data or variables type is very common.

<?php

function foo($a, $b) {
    assert(is_int($a), '$a must be an integer';
    assert(is_int($b), '$b must be an integer';
    assert($b === 0, '$b can\'t be 0';
    
    return $a / $b;
}
?>

This is very efficient in the early stages of the code, where anything can actually be send to foo(). As soon as an error becomes recuring, assertions may be added in the code to warn about it. Usually, the code is then close to the point of emission. Also, when an error is dectected, the whole code stops immediately, not messing with the rest of the application.

In a word, assertions are executable comments.

Assertions are still debug

Looking at the example, assertions actually look like a weak solution to make the code robust. First, PHP has scalar type-hint, and adding ‘int’ in the function signature will provide better tests.

Moreover, when $b is 0, there should be a much stronger feedback than a simple error message : what about throwing a DivisionByZeroError exception ? Indeed, that is a runtime problem, more than a development problem.

In the end, this simple example should really be written :

<?php

function foo(int $a, int $b) {
    if ($b === 0)  
        throw new DivisionByZeroError('$b can\'t be 0');
    }
    
    return $a / $b;
}

?>

Avoid assertions on non-controlled resources

One situation that may typically look like a good spot for assertions are database connexions. Everyone has read code like (ext/mysql is intended) :

<?php

$mysql = mysql_connect('localhost', 'web', 'password') or die("No database connexion"); 

assert(is_resource($mysql), "No database connexion");

?>

The assertion is not a good idea here : the database connexion may or may not be available, and there is no way to put this under control. So, the assertion is not going to help here.

What to do after assertions ?

So, it seems that assertions a nice temporary tool. Yet, we are already reaching their main limitation : it is nice that they can be removed with a simple call to a PHP directive, but that doesn’t cope in any way with the situation. In production, without assertions, the code would keep being executed. Data would get corrupted because of the fallthrough to the wrong expressions. The bug is not magically removed.

In case the bug may be met in production, I recommend replacing assertions by an exception (or a trigger_error()). That way, logging and monitoring can pick up the problem and report it to coders. That’s important.

In case you just want a canary, that warns you about a potential problem at a certain point in the code, without blocking it, then assertions are great.

Assertions for debug during in development

On the other hand, assertions are great to spot errors during development. For example, while developing new analysis for Exakat, we have to rely on the tokens names and their relations. Currently, there are 114 different tokens and 67 type of relations. Those tokens evolve with time, so are their relations : everytime one of them is getting obsolete, we add an assertion.

The assertion act here as a fail-safe : it triggers warning early and often act close to the point of origin. We can then fix the name early, or decide to keep it until later, while knowing about it. When the code doesn’t emit anymore warning after several run, it is time to consider removing the assertion.

This is a classic development error : we want to detect this situation as often as possible so it won’t even go to production. Assertions are the best tool here : active in development, and removed in production.

Assertions are of temporary service

In the end, assertions are a development tool, and should be used that way : they are easy to write, and better than comments. Anytime they can spot a problem early is a good usage. With the maturity and stability of the code, they tend to upgrade to exception, or even better, just disappear.

Tweet about this on TwitterShare on RedditShare on LinkedInEmail this to someone