Page 1 sur 1

Etrangeté de php ?

Posté : 05 juil. 2009, 14:33
par boobz
Bonjour,

En codant une validation d'entrée de formulaire, je suis tombé sur un comportement étrange de php (en tous cas pour moi).

Code : Tout sélectionner

<?php $a = array("1","1a","a1","a","-1","-2h","-h2","l'p0","12'ps"); foreach ($a as $key => $value) { $temp = $value + 4; echo "(".$value." + 4 ) = $temp<br>"; } ?>
Avec comme résultat :

Code : Tout sélectionner

(1 + 4 ) = 5 (1a + 4 ) = 5 (a1 + 4 ) = 4 (a + 4 ) = 4 (-1 + 4 ) = 3 (-2h + 4 ) = 2 (-h2 + 4 ) = 4 (l'p0 + 4 ) = 4 (12'ps + 4 ) = 16
Donc la somme d'un nombre avec quelque chose qui n'en est pas (par ex. 12'ps) donne un nombre !

Manifestement php considère qu'une chaîne de caractère qui commence par des chiffres est (comme) un nombre (12'ps devient 12) et une chaîne de caractère qui contient un chiffre vaut le nombre zéro dans un contexte d'arithmétique.

Ce comportement est en fait voulu (cf. http://www.php.net/manual/fr/language.t ... conversion).

En connaissez-vous les raisons ainsi que la manière de traiter ce genre de cas dans un formulaire ? Je m'explique.

J'attends en entrée une variable $var > 0. Si je fait un test tout bête :

Code : Tout sélectionner

if ($var > 0) echo "ok"
Si $var = 3, c'est ok, si $var = "3sdsd", c'est *aussi* ok. Il faut donc trouver un autre test, par exemple, is_numeric. Si $var = 3, c'est ok, si $var = "3sss", c'est pas ok et si $var= '' (cas où aucune valeur n'est entrée dans le formulaire), c'est pas ok (ce qui est embêtant quand on arrive pour la première fois sur le formulaire).

J'ai l'impression de tourner en rond par la faute de php qui considère '12ksksks' comme un nombre (mais pas numérique ...).

Quelqu'un aurait la gentillesse de me remettre sur la bonne voie ?

Merci d'avance
boobz

Posté : 05 juil. 2009, 15:19
par jojolapine
Bonjour,
La manière la plus classique de faire la vérification d'un formulaire, c'est tout d'abord de tester si ce dernier à été "submit"... en l'occurence, si on veut tester ke champ de formulaire "nom", et que le formulaire est en méthode post:
<?php
// Si le formulaire a été submit
if(isset($_POST['nom'])){
    // test sur la longueur
    if(strlen($_POST['nom']) > 12){
         /// bla bla bla
    }
}
et si on veut tester le champ age par exemple:
<?php
// Si le formulaire a été submit & que le contenu est bien un nombre
if(isset($_POST['age']) && is_numeric($_POST['age'])){
    // test
    if((int)$_POST['age'] > 18){
         /// vous êtes majeur!
    }
}
Voilà à peu près l'exemple, maintenant essaye un peu sur ton formulaire,e t reviens nous dire si ça coince ;)

Posté : 05 juil. 2009, 16:32
par Nagol
l'évaluation de string en tant qu'int est particulierement trompeuse, mais comme on est pas sensé faire des opérations mathématiques avec des string ou comparer des strings avec des ints quand on code bien, je trouve pas ca choquant. oui php est faiblement typé, mais il est typé...

Posté : 05 juil. 2009, 22:19
par boobz
Bonsoir,
l'évaluation de string en tant qu'int est particulierement trompeuse, mais comme on est pas sensé faire des opérations mathématiques avec des string ou comparer des strings avec des ints quand on code bien, je trouve pas ca choquant. oui php est faiblement typé, mais il est typé...
OK. Alors comment fait-on « quand on code bien », pour tester une valeur d'un formulaire (type="text", donc string passé à php) ? Dans mon cas, je veux tester si ma $var > 0, si oui je passe à la suite, si non, je mets en rouge et attend que l'utilisateur entre une donnée correcte.

Merci d'avance pour vos suggestions.

Posté : 06 juil. 2009, 09:10
par jojolapine
Tu as lu ce que je t'ai donné?

Posté : 06 juil. 2009, 09:40
par boobz
Tu as lu ce que je t'ai donné?
Certes, mais cela ne m'aide pas vraiment. Le problème vient vraiment du fait que le formulaire me renvoir une string alors que je veux un nombre entier.

Si je rentre $var = 12kksksks

ça va passer alors que je ne veux pas. Mais trêve de parlotte, il faudrait que j'explique un peu mieux mon raisonnement.

1) je teste sur submit, si ok, je teste mes variables les unes après l'autre et s'il y a erreur, je mets ça dans un tableau $error :

Code : Tout sélectionner

// teste si le formulaire a été soumis. Si oui, on crée le tableau des erreurs $allempty = 0; if (isset($_POST['submit'])) { $error = array(); // on fait une boucle foreach sur le tableau $_POST $i=0; foreach ($_POST as $key => $value) { echo "key: ".$key." ==> value: ".$value."<br>"; if ( empty($value) ) { $value = 0 ; } elseif ( $value < 0 ) { array_push($error,"Negative value (".$value.") for ".$key.""); } elseif ( !is_numeric($value) ) { array_push($error,"".$key." is NOT numeric"); } elseif ( $value > 0 ) { echo "ok for ".$key."<br>"; } $i=$i+1; if ($i > 2) { // on arrive à submit => on sort du foreach break; } } // on demande au moins une entrée if ( empty($_POST['cd_qty']) && empty($_POST['pub_qty']) && empty($_POST['hq_qty'])) { array_push($error,'All three empty'); $allempty = 1; } // manque le reste mais pas important.
Maintenant j'ai le tableau $error. S'il est videi (if count($error)), c'est bon, sinon, je réaffiche le formulaire avec les données entrées et en rouge pour les erreurs.


Et maintenant, le code du formulaire :

Code : Tout sélectionner

<tr> <td><?php if (empty($_POST['cd_qty'])) {echo "CD";} elseif ( $_POST['cd_qty'] > 0 ) {echo "CD";} elseif ( !is_numeric($_POST['cd_qty']) ) {echo "<div class='error'>CD</div>";} else {echo "<div class='error'>CD</div>";} ?></td> <td><div align="left">5 Francs</div></td> <td><div align="right"><input type="text" name="cd_qty" value="<?php echo $_POST['cd_qty'];?>"></div></td> </tr>
et ainsi pour les autres variables. (le div class='error' met en rouge le texte).

Alors maintenant le raisonnement.

1° si $var = 12sss => $error sera rempli car pas numérique, *mais* en bas, la condifion >0 sera remplie et par conséquent le formulaire ne sera pas validé mais la ligne ne sera pas en rouge comme désiré. Et c'est là tout mon problème. Je me demande s'il ne faudrait pas vérifier l'entrée avec une regex, mais je trouve que c'est un peu con vu que des fonctions existent pout vérifier le type de variables.


Voilà, j'espère avoir été plus clair.

Merci pour vos idées.
boobz

Posté : 06 juil. 2009, 10:58
par Calimero
Je me demande s'il ne faudrait pas vérifier l'entrée avec une regex, mais je trouve que c'est un peu con vu que des fonctions existent pout vérifier le type de variables.
C'est une question pas aussi simple qu'elle en a l'air : il existe plein de façons de "valider" un nombre en php. Le problème c'est que de dire "ça c'est un nombre" et "ça ça n'en est pas un" nécessite déjà de faire des choix, car selon la notation et les besoins, certaines écritures peuvent être considérées comme des nombres et d'autres non.

par exemple : 0xDEFACED : c'est un nombre entier en hexadécimal (tu peux l'écrire tel quel dans ton code php, sans guillemets), ou bien une chaîne.

