JeuWeb - Crée ton jeu par navigateur
Problême avec une boucle dans un tableau - Version imprimable

+- JeuWeb - Crée ton jeu par navigateur (https://jeuweb.org)
+-- Forum : Discussions, Aide, Ressources... (https://jeuweb.org/forumdisplay.php?fid=38)
+--- Forum : Programmation, infrastructure (https://jeuweb.org/forumdisplay.php?fid=51)
+--- Sujet : Problême avec une boucle dans un tableau (/showthread.php?tid=6811)



Problême avec une boucle dans un tableau - Lindis - 27-01-2014

Salut à la communauté, je suis inscrit sur votre forum depuis un petit moment, :$
Etant assez patient et débrouillard je n'ai jamais demandé d'aide ici Big Grin
Mais la ce problême de boucle me cause bien du tors.

Je suis sur le développement d'un jeu de simulation de culture de canna... :!:

[Image: 604967hjgfgfjfhjuft.png]

La boucle du haut fonctionne bien, car elle affiche bien les données de la base Big Grin
mais elle est ici provisoirement.

Quand à celle du bas, elle n'affiche pas les données exactes comme je l'aimerais, et franchement je sêche :heu:
Voyez les tailles et la variété, elles ne sont pas les mêmes que la boucle d'au dessus qui dit vrai :bave:


Voici le code de la boucle du bas, si quelqun cerne le problême ou a une autre solution plus fonctionnelle.

Un grand merci à la personne qui saura m'aider. Smile


<?php
include 'config.php';
$nbCols = 3; // nombre de colonnes du tableau
$cpt = 0; // compteur d'éléments
$login = $_SESSION['login'];
$infosNiveau1 = mysql_query('SELECT * FROM niveau1 WHERE login="'.$_SESSION['login'].'"');
$infosNiveau1A = $infosNiveau1;
$infosNiveau1B = $infosNiveau1;


echo "<table border='0'>";

$rs = mysql_query('SELECT * FROM niveau1 WHERE login="'.$_SESSION['login'].'"');
while ($row=mysql_fetch_assoc($rs)) {
if ($cpt%$nbCols==0)

echo '<tr>';

$sql = 'SELECT * FROM niveau1 WHERE login="'.$_SESSION['login'].'"';
$req = mysql_query($sql) or die('Erreur SQL !<br />'.$sql.'<br />'.mysql_error());
$data = mysql_fetch_array($req);

echo '<td>';
echo "<table style='background:#fff;border-radius:10px;border:2px solid
#ddb619;padding:3px;margin:3px;box-shadow: 1px 1px 2px #555;' width='200px' border='0'>";
echo "<tr>";
echo "<TD ROWSPAN=2>";
echo "<img src='./../images/frez_pousse.png'/>";
echo "</TD>";
echo "<TD>";
echo "</TD>";
echo "</TR>";
echo "<TR>";
echo "<TD>Variété: <font color='green'>".$data['variete']."</font><br />
Stade: <font color='green'>".$data['taille']."</font> Cm <br />";
echo "Solution: <font color='green'>".$data['eau']."</font> cl";
echo "</TD>";
echo "</tr>";
echo "</table>";
echo '</td>';

if ($cpt%$nbCols==($nbCols-1))

echo '</tr>';

$cpt++;
}

echo '</table>';

?>



RE: Problême avec une boucle dans un tableau - Xenos - 27-01-2014

Salut,

1) Les <table> sont réservés aux données tabulaires
N'utilise pas une table, c'est totalement inadapté ici. Les données ne sont pas tabulaires. En fait, ce que tu cherches ici, c'est afficher chaque "brique" décrivant une plante l'une après l'autre, en revenant à la ligne si besoin. Le display "inline-block" fait exactement cela Wink
Donc, utilise plutôt une div (je n'ai rien de plus sémantique à proposer), à laquelle tu appliques un CSS "display:inline-block".


2) Attention à l'injection
Bonjour, injection SQL! Je ne sais pas quelle est la "fiabilité" de ta variable de session "login", mais ici, si jamais cette variable vaut "0\" OR \"1\"=\"1", alors la requête:
Code :
SELECT * FROM niveau1 WHERE login="$_SESSION['login']"
devient
Code :
SELECT * FROM niveau1 WHERE login="0" OR "1"="1"
Et j'ai accès à toutes les plantes de tous les joueurs. C'est pas le pire hack, mais on peut facilement imaginer qu'au lieu de 1=1, j'insère un "DROP DATABASE", et là, tu pleures Wink


3) fetch_array récupère déjà les données d'une ligne issue d'une requête
Je ne comprends pas pourquoi il y a ce code:
Code :
$sql = 'SELECT * FROM niveau1 WHERE login="'.$_SESSION['login'].'"';  
      $req = mysql_query($sql) or die('Erreur SQL !<br />'.$sql.'<br />'.mysql_error());  
      $data = mysql_fetch_array($req);

Puisque celui-ci est déjà présent:

Code :
($row=mysql_fetch_assoc($rs))

Alors $row contient les données d'une ligne de la BDD. Au lieu de "$data[*], tu devrais utiliser $row[*], qui contient les données de la ligne courante.


4) mysql_* sont dépréciées
Lis la documentation de php quand tu utilises une fonction. En effet, mysql_fetch_array est dépréciée depuis un moment. Préfères-lui mysqli (en Orienté Objet ou en Procédural), ou PDO (mais celui-ci est plus abstrait je trouve).


