Des assert pour vos classes php5

Des assert pour vos classes php5

Introduction

Voici une explication sur l'utilisation des assert dans des classes en php5.
Ici, j'explique un peu l'utilité des assert, comment ça a été implémenté/remplacé dans d'autres langages, et comment on peut en faire des sympas en php5 OO.

Pourquoi et comment utiliser un assert en php5...

Généralités sur les asserts

Le assert est quelque chose qui sert énormément dans un programme,en général en C. On s'en sert souvent pour vérifier lors d'un debug, si nos variables sont bien cohérentes. L'utilisation d'un assert :

assert(chose_qui_doit_être_vraie);

On peut trouver de nombreux exemples sur les assert sur cette page : http://www.google.fr/#q=assert

voici un exemple marquant :

assert( min <= max );

en C, ce n'est pas une fonction mais une macro, elle peut-être définie comme ceci :

#ifdef DEBUG

#define assert(expr) {if (!(expr)) 

  FatalError("Assertion failed file %s, line %d: exprn", 

  __FILE__, __LINE__); }

#else

#define assert(expr)

#endif

Le fait que ça soit une macro ne ralentit en rien l'exécution en production (en debug, on s'en moque). C'est un avantage comparé aux assert de d'autres langages (php par exemple.)

Quand on débute, on peut s'imaginer que ça ne sert à rien de vérifier si un min est inférieur à un max, mais parfois, il arrive que des bugs nous amènent à cette situation, on doit donc pouvoir être prévenu en cas de bug, trouver le bug, et le corriger. L'assert ne nous permet pas de le corriger, mais il nous permet de savoir qu'il existe, et en gros de situer ou se trouve le bug.

En gros, vérifiez les évidences et vous debugerez moins longtemps.

Pourquoi utiliser un assert en php5 ?

En php, trop peu de gens se servent d'un error_reporting strict, on trouve trop de gens qui codent sans afficher les warnings et notices.
Si ce n'est déjà fait : la page : http://fr2.php.net/manual/fr/function.error-reporting.php vous offre un aperçu intéressant pour reconfigurer votre php.ini.
Voici la fonction assert prédéfinie dans php : http://fr2.php.net/manual/fr/function.assert.php trop peu s'en servent.
Tout les programmeurs C ont déjà vu des assert, j'en ai rarement vu en php. En php : http://www.google.fr/#q=php+assert 8000 codes trouvés seulement (contre 1 570 000 pour tout langages confondus)

En php, on peut passer des éléments du mauvais type, voir, laisser passer des choses par le client (en GET ou POST), qu'il ne devrait pas passer.

Parfois, vérifier tout ça, avec des assert, serait utile, ou vérifier qu'une date de naissance à une valeur inférieure à la date du jour, des choses du genre. Ça peut toujours être utile de vérifier ça, pour interdire les objets "mauvais", ces objets entrainent un code "fou", il est important débloquer le flux d'exécution quand on arrive là.

Quand on utilise le modèle objet, il est simple de vérifier dans chaque "seter" de propriété privée ou protégée, si la variable à une valeur "corrompue" ou non.

En php, on a pas de properties, on fait soit des seters, soit une méthode magique :

public function __set($clef, $valeur){
    $this->$clef = $valeur ;
}

Comment utiliser un assert en php5

On peut commencer par définir une Exception d'assertion.

class AssertException extends Exception {}

Pour la suite, on vérifiera si un humain a un âge négatif ou non.
En général, quand on fait de l'objet en java ou php5, on fait un truc du genre :

class Humain extends assertable{
  public function __construct($age){
    parent::__construct();
    echo 'Un humain nait<br/>';
    $this->setAge($age);
  }
  protected function setAge($age){
    if ($age < 0){
      throw new AssertException('age invalide');
    }
    $this->age=$age;
  }
}

En pascal ou C#, on peut utiliser les properties, ça revient à peu près au même, mais c'est transparent.

En php, la méthode magique __set permet de faire une fonction appelée pour gérer les affectations de variables membres par du code qui n'est pas contenu dans la classe.

$truc->clef=val;

cependant, ça à plusieurs inconvénients : exemple : l'utilisation ne sera pas transparente, c'est seulement ce qui est hors de la classe qui appelle __set. Voici un exemple de la porté de __set.

//exemple pour voir les appels de __set

class A{
    public function __construct($a){
        $this->a=$a; // ceci ne l'appelle pas.
    }
    public function __set($k, $v){
        echo 'appel a __set';
        $this->$k=$v;
    }
    private $a;
}
$a=new A('ok'); // ceci ne l'appelle pas.
$a->a='B'; // ceci l'appelle

Pour l'avoir de façon transparente, à l'intérieur de la classe, on doit faire un héritage :

abstract class assertable{
  public function __construct(){ }
  public function __set($key, $val){
    $this->$key = $val;
    $this->__assert();
  }
  abstract protected function __assert();
}

Voici une classe qui en hérite :

class Humain extends assertable{
  public function __construct($age){
    parent::__construct();
    echo 'Un humain nait<br/>';
    $this->age=$age;
  }
  protected function __assert(){
    if ($this->age > 130){
      throw new AssertException('age invalide');
    }
  }
}

Inconvénients

L'inconvénient principal, c'est la porté de la variable.
Humain::age n'existe pas, en réalité, c'est assertable::age, ce qui fait que age est défini comme étant public...

Ce qui est intéressant ici, c'est que le coté "est-ce public ou pas" n'intervient pas trop, puisqu'on reste dans la boite noire, avec ce seter magique qui vérifie les mauvaises valeurs (en résumé, on simule des sortes de properties du C# ou freepascal).

Cependant, ça peut-être embêtant pour des choses comme ceci :

[assertable] <--- [Humain] <--- [Enfant]

Sur un UML comme ceci, on aurait des choses bizarres : on aurait toutes les variables membres de Enfant stockées en réalité dans assertable.

Avec une déclaration correcte des variables, si on avait voulu définir dans Humain une variable private ($age par exemple), et le redéfinir dans Enfant, sans le hack, ça nous aurait fait deux instances différentes (à condition de les déclarer avant). Avec le hack, ça ne fonctionne plus, nos deux Enfant::age, et Humain::age sont les mêmes variables.

Conclusion

Utiliser les assert vous fera gagner du temps lors du debug, utiliser les __set vous permet d'appeler vos assert quand vos variables sont modifiées depuis d'autres classes, et utiliser __set couple à de l'héritage vous permet de faire un assert puissant, qui vérifie chaque modification interne à la classe, bien que vous y perdiez en rigueur.

Ce document intitulé « Des assert pour vos classes php5 » issu de CodeS SourceS (codes-sources.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.
Rejoignez-nous