Ou encore 0.23e-5, c'est aussi un nombre (réel, pas entier) exprimé sous la forme mantisse+exposant.

Ou encore 2,4i, qui peut être considéré comme un nombre complexe.

Exemple encore plus simple, -8 : c'est un nombre (entier négatif) mais il comporte un signe, alors il ne validerait pas une fonction qui n'accepterait que les chiffres (par exemple celle-là : http://www.php.net/manual/fr/function.ctype-digit.php ).

Donc il n'y a pas vraiment de réponse définitive et tranchée, ce que tu considères comme un nombre valide ou non varie suivant le script que tu développes. Ce qui fait que la regexp reste somme toute une bonne solution puisqu'elle te permet de "voir" facilement ce qui passe ou non dans ton filtre de validation, à contrario de beaucoup de fonctions php dédiées à cet usage (qui ont presque toutes un comportement différent).

Posté : 06 juil. 2009, 11:16
par boobz
Je me demande s'il ne faudrait pas vérifier l'entrée avec une regex, mais je trouve que c'est un peu con vu que des fonctions existent pout vérifier le type de variables.
C'est une question pas aussi simple qu'elle en a l'air
Cela je m'en suis rendu compte la semaine dernière ;-)

il existe plein de façons de "valider" un nombre en php. Le problème c'est que de dire "ça c'est un nombre" et "ça ça n'en est pas un" nécessite déjà de faire des choix, car selon la notation et les besoins, certaines écritures peuvent être considérées comme des nombres et d'autres non.

par exemple : 0xDEFACED : c'est un nombre entier en hexadécimal (tu peux l'écrire tel quel dans ton code php, sans guillemets), ou bien une chaîne.
Hum l'exemple ... ;-)