5) Tout code tapé doit être utilisé
Tu as un "$login = $_SESSION..." en début de fichier: sers t'en dans la suite, au lieu d'utiliser $_SESSION (dans les requêtes SQL par exemple)


6) HTML5 est la nouvelle norme
Passe à HTML5. Le tag font est déprécié depuis longtemps. Utilises les classes dans le code html, et applique ta mise en forme via un CSS.


7) echo() peut être multi-ligne
Une instruction echo() peut s'étendre sur plusieurs lignes si besoin. Au lieu de répéter:
Code :
echo "<td>";
echo "</td>";

Tu peux utiliser

Code :
echo "<td>
</td>";

Ce sera un poil plus rapide, mais ce sera surtout plus lisible je pense, d'autant que tu peux alors indenter ton code HTML:

Code :
echo ("
<table>
    <tr>
        <td>
            Texte ".$row['taille']."
        </td>
    </tr>
</table>
");

8) Fixe-toi des règles de codage HTML
Ici, on voit fleurir plusieurs façons de coder:
TD, tr, ROWSPAN=5, width='200px' (qui devrait d'ailleurs aller dans l'attribut 'style', qui devrait lui-même être ensuite remplacé par un attribut 'class' et un code CSS)...
Fixes-toi des règles! Par exemple:
  • Tous les noms de tag en minuscule (ou tous les noms de tags en MAJUSCULES, mais pas un mélange des deux).
  • Tous les noms d'attributs en minuscules (donc, "rowspan", et pas "ROWSPAN")
  • Toutes les valeurs d'attributs entre guillemets simples (donc, rowspan='5' et non rowspan=5)

Un changement de convention doit avoir une raison. Tu peux choisir de mettre certains attributs en majuscules, par exemple, "data-MES-DONNES", mais évites de faire du mixe.
Par exemple, dans mes code PHP, j'utilise la camelCase pour les variables, et la snake_case préfixée par "p_" pour les paramètres des fonctions. Ainsi, je sais que "$p_ma_variable" est un paramètre de la fonction (donc, non vérifié et auquel on ne peut pas faire confiance, il peut être source d'injection par exemple) alors que $maVariable est une variable créée dans la fonction, et donc, elle est réputée sûre (car vérifiée à sa création).

9) Préfère "for" à "while"
Surtout quand c'est parfaitement justifié comme ici Wink
Les boucles for utilisent la syntaxe

Code :
for (<instruction initiale>;<test>;<instruction répétée>)

Avant le lancement de la boucle, <expression initiale> est exécutée. Le <test> est ensuite vérifié. S'il est valide, le contenu de la boucle est exécuté. Enfin, <instruction répétée> est exécuté, puis on recommence en vérifiant <test>. La boucle se termine si "break;" est rencontré, ou si <test> n'est pas passé (<test> est faux).
On peut mettre plusieurs <instruction initiale> et/ou plusieurs <instruction répétée>, en les séparant d'une virgule.
Enfin, les variables créées dans <instruction initiale> seront détruite lorsque l'on quittera la boucle. Par exemple
for ($i=0;$i<10;++$i)
{
echo ($i . " ");
}
echo ("$i n'existe plus à partir d'ici!");


Donc, dans ton cas:
Code PHP :
<?php 
for ($compteur = 0, $requete = mysql_query('SELECT * FROM niveau1 WHERE login="'.$_SESSION['login'].'"'); $row = mysql_fetch_array($requete); ++$compteur)
{
// ... code précédent
// $cpt ++ // déjà inclus dans le "for"
}
// $compteur existe toujours!
// Il vaut d'ailleurs le nombre total de lignes retournées par la requête
Cela t'évitera de trainer "$compteur" et "$requete" alors que tu n'en n'a plus besoin une fois la boucle terminée.


RE: Problême avec une boucle dans un tableau - xviniette - 27-01-2014

Pourquoi faire 3 fois la même requête ? Tu n'utilises pas $infosNiveau1 et à quoi sert celle dans le while ?

