Page 1 sur 2

Gestion des erreurs 2 : plus poussé ^^

Posté : 17 juin 2006, 00:59
par insomniak
Salut à tous,

Pour reprendre mon collègue Sarthois Sadeq que je cite :
L'idéal en programmation objet est de s'aligner au niveau de l'objet à programmer pour concevoir le comportement de ses méthodes vis-à-vis des exceptions qui devraient provoquer ou pas son arrêt.

Le capteur d'exception try .... catch () est dans ce cas plus intéréssant que les die et exit

En principe l'algorithme de gestion des exceptions dans une méthodes qui manipule des commandes génératrices d'erreurs est le suivant :

PHP:
class foo {
function foo (args) {
//travailler tout en observant une erreur
try {
//le travail à faire
}
catch (MyException $e) {
//capteur de try : reçoit l'objet erreur et le nomme '$e'
//programmer ce que vous devez faire de l'erreur $e
//Peut être retourner l'objet $e à l'appelant :
return $e;
//Peut être retourner un False pour signaler un echec de la méthode
return false;
//Peut être afficher l'erreur survenue: (déconseillé)
echo $e->getError();
}
}
J'aimerai avoir un peu plus de précisions sur un fonctionnement théorique que je n'arrive pas à cerner.

L'exemple de Sadeq est pour moi tout à fait clair et ne me pose aucun probleme.
Là ou ca me pose probleme c'est dans la partie qui utilise les classes.
Voyons un exemple :

- Une classe Foo comme celle de l'exemple avec plusieurs méthodes gérant les erreurs comme indiqué dans l'exemple.
- un fichier mapage.php qui va utiliser la classe Foo.

Dans ce fichier mapage.php admettons que nous fassions un truc du genre :

* traitement lambda qui n'a rien à voir avec Foo
* on instancie Foo
* Foo->methode1
* un test sur un des attributs de foo
* foo->methode2
* un traitement qui n'a rien a voir avec foo.

Imaginon que la methode 1 de Foo remette en question totalement l'objet en lui meme (objet pas completement chargé, erreur ou autre) mais que l'on souhaite tout de meme effectuer les traitements lambda qui n'ont rien a voir avec foo ET en plus afficher qu'il y a une erreur à l'utilisateur.
Comment agiriez vous ?

Personnellement je pensais à un truc mais ca va faire du code archi moche quoi....

Voici mon exemple :
Dans la mesure ou mes methodes renvoient true en cas de succes et false en cas d'echec :

Code : Tout sélectionner

/* traitement lambda */ $foo = new Foo; if($foo->methode1()) _____ /*test sur mon attribut et traitement annexe */ _____if($foo->methode2()) _________/*autres traitements avec foo */ _____else _________echo 'une erreur est survenue'; else _____echo 'une erreur est survenue'; /*traitement lambda*/
Comme vous pouvez le constater, il faut cascader les tests à la queue leuleu et ca donne un truc vraiment horrible à coder.
Mon exemple étant simple avec seulement deux tests ne montre pas vraiment le probleme mais imaginez que vous devez appeler 8 méthodes par exemple, cela fait au moins 8 tabulations ! soit 40 espaces bouffés (en suivant la norme de codage (1tab = 5caracteres)!

Bref, je voulais savoir comment vous gereriez cela de façon efficace et pro ?

Merci d'avance
@++

ps : désolé pour les "_" dans mon code exemple, mais phpbb n'a pas encore compris qu'il était possible d'ajouter un bbcode "tab" pour inserer 5 fois &nbsp ; m'enfin...

Posté : 17 juin 2006, 14:12
par Lorenzo
on ne mets pas une action/fonction/methode par try .... :D
ca reviendrait a encapsuler l'intégralité de ton code pour y empecher d'eventuels erreurs ce qui serait completement inutile et tres tres lourd :!:

logiquement un bon programmeur n'aura meme pas besoin de ce genre de systeme pour empecher d'eventuels erreurs car il aura deja prevu les possiblités et implanter des blocs conditionnels et des tests pour verifier les données ou autre pouvant poser un eventuel probleme, generalement les données utilisateurs (d'un form ou autre) ou d'une BD (ou autre espace de stockage).

Posté : 17 juin 2006, 14:39
par insomniak
Salut Lorenzo

Oui je suis d'accord avec toi, un bon dev doit prevoir les differentes eventualités.
Mais pour te montrer mon probleme j'ai un exemple concret :

Imagines une classe Picture avec les méthodes suivantes :

LoadPicture : charge une image. renvoie true si tout est ok ou false si erreur (fichier absent, fichier n'est pas une image, plantage au chargement de l'image, bref les cas à gérer quoi).

Redimensionner : redimensionne une image selon les valeurs passées. Fonction bete et méchante qui fait ce qu'on lui dit. Idem, on gere tous les cas d'erreur (pas d'image chargée...)

RedimBox : calcule les valeurs width et height optimales pour rentrer dans la box définie par les parametres width et height. Une fois calculées, lance la methode redimensionner. Idem on gere les erreurs

SaveToFile : enregistre l'image dans un fichier. On gere encore une fois les erreurs (fichier deja existant, pas de possibilité d'enregistrer...)

Toutes ces méthodes renvoient true si ok false si nok.

Maintenant, imaginons que nous lancions un processus pour redimensionner une image bete et mechante. Si je suis ce que tu dis, et je te cite : " il aura deja prevu les possiblités et implanter des blocs conditionnels et des tests pour verifier les données ou autre pouvant poser un eventuel probleme", voila ce que ca donne :
$image = new Picture;
if($image->loadPicture($filename))
  if($image->redimbox($width, $height))
    if($image->saveToFile($fichier_dest))
      echo 'tout est ok';
    else
      echo 'erreur';

Bon là ce n'est qu'un exemple avec 3 méthodes, mais imagines que tu sois obligé d'appeler 12 méthodes sur lesquelles tu dois savoir si ca a merdé ou non avant de passer à la suite... Là c'est galère.

Alors hier j'ai vu avec quelqu'un sur le chat qui m'a conseillé de faire comme ceci :
try {
$image = new Picture;
$image->loadPicture($filename);
$image->redimbox($width, $height);
$image->saveToFile($fichier_dest);
}
catch (Exception $e)
{
echo "y'a un pepin"; (bon la je simplifie, mais je vais logguer tout ca dans ma bdd...
}
Voilou ! Donc maintenant, à voir au niveau des pou et des contres, justement, je cherche des témoignages de gens qui ont une vraie méthode de gestion d'erreur qu'ils ont pu tester à fond.

Merci d'avance
@++

Posté : 17 juin 2006, 15:55
par Lorenzo
j'ai un exemple et avis concret justement pour gerer ca !

toutes mes classes descendent d'une classe qui s'appelle ERROR et qui est la pour gerer toutes les erreur suivant une config que l'on peut personnaliser.
-gerer differents niveaux d'erreurs
-couper ou non le script en cas d'erreur suivant le niveau
-afficher ou non l'erreur simple (production) ou complete (devellopement)
-logger ou non l'erreur complete
- ..... etc

Posté : 17 juin 2006, 16:05
par insomniak
Tu m'interresses justement ! j'aimerai savoir comment tu procèdes dans un cas concret s'il te plait.
Pour que je comprenne encore mieux comment tu te servirais de cela, pourrais tu me donner une sorte d'exemple sur un cas similaire au miens s'il te plait ?

Genre, un index.php qui include un module.php qui utilise une classe.php
Sachant que dans module.php les actions avant et apres le traitement utilisant la classe ne sont pas toutes liées à ta classe et donc devront peut etre etre executées.

Merci d'avance, tu me serais d'un grand secours ;)
@+

Posté : 17 juin 2006, 16:36
par Lorenzo
sur un cas similaire ca va etre dur vu que je n'ai jamais eu a faire une classe picture ou ressemblant ...

comme je l'ai dit plus haut : TOUTES mes classes descendent de ERROR.
donc dans chaque methode importantes (pas celle pour une simple lecture ou ecriture d'une var) j'appelle la methode erGestion()

voici un exemple pour la methode de la classe REP (repertoire) qui crée un pointeur sur un repertoire :

	/*** ---- CREATION POINTEUR ----
	*
	* Creer un pointeur sur le repertoire $stCheminRep
	*
	* (booléen) TRUE | FALSE | coupe le script
	*/
	function repPointeur($stCheminRep){
		$this->stCheminRep = $this->repModif($stCheminRep);
		$this->rsPtRep = @opendir($this->stCheminRep);
		if( !$this->rsPtRep ){
			return $this->erGestion(
				"class Rep->repPointeur() :<br>Erreur lors de la création du pointeur sur le répertoire.",
				"class Rep->repPointeur() :<br>Erreur lors de la création du pointeur sur le répertoire.<br>".$this->stCheminRep."<br>".@$php_errormsg
			);
		}
		return true;
	}
si je passe 2 parametres différents a erGestion() c'est juste que ca correspond a : erreur simple (production) ou complete (developpement)

Posté : 17 juin 2006, 17:00
par rami
Faire hériter toutes ses classes d'une classe destinée à gérer les erreurs est une erreur de conception à mon sens.

Pourquoi ne pas utilisée les exceptions? Elles sont faites pour cela.

Voici comment je les utilise. Je me place dans un contexte MVC, ce qui signifie que toute requête passe toujours par le même point d'entrée de mon application (généralement index.php).


Dans toutes les méthodes de classe, je teste les types des arguments passés, et je lance une exception spécifique (BadTypeException par exemple) s'ils ne correspondent pas au type attendu. De même, lors d'un accès à une ressource, je lance une exception si elle n'existe pas ou est occupée. Lorsqu'une varaible est null alors qu'elle ne devrait pas, je lance une exception du type NullPointerException, etc.

Comme je fais des "throw", ces exceptions "remontent" jusqu'au point d'entrée de l'application. Là, 2 choix sont possibles :

- faire un try / catch général et gérer les erreurs dans le bloc catch
- définir son propre gestionnaire d'exception

Cela permet de faire du log afin de tracer les erreurs, de personnaliser les erreurs, de centraliser la gestion de ces erreurs, de basculer entre un mode développement (où on affiche toutes les informations pour débugguer) et un mode production, etc.


J'espere t'avoir espérer aider un peu ;)

Posté : 17 juin 2006, 17:16
par Lorenzo
c'est exactement ce que je fais avec la classe erreur sauf que je n'ai plus a m'en occuper quand je programme une appli donc : temps gagné + facilité + presentation propre car - de code ...etc

Posté : 17 juin 2006, 17:19
par insomniak
Ahhhh ca devient interressant tout ca !

Lorenzo : je pense que Rami a raison car php5 est une version POO et en POO dans les autres langages (non web) on utilise les exceptions. Je pensais au départ que tu les utilisais, sinon je ne t'aurais pas demandé un exemple ^^. C'est justement pour éviter de galerer avec ce genre de systeme que je m'oriente vers les exceptions. Beaucoup plus simple, beaucoup plus flexible et surtout une gestion des erreurs bien plus accrue (notamment en terme de détails de bugs...). Désolé Lorenzo.

Rami : Effectivement je me situe dans ta situation : MVC + Exceptions.
Seul détail qui change : j'ai une étape intermédiaire par rapport a ce que tu viens d'expliquer.

Je suis plutot comme ceci :

index.php -> module.php -> classe.php

Mon soucis pour le moment est de savoir comment BIEN gerer les exceptions. Car les utiliser je sais faire, mais bien les utiliser, je n'en suis pas sur. C'est surtout ca l'objet de ma question.

Concernant ta méthode Rami : avec des exceptions BadTypeException, NullPointerException, elles sortent d'ou ces exceptions ? ce sont des classes dérivées de Exception ou bien tu utilises celles de php ?

Ensuite, j'avais pensé faire un truc, car gérer les exceptions uniquement au niveau de index.php est assez restrictif : admettons qu'un bug survienne dans une classe qui va servir uniquement à afficher un pauvre truc tout bidon dans une page mais qui ne gene en rien le reste. Beh si tu geres au niveau de ton index.php, le reste du code de module.php n'est pas executé donc en gros tu mets en rade toute une page pour finalement un petit bouiboui de rien du tout.

En reflechissant, je me suis dit que je pourrais éventuellement faire un truc interressant :

Faire 3 classes d'exceptions differentes héritant de Exception :

- une classe UserError
- une classe CodeError

La classe UserError serait donc catchée dans le module.php et ne zigouillerai pas toute la page, mais afficherai uniquement un message d'erreur à la place de ce qui aurait du etre affiché.

La classe CodeError qui serait elle catchée dans l'index.php et qui là causerait l'arret de la page.

Comme cela, on pourrait éviter quelques petits désagréments non ?
Qu'en pensez vous ?

Merci de vos participations en tout cas
@++

Posté : 17 juin 2006, 18:00
par rami
Concernant ta méthode Rami : avec des exceptions BadTypeException, NullPointerException, elles sortent d'ou ces exceptions ? ce sont des classes dérivées de Exception ou bien tu utilises celles de php ?
J'utilise les exceptions de la SPL plus certaines que j'ai créé en dérivant la classe de base Exception. Elles ne font rien du tout, elles permettent seulement d'identifier quelles types d'erreur a entraîné l'exception.

Je ne suis pas sûr de saisir le fonctionnement de ton application. Peux-tu détaillé le fonctionnement de celle-ci?

En ce qui concerne la suite de ton message, je ne suis pas tout à fait d'accord. Pour moi, toute erreur impliquant que l'application ne puisse pas remplir sa fonction entraîne la levée d'une exception. Par exemple, un identifiant null ou égal à 0, un chemin qui n'existe pas, une connexion à une base impossible...

Si l'erreur provient d'une donnée extérieure (saisie d'un utilisateur le plus souvent), aucune exception n'est levée car cela ne cause pas le dysfonctionnement de l'application.

Il ne faut pas confondre erreurs de fonctionnement de l'application - qui doivent alors lever des exceptions - et erreurs fonctionnelles, qui doivent alors générer un message d'erreur à l'utilisateur.

Cependant, il est possible de gérer les erreurs fonctionnelles avec des exceptions. Il faut alors discrimer les exceptions "fatales", des exceptions "fonctionnelles". Il est possible de faire cela en dérivant Exception et en ajoutant un attribut. Je ne suis pas partisan de ce type de manipulation car , pour moi, les exceptions correspondent à un dysfonctionnement de l'application.

Je préfère passer par une classe qui gère les erreurs, ou bien par une variable de session. pour gérer les erreurs fonctionnelles.

Posté : 17 juin 2006, 19:49
par insomniak
re Rami

Pour ce qui est de ne pas confondre exception et erreurs utilisateur, je suis d'accord avec toi. Seulement, j'ai un probleme avec le code, comme j'ai expliqué, si je dois tester tous les retours pour savoir si oui ou non je dois continuer le code, ca me donne une cascade phenomenale de if...
Le try catch m'aurait permis de gérer cela sans if.
Maintenant dans un soucis de logique de developpement, comment fais tu toi pour eviter cela ? (Cf exemple au dessus, consideres seulement que les methodes de la classe renvoient juste true ou false et que le false est une erreur utilisateur)

Merci !

ps : je re plus tard pour t'expliquer plus precisement le fonctionnement de mon site car je dois bouger là

Posté : 17 juin 2006, 20:43
par rami
Généralement, le code de mes méthodes ne dépassent guère 30 ou 40 lignes, ce qui fait que je n'ai pas énormément de tests à effectuer. Il arrive parfois que ce soit le cas, alors j'utilise le principe suivant :
<?php
/**
 * Méthode classique plutôt courte
 */
function test($int)
{
  if(!is_int($int))
    throw new BadArgumentTypeException('Argument de type entier attendu');
 //ici le traitement à effectuer
}

/**
 * Méthode plus longue
 */
function toto($int, $array)
{
   try
  {
       //traitements longs à effectuer
  }
  catch(Exception $e)
  {
       throw new Exception('Erreur dans '.__METHOD__);
  }

}
}

Tu captures les exceptions dans un blocs try / catch, et dans le catch, tu lances une exception. Cette pratique est du coup un peu moins précise quant à l'exception levée car tu ne peux pas savoir où ca a crashé. Cependant, cela peut éviter d'avoir à faire un nombre rébarbatif de tests.

Posté : 17 juin 2006, 20:53
par insomniak
re !

comme je suis de retour et que j'ai un peu de temps devant moi, voici l'explication de mon truc :

J'ai un fichier index.php qui gere les modules qui sont affichés sur ma page.
un parametre dans l'url dit quel module doit etre chargé. Sans parametre il include le fichier main.php

Dans main.php, j'affiche moult choses donc plusieurs parties et plusieurs objets qui n'interferent pas entre eux. Imaginons qu'un objet crash en cours de route. Il faudrait qu'uniquement cette partie merde et pas le reste.
Maintenant, dans les objets si je leve une exception de type erreur de soft, ok, je met en vrac la page, ca ca me semble logique. Donc pour la partie crash d'objet normal pour moi c'est acquis y'a pas de lezard.

Par contre pour ce qui est des erreurs autres (admettons, lors d'un upload, je teste si l'upload est une image de type gif ou jpeg) là je galere. Car avec un systeme d'erreur lambda je dois faire des tests if.
Pour te citer le pire exemple que je puisse te donner, voici un algo de ce que je fais dans la partie upload :
if(jai pas plus de 4 images dans ma banque d'images)
  if(ma 4eme image est vide)
    if(variable $_FILES[mon fichier] pas vide && pas d'erreur)
      if(fichier uploadé)
        if(fichier uploadé est une image)
          if(fichier uploadé est un gif ou un jpeg)
            if(sa resolution x < 2500 et sa resol y < 1950)
              alors fais le traitement
            sinon
              affiche "resol trop elevée"
          sinon
            affiche "ce n'est pas un gif ou jpeg"
        sinon
          affiche "ce n'est pas une image"
      sinon
        affiche "fichier pas uploadé"
    sinon
      affiche "erreur d'upload"
  sinon
    affiche "vous avez deja 4 images"
sinon
  affiche "vous avez dépassé la limite de 4 images"
      
Comme tu peux le constater ca donne pas mal de lignes pourrav avec une indentation en montagne.... bref, c'est pas top.
Avec un systeme de throw, je dirais que le probleme de l'indentation n'est plus, mais par contre, c'est plus vraiment dans la logique de developpement. De plus, pour afficher l'erreur à l'utilisateur sans trop perturber la suite c'est pas top...

Comment ferais tu toi?
Là je dois dire que je m'embrume le cerveau avec tout ca ^^

Pour l'exemple que tu viens de citer, pas de probleme ca c'est acquis pour moi.

Merci d'avance

Posté : 17 juin 2006, 21:41
par rami
D'une part tu peux découper tes méthodes (ou fonctions) par rafinement successif. D'autre part, tu peux regrouper des conditions, voire même en supprimer.

function ajout_image_possible()
{
if(jai pas plus de 4 images dans ma banque d'images et ma 4eme image est vide)
    if(variable $_FILES[mon fichier] pas vide && pas d'erreur)
    sinon
      return "erreur d'upload"
  sinon
    return "vous avez deja 4 images"
}



function contrainte_image()
{
  if(fichier uploadé)
        //la condition suivante suffit
       //if(fichier uploadé est une image)
          if(fichier uploadé est un gif ou un jpeg)
            if(sa resolution x < 2500 et sa resol y < 1950)
              alors fais le traitement
            sinon
              return "resol trop elevée"
          sinon
            return"ce n'est pas un gif ou jpeg"
   sinon
        affiche "fichier pas uploadé"
} 

Posté : 18 juin 2006, 00:10
par insomniak
Arf, je me doutais que tu allais dire ça... :(

D'abord, je tiens à préciser que certes j'aurais pu modifier mes if pour alleger tout ca, mais c'etait pour l'exemple. Il y a d'autres traitements qui sont aussi lourds voir plus lourds en conditions.

Pour ce qui est de la fonction, je ne suis pas trop pour en fait... je trouve que ca allourdit la compréhension du code pour pas grand chose, que tes if soient dans le code ou a part dans une fonction, ca reste à peu pres pareil (entre guillemets bien sur, j'ai bien vu ou tu voulais en venir).

En fait, ma question pour la résumer, c'est : "comment faites vous pour gérer vos erreurs quand vous gérez des cas particuliers d'utilisation de votre site (donc uniquement les erreurs utilisateurs quoi) sachant que vous devez stopper un bout de script après ce cas particulier sans pour autant stopper tout le script" ?

Encore merci en tout cas !