Articles

Le mois de la fonction PHP : assertions

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

This document is also available in English en 


Nous avons vu durant ce mois différentes fonctions natives pour déboguer PHP. var_dump(), print_r() et tout simplement echo() sont généralement en tête du palmarès de déboguage. Mais elles présentent un inconvénient majeur : à la mise en production, il est fréquent d'en oublier.

Qui n'a pas pesté contre un 'coucou' qui apparaît intempestivement dans le site Web, ou un print mysqli_error($mid); qui a été oublié? Pour s'en prémunir, les outils de mise en production incluent alors un grep pour identifier les affichages print_r(), et parfois aussi print() : par convention, print() est utilisé pour le déboguage, et echo() pour l'affichage. Ou le contraire.


Il serait bien pratique de pouvoir faire disparaître du code en production tous ces tests, d'un seul coup de baguette magique. Ou bien, dans le cas de PHP, d'un coup de directive. C'est exactement ce que fait les assertions.

Les assertions sont des tests à placer dans le code, un peu partout. Elles sont composées d'une condition. Dans sa forme la plus simple, on trouve la syntaxe suivante :

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

Le résultat de ce script est :

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
connexion &eacute;chou&eacute;e


La fonction assert() a exécuté le code PHP qui lui est passé en argument. Si ce code retourne TRUE, alors la fonction ne bronche pas, et continue. Si ce code retourne false, elle émet par défaut une alerte. C'est la deuxième alerte que le code produit.

Notez que la commande assert() fait la même chose que la condition if qui est fournie juste en dessous. La condition doit simplement être inversée : assert() vérifie avec is_object() alors que le if vérifie avec !is_object().

Désormais, les tests de viabilité dans le code produisent des alertes comme du code PHP invalide, au lieu de produire un affichage qui serait difficile à séparer de la production de contenu elle-même. D'ailleurs, vous pouvez annuler complètement les affichages avec error_reporting(), et même noter ces alertes dans le fichier de log.

Certains noteront que le if ci-dessus produit un arrêt du script, alors que assert() ne fait que lever une alerte. Il est possible de relever le niveau critique des assertions avec 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("connexion echou&eacute;e\n");
}
 
 
?>

Avec ce script, on a maintenant le résultat suivant :

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

Notez que la condition if() n'est plus évaluée, car assert() a interrompu le fonctionnement. Dans le cadre d'un développement, cela permet d'arrêter directement l'exécution dès que la première erreur apparaît. Pour renforcer la robustesse de son programme, il suffit de placer des assertions un peu partout dans le code, comme on le fait avec les conditions if() de déboguages.

Jusqu'à présent, nous avons surtout égalé les fonctionnalités que propose if() en termes de déboguages. Il y a encore deux autres fonctionnalités qui permettent de dépasser if() comme outil de débogage. La première est que assert() accepte un gestionnaire personnalisé.

<?php
 
 
function deboguage($fichier, $ligne, $condition) {
    $condition = htmlentities($condition);
    echo "\n\nUne assertion a &eacute;chou&eacute; : 
        dans le fichier \"$fichier\",
        &agrave; la ligne \"$ligne\",
        la 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("connexion echou&eacute;e\n");
}
 
 
 
?>

Ce script produit alors

 
 
Warning: mysqli_connect(): (28000/1045): Access denied for user 'user'@'localhost'
 (using password: YES) in /Users/macbook/Desktop/test.php on line 19
 
 
 
Une assertion a &eacute;chou&eacute; : 
        dans le fichier "/Users/macbook/Desktop/test.php",
        &agrave; la ligne "21",
        la condition "is_object($mid)"
 
 
---- Code ---
 
 
assert_options(ASSERT_CALLBACK, 'deboguage');
$mid = mysqli_connect('localhost','user','bad_pass','base');
 
 
assert('is_object($mid)');
 
 
if (!is_object($mid)) {
    die("connexion &eacute;chou&eacute;e\n");
}
 
 
---- Code ---
 
 
Warning: assert(): Assertion "is_object($mid)" failed in script.php on line 21
connexion echou&eacute;e

Notez que nous aurions pu aussi arrêter le script en configurant ASSERT_BAIL comme dans l'exemple précédent. En prime, nous avons même un affichage du code qui a produit l'erreur, ce qui donne un début de piste pour corriger le problème.

Enfin, le dernier avantage des assertions est qu'elle sont pilotées par une directive. Vous pouvez utiliser

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

en début de code, ou bien la directive assert.active dans php.ini pour désactiver automatiquement toutes les assertions dans votre application. Pas besoin de leur faire la chasse dans le code : PHP se chargera de les ignorer durant l'exécution. Cette approche a un coût en termes de performances, mais désormais, vous avez la conscience tranquille : plus aucun affichage de déboguage inopiné.

Si les pertes en performances ci-dessus sont rédhibitoires, il y a une autre alternative : utilisez un analyseur de code ou même grep pour identifier et supprimer toutes les occurrences de la fonction assert() dans le code, lors de la mise en production : il n'y a aucune chance de les confondre avec echo() ou print(), et de supprimer un affichage par inadvertance!

A mémoriser
  • les assertions sont disponibles depuis PHP 4
  • si la fonction de rappel n'existe pas, assert() reprend son comportement par défaut
  • les assertions sont activées par défaut dans les configurations PHP.
  • les assertions exécutent du code PHP, passés comme chaîne de caractères. Tâchez de toujours utiliser des chaînes de caractères fixes, et de ne pas créer les conditions à la volée, pour ne pas ouvrir la porte à des injections.
< Précédent   Suivant >

Commentaires

Vous pouvez ajouter votre commentaire!


Vous devez vous connecter pour commenter