Edit : Xenos est beaucoup trop rapide !


RE: Problême avec une boucle dans un tableau - Lindis - 27-01-2014

Merci à vous, votre aide m'a été trés utile, j'ai résolu le soucis que je cherchais depuis perpéte Smile
Super la rapidité Smile

En revanche je n'ai pas trop compris quel est le soucis de sécurité de cette requête

SELECT * FROM niveau1 WHERE login="$_SESSION['login']"

qui est censée devenir

SELECT * FROM niveau1 WHERE login="0" OR "1"="1"

Merci pour tout.

http://green-paradis.comlu.com


RE: Problême avec une boucle dans un tableau - Xenos - 28-01-2014

tl;dr
L'injection va venir de la fiabilité de $_SESSION['login']: si elle n'est pas vérifiée et que sa valeur vient du visiteur, alors ce visiteur peut injecter du code dans la requête et lui faire faire ce qu'il veut.
Crée des fonctions séparées dédiées à la lecture/écriture de la BDD.
Dans toute requête, cast tes variables avant de les insérer dans la requête ("SELECT * FROM `foo` WHERE `id`=".(int)($id))


Injection

Supposons que $_SESSION['login'] soit défini par le serveur de cette façon:
Code :
$_SESSION['login'] = 25
Ou
Code :
$req = mysqli_query(...);
$row = mysqli_fetch_array($req);
$_SESSION['login'] = (int)$row['id'];

Alors, en ce cas là, la variable $_SESSION['login'] ne pourra contenir qu'un integer. Elle sera donc fiable, et il n'y aura pas d'injection dans le code présenté.

Maintenant, supposons que le serveur définisse la variable $_SESSION['login'] comme ceci:

Code PHP :
<?php 
$_SESSION
['login'] = $_COOKIE['login'];

Ou comme cela

Code PHP :
<?php 
$_SESSION
['login'] = $_GET['login']; // $_GET ou $_POST

Alors la variable $_SESSION['login'] peut contenir n'importe quoi, y compris une chaine de texte arbitraire, fixée par le client.
En ce cas, si tu fais une concaténation:
Code PHP :
<?php 
'SELECT * FROM `table` WHERE `id`="'.$_SESSION['login'].'"'"

Alors PHP fera le remplacement de $_SESSION['login'] par son contenu, puis il enverra le résultat à mysql (via mysqli_query()).
En ce cas, danger! $_SESSION['login'] a été définie par le visiteur ($_SESSION['login'] = $_GET['login']). Du coup, la commande "SELECT..." contient un morceau de code définit par le visiteur. Cela s'appelle de l'injection: le visiteur du site peut faire exécuter la commande qu'il souhaite à mysql.
Ainsi, si l'utilisateur définit

Code PHP :
<?php 
$_GET
['login'] = '" OR "1"="1';

Oui, le pseudonyme " OR "1"="1 n'est pas très courant, mais bon...
Alors, on aura:
Code PHP :
<?php 
$_SESSION
['login'] = $_GET['login'];
// c'est à dire $$_SESSION['login'] = '" OR "1"="1'
$requete = 'SELECT * FROM `table` WHERE `id`="'.$_SESSION['login'].'"'";
// C'est à dire 'SELECT * FROM `table` WHERE `id`="" OR "
1"="1"'
mysqli_query(
$requete)
// MySQL execute la requete 'SELECT * FROM `table` WHERE `id`="" OR "
1"="1"'
// la condition "
1"="1" est toujours vraie quelque soit la ligne
// MySQL va donc renvoyer TOUTES les lignes de la table `table`
//alors que l'on voulait uniquement les lignes de l'utilisateur courant

Tout dépend donc de la fiabilité de ta variable de session. Est-ce un (int)? Ou bien est-ce une chaine qui vient du visiteur?


Transtypage
Dans tous les cas, je te recommande de toujours caster tes variables quand tu les utilises dans une requete SQL. Ainsi, si une variable est un entier, cast-la en "int" comme ceci:
Code PHP :
<?php 
$requete
= 'SELECT * FROM `table` WHERE `id`="'.(int)($_SESSION['login']).'"'";
Ainsi, quoique $_SESSION[] contienne (peu importe sa fiabilité), (int)($_SESSION['login']) sera toujours un entier. Cela te protège de l'injection grave. En revanche, cela ne garanti en rien que $_SESSION['login'] soit l'identifiant de l'utilisateur: ailleurs, dans le code, l'utilisateur a peut-être eu l'occasion d'envoyer un login qui n'est pas le sien, pour récupérer les infos d'un autre joueur. Ou encore, si $_SESSION['login'] n'est pas transtypable en un entier (par exemple, "toto" est une chaine de caractères qui n'est pas convertible en entier), alors (int)($_SESSION['login'] ) vaudra 0, et non l'identifiant du joueur.