Ou encore 0.23e-5, c'est aussi un nombre (réel, pas entier) exprimé sous la forme mantisse+exposant.

Ou encore 2,4i, qui peut être considéré comme un nombre complexe.

Exemple encore plus simple, -8 : c'est un nombre (entier négatif) mais il comporte un signe, alors il ne validerait pas une fonction qui n'accepterait que les chiffres (par exemple celle-là : http://www.php.net/manual/fr/function.ctype-digit.php ).
Je comprends tes exemples (j'ai lu la doc). Dans mon cas, je n'attends que des entrées entre 0 <= $var (je ne pense pas que mes utilsateurs vont s'amuser à écrire un nombre de manière scientifique. Et je ne le veux pas, $var est une variable qui représente une quantité qui sera dans la réalité assez petite (1000 au plus).
Donc il n'y a pas vraiment de réponse définitive et tranchée, ce que tu considères comme un nombre valide ou non varie suivant le script que tu développes. Ce qui fait que la regexp reste somme toute une bonne solution puisqu'elle te permet de "voir" facilement ce qui passe ou non dans ton filtre de validation, à contrario de beaucoup de fonctions php dédiées à cet usage (qui ont presque toutes un comportement différent).
Donc un truc du genre ?

Code : Tout sélectionner

if (!preg_match('/^[0-9]{0,}/', $value)) { echo "ok"; } else { echo "pas ok"; }

Merci pour ton explication Calimero (et pourtant je n'ai pas vu te plaindre..;-))


boobz

[Note : ce message a été posté de manière anonyme avant d'être réattribué à son auteur]

Posté : 06 juil. 2009, 12:58
par Calimero
Donc un truc du genre ?

Code : Tout sélectionner

if (!preg_match('/^[0-9]{0,}/', $value)) { echo "ok"; } else { echo "pas ok"; }

Merci pour ton explication Calimero (et pourtant je n'ai pas vu te plaindre..;-))


boobz
Toujours sans me plaindre donc, et vu que tu m'as suivi ;-)

J'ai corrigé trois erreurs : le ! devant qui inverse le sens du test, et le quantifieur (qui est erroné car tu attends toujours au moins un chiffre). Il faut aussi ajouter un délimiteur de fin de masque ($) pour être bien sûr. Ton expression régulière peut alors être simplifiée en :
if (preg_match('/^\d+$/', $value)) { echo "ok"; } else { echo "pas ok"; }
Mais tu peux aussi (et je te le recommande) compléter par une fourchette de comparaison, après t'être assuré via cette regexp que tu as bien affaire à un entier (quoique là, ta regexp permet aussi de s'assurer que ta valeur est _ au minimum _ un zéro, donc pas besoin de retester derrière) :
if (preg_match('/^\d+/', $value)) { if( $value < 1234567 ) { echo "ok"; } else { echo "hors limite"; } } else { echo "pas ok"; }

Posté : 06 juil. 2009, 13:02
par jojolapine
Juste pour infos...
ça n'est pas plus rapide dans ce cas là d'utiliser ctype_digit() puis une fourchette?

Posté : 06 juil. 2009, 13:06
par Calimero
Juste pour infos...
ça n'est pas plus rapide dans ce cas là d'utiliser ctype_digit() puis une fourchette?
Si tu as la patience d'attendre ce soir je pourrai te répondre benchmark à l'appui (en listant d'autres solutions).

A priori, je pense pas que la différence de performance soit vraiment notable, à moins que cette validation serve plus de dix mille fois d'affilée dans ton script. Mais c'est toujours judicieux de se poser la question ;-)

Posté : 06 juil. 2009, 14:26
par boobz
Donc un truc du genre ?

Code : Tout sélectionner

if (!preg_match('/^[0-9]{0,}/', $value)) { echo "ok"; } else { echo "pas ok"; }

Merci pour ton explication Calimero (et pourtant je n'ai pas vu te plaindre..;-))


boobz
Toujours sans me plaindre donc, et vu que tu m'as suivi ;-)
Mais je suis toujours très bien quand l'explication est aussi clair !

J'ai corrigé trois erreurs : le ! devant qui inverse le sens du test, et le quantifieur (qui est erroné car tu attends toujours au moins un chiffre). Il faut aussi ajouter un délimiteur de fin de masque ($) pour être bien sûr. Ton expression régulière peut alors être simplifiée en :
° Pour le !, c'est parce qu'en fait j'utilise cette fonction pour créer mon tableau des erreurs, donc il faut le contraire.

° Le quantificateur +, évidemment, merci.

° Et pour le $, merci également, je me demandais avant d'aller manger pourquoi quelque chose du style de '12skks' passait. Et bien je crois que c'est parce qu'il s'arrête à la première occurence trouvée, est-ce juste ?

if (preg_match('/^\d+$/', $value)) { echo "ok"; } else { echo "pas ok"; }
Marche parfaitement, merci mille fois !

Mais tu peux aussi (et je te le recommande) compléter par une fourchette de comparaison, après t'être assuré via cette regexp que tu as bien affaire à un entier (quoique là, ta regexp permet aussi de s'assurer que ta valeur est _ au minimum _ un zéro, donc pas besoin de retester derrière) :
Pour s'assurer que la valeur n'est pas trop grande, c'est bien ça ? Le problème c'est qu'a priori je ne connais pas la quantité maximale.
if (preg_match('/^\d+/', $value)) { if( $value < 1234567 ) { echo "ok"; } else { echo "hors limite"; } } else { echo "pas ok"; }
Ok merci.


PS : petite question subsidiaire pendant qu'on y est. Ici il était question de tester une valeur numérique, mais évidemment dans mon formulaire je demande nom et prénom aussi. J'ai essayé d'écrire une regexp pour cela, mais quelle n'a pas été ma surprise de constater que ce n'était vraiment pas simple si l'on veut couvrir le champs entier des noms écrit avec notre alphabet. Mes clients proviennent du monde entier et ont donc des noms avec un ', et/ou un -, et/ou des espaces et j'en passe et des meilleures. Ma question est donc simple. Est-ce que l'usage est de vérifier ce genre d'entrée et si oui auriez-vous des exemples ?

PS2 : veuillez excuser le formatage s'il est un peu bizarre, j'écris ça depuis w3m, alors ce n'est pas toujours très évident de s'y retrouver avec les balises.


Merci encore pour votre aide,
boobz