Namespace dédié aux E/S de la BDD
Egalement, je te conseille de te créer un ensemble de fonctions dédiées à la récupération de données dans la BDD. Tu peux les regrouper dans un namespace, pour plus de clarté.

Ainsi, au lieu de ceci:
Code PHP :
<?php 
// code A
for ($requete = mysqli_query('SELECT * from `toto` WHERE `id`="'.(int)($_SESSION['login']).'"'); ($row = mysqli_fetch_array($requete)); )
{
// code B
}
// code C

Tu aurais cela
Code PHP :
<?php 
// Dans un fichier nommé, par ex, "mySQLi.php", que tu inclus dans tes pages de site
function getToto()
{
$id = (int)($_SESSION['login']);
if (!
$id > 0)
error("Identifiant invalide");
//throw new Exception("Identifiant invalide");
// Je ne sais pas si les exceptions te sont familières
// Si non, cherche dans la documentation.
// En quelques mots, elles servent à gérer les cas "d'erreur"
// Ici, un identifiant négatif est un cas d'erreur, et si cela arrive, il ne faut pas exécuter le "for"
// lancer (throw) l'exception stoppe getToto() et redonne la main à la ligne de code qui l'a appelé

$retour = array();
for (
$requete = mysqli_query('SELECT * from `toto` WHERE `id`="'.(int)($id).'"'); ($row = mysqli_fetch_array($requete)); )
{
$retour[] = Array( 'login' => $row[0], 'taillePlante' => $row[1], 'typePlante' => $row[2]);
}
return (
$return);
}

// dans le php de la page du site
// code A
foreach (getToto() as $plante)
{
// code B
}
// code C


Programmation Orientée Objet
Enfin, je te conseillerai d'utiliser l'approche OO de PHP, qui sera plus simples à manipuler (même sans en utiliser tous les ressorts). Ainsi, au lieu de te farcir un tableau associatif
Code :
$retour[] = Array( 'login' => $row[0], 'taillePlante' => $row[1], 'typePlante' => $row[2]);
Tu pourras créer un classe "Plante" avec les attributs "joueur", "taille" et "type". L'intérêt, c'est que tu pourras mettre des codes vérifiant l'intégrité ddu numéro du joueur, de la taille et du type de la plante dans le code de la classe "Plante".
Ainsi, vue de l'extérieur, tu auras ceci:

Code :
echo ($plante->getTaille());

Simple. Et vu de l'intérieur:

Code PHP :
<?php 
class Plante
{
private
$taille = 0;
//...
public function getTaille()
{
$t = (int)($this->taille);
if ( !(
$t >= 0) )
throw new
Exception("Plante invalide!");

return (
$t);
}
}

Ainsi, tu seras certain qu'un objet "plante" dont tu demandes la taille te renverras toujours une valeur valide, ou lèvera une exception (qui est traitée par un bloc "try / catch"). Tu sauveras ta BDD des hackers et tu adoreras la belle allure de ton nouveau code Smile


RE: Problême avec une boucle dans un tableau - Lindis - 28-01-2014

Xenos merci pour toutes ces précisions ainsi que les démonstrations, je vais revoir le cote de mes requêtes, car oui j'utilise une session login donc une chaine de caractêres qui est le pseudo du joueur.

Et pour ce qui est de la programmation orientée objet je compte m'y mettre pour avoir un code plus aéré et plus fiable de nos jours voyant les évolutions du langage, mais ayant commancé en simple php, j'ai un peux de mal a migrer vers la PDO Smile

soit, il le faudra. allé à bientôt.


RE: Problême avec une boucle dans un tableau - niahoo - 28-01-2014

N'écoute pas ce que dit Xenos pour les boucles for/while, il raconte n'importe quoi. Pour le reste, suis bien ses conseils.


RE: Problême avec une boucle dans un tableau - Xenos - 28-01-2014

Hum... ok, effectivement, les instructions initiales et répétées ne sont pas limitées au "for". XSL a finit par me corrompre ^^
Je corrige, merci d'avoir souligné niahoo. Smile


RE: Problême avec une boucle dans un tableau - Lindis - 28-01-2014

Confusediffle: niahoo :lol:

Bah enfaite je vais pas tarder à créer un topic pour dévoiller tout le projet en cours de Développement,
J'ai une bonne grosse base, mais je pense recruter un codeur pour m'aider à réaliser quelques fonctions que je ne saurais pas faire :langue2:

Bien que je ne sois pas mauvais dans les graphismes, j'ai encore de grosse lacunes dans le domaine php.
Mais je m'en sors Big Grin

PS: E.C.L.E.R.D à de l'allure Big Grin