8768 sujets

Développement web côté serveur, CMS

Bonjour,

Cette discussion fait suite à ce sujet.

Passer d'une requête simple à une requête préparée sans être spécialiste n'est pas chose facile car les changements interviennent aussi sur le code en AVAL de la requête.

Je prends l'exemple d'un petit SELECT.

Requête simple en PHP :

$connexion = mysql_connect ("localhost", "mysql_user", "mysql_password");

$requete = "SELECT champ_1 FROM base WHERE id='" . intval ($ref) . "'  LIMIT ";

$resultat = mysqli_query ($connexion, $requete);

$nbr = mysqli_num_rows ($resultat);

$retour = mysqli_fetch_assoc ($resultat);


Requête préparée (sauf erreur) :

$stmt = mysqli_prepare ($connexion, "SELECT champ_1 FROM base WHERE id = (?) LIMIT 1");

$id = intval ($ref);

mysqli_stmt_bind_param ($stmt, 'i', $id);

mysqli_stmt_execute($stmt);

$nbr = mysqli_stmt_num_rows ($stmt)

Pour commencer je ne trouve pas l'équivalent de mysqli_fetch_assoc...

Merci de votre aide.
Modifié par boteha_2 (01 Nov 2024 - 18:07)
Bonjour,

Je n'arrive pas à récupérer à la fois le nombre de lignes retournées et le tableau associatif des résultats.

D'après ce que j'ai lu, pour activer mysqli_stmt_num_rows il faut activer d'abord mysqli_stmt_store_result.

Mais une fois activé mysqli_stmt_store_result je n'arrive pas à injecter la bonne valeur dans mysqli_srmt_fetch_array car il devient impossible d'appeler mysqli_stmt_get_result.

Par ailleurs je n'ai pas compris si mysqli_stmt_fetch_array ($retour, MYSQLI_ASSOC); retourne tout le tableau (comme fait mysqli_fetch_assoc ($resultat)) ou juste la première ligne.

$requete = "SELECT cate FROM base WHERE id = ? LIMIT 1";

$stmt = mysqli_prepare ($connexion, $requete);

$id = intval ($id);

mysqli_stmt_bind_param ($stmt, 'i', $id);

mysqli_stmt_execute($stmt);

mysqli_stmt_store_result($stmt);

$nbr = mysqli_stmt_num_rows ($stmt);

// C'est bon, je récupère le bon nombre de lignes

$retour = mysqli_stmt_get_result ($stmt);

$retour = mysqli_stmt_fetch_array ($retour, MYSQLI_ASSOC);

// Là c'est erreur fatale.


Pouvez-vous m'aider ?

Merci d'avance.
Salut,

petite question ("con" qui n'aide pas mais bon) :
- Pourquoi est ce que tu utilise les fonctions "procédurales" ?

Perso je ne suis pas super fan, ça fait utiliser des noms de fonction a rallonge dans lesquelles tu dois à chaque fois passer $stmt en paramètre.
Cela me semble être un des cas où je trouve plus simple/lisible d'utiliser les fonctions "objets" ( et si tu n'as jamais vu la programmation orienté objet ça peut être l'occasion de commencer à comprendre un peu le principe sans avoir besoin de trop rentrer dans le détail).

Cela fait trois plombes que je n'ai pas fais de mysqli, mais en très gros tu vas créer un objet $stmt au départ puis tu pourras t'en servir pour appeler les différentes fonctions ("méthodes" pour utiliser le bon terme dans le cadre d'un objet). Pour accéder aux méthodes d'un objet c'est avec la notation ->. Pour illustrer : mysqli_stmt_execute($stmt); deviendra un simple $stmt->execute();

Hop petit edit pour ajouter un lien vers une page qui permet de voir les 2 notations (en haut objet et en bas procédurale) : https://www.w3schools.com/php/func_mysqli_stmt_init.asp
Modifié par Mathieuu (05 Nov 2024 - 09:30)
Bonjour Mathieuu,

Merci de ton suivi.

Je connais le style orienté objet mais je style procédural ne me dérange pas.

Merci pour ton lien, j'ai retenu deux fonctions :

 // Bind result variables
  mysqli_stmt_bind_result($stmt, $district);

  // Fetch value
  mysqli_stmt_fetch($stmt);


Mon problème est de trouver les équivalents après requêtes préparées de :

$nbr = mysqli_num_rows ($resultat);

$retour = mysqli_fetch_assoc ($resultat);
En complément du message de Mathieuu, les syntaxes procédurale et objet sont également décrites sur la doc : https://www.php.net/manual/fr/mysqli.quickstart.dual-interface.php

Un exemple simple avec mysqli :
<?php

$driver = new mysqli_driver();
$driver->report_mode = MYSQLI_REPORT_ALL;

$mysqli = new mysqli('localhost', 'root', '', 'mydb');

// fetch_assoc
$stmt = $mysqli->prepare('SELECT id, email FROM user WHERE id < ?');
$search = $_GET['search'] ?? 0;
$stmt->bind_param('i', $search);

$stmt->execute();
$result = $stmt->get_result();

while ($row = $result->fetch_assoc()) {
    echo $row['id'], ' ', $row['email'];
}

// fetch_all
$stmt = $mysqli->prepare('SELECT id, email FROM user WHERE id < ?');
$search = $_GET['search'] ?? 0;
$stmt->bind_param('i', $search);

$stmt->execute();
$result = $stmt->get_result();

$users = $result->fetch_all(MYSQLI_ASSOC);

foreach ($users as $user) {
    echo $user['id'], ' ', $user['email'];
}


Quitte à refaire ce code, tu peux aussi regarder l'API PDO à la place de mysqli :
<?php

$pdo = new PDO('mysql:host=localhost;dbname=mydb', 'root', '', [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);

// fetch
$stmt = $pdo->prepare('SELECT id, email FROM user WHERE id < :id');
$stmt->execute([':id' => $_GET['search'] ?? 0]);

while ($row = $stmt->fetch()) {
    echo $row['id'], ' ', $row['email'];
}

// fetchAll
$stmt = $pdo->prepare('SELECT id, email FROM user WHERE id < :id');
$stmt->execute([':id' => $_GET['search'] ?? 0]);
$users = $stmt->fetchAll();

foreach ($users as $user) {
    echo $user['id'], ' ', $user['email'];
}

Modifié par Pitet (06 Nov 2024 - 12:13)
Bonjour Pitet,

Merci de ton suivi.

J'ai un code existant en mysqli, je préférerais rester en mysqli, pourquoi pas style objet.

Par contre j'ai deux remarques.

$nbr = mysqli_num_rows ($resultat);
Il n'y a pas d'équivalent dans ton code.

$retour = mysqli_fetch_assoc ($resultat);
Là je récupère tout la tableau à traiter dans une boucle WHILE.

Après requête préparée il faut lancer le WHILE sans avoir le tableau.
while ($row = $result->fetch_assoc())
Pas sûr d'avoir compris ta dernière remarque.
Mon précédent extrait de code mysqli montre 2 manières de récupérer les données, soit ligne par ligne avec fetch_assoc, soit toutes les lignes avec fetch_all.

Le nombre de résultat est retourné par la méthode $result->num_rows

<?php

$driver = new mysqli_driver();
$driver->report_mode = MYSQLI_REPORT_ALL;

$mysqli = new mysqli('localhost', 'root', '', 'mydb');

$stmt = $mysqli->prepare('SELECT id, email FROM user WHERE id < ?');
$search = $_GET['search'] ?? 0;
$stmt->bind_param('i', $search);

$stmt->execute();
$result = $stmt->get_result();

while ($row = $result->fetch_assoc()) {
    echo $row['id'], ' ', $row['email'];
}

echo 'nb : ', $result->num_rows;
Bonjour,

Un intérêt des requêtes préparées est d'échapper les valeurs.

Mais avec une requête simple et des apostrophes doubles, la valeur n'est-elle pas échappée ?

'SELECT * FROM base WHERE id = "' . $id . '"';


Un inclusion SQL est-elle possible ?
Modifié par boteha_2 (09 Nov 2024 - 17:05)
Modérateur
Bonjour,

boteha_2 a écrit :
Mais avec une requête simple et des apostrophes doubles, la valeur n'est-elle pas échappée ?

'SELECT * FROM base WHERE id = "' . $id . '"';


Un inclusion SQL est-elle possible ?

Si $id provient par exemple d'un formulaire et qu'il est inséré tel quel dans la requête, oui, une injection (et non pas un inclusion) SQL est possible. Et il n'y a pas que ce cas à considérer. C'est pourquoi il est toujours préférable d'utiliser une requête préparée à l'aide des fonctions php conçues pour ça.

Amicalement,
Bonjour,

parsimonhi a écrit :
Si $id provient par exemple d'un formulaire et qu'il est inséré tel quel dans la requête, oui, une injection (et non pas un inclusion) SQL est possible. Et il n'y a pas que ce cas à considérer. C'est pourquoi il est toujours préférable d'utiliser une requête préparée à l'aide des fonctions php conçues pour ça.


D'accord, c'est noté.

J'ai enfin trouvé un code qui fonctionne pour mon besoin de requête préparée mysqli en type procédural.

Je rappelle que je dois trouver le nombre de lignes retournées et un tableau associatif de l'ensemble, soit en requête simple :

$connexion = mysqli_connect ("localhost", "mysql_user", "mysql_password");

$requete = "SELECT champ_1 FROM base WHERE id='" . intval ($ref) . "'";

$resultat = mysqli_query ($connexion, $requete);

$nbr = mysqli_num_rows ($resultat);

$retour = mysqli_fetch_assoc ($resultat);


Requête préparée :

$connexion = mysqli_connect ("localhost", "mysql_user", "mysql_password");

$requete = "SELECT champ_1 FROM base WHERE id= ? ";

$stmt = mysqli_prepare ($connexion, $requete);

$id = intval ($reftlc);

mysqli_stmt_bind_param ($stmt, 'i', $id);

mysqli_stmt_execute($stmt);

$result = mysqli_stmt_get_result ($stmt);

$retour = mysqli_fetch_all ($result, MYSQLI_ASSOC);

$nbr = count ($retour);


Le nombre de lignes a doublé mais ça fonctionne...

Si ce code peut être optimisé merci d'avance.

Avec mysqli_stmt les variables de la requête préparée sont-elles forcément des "?" ou peut-on utiliser des noms de variables comme avec PDO
Modifié par boteha_2 (10 Nov 2024 - 21:20)
Bonjour,

S'agissant d'une recherche BOOLEAN MODE.

$requete = "SELECT * FROM base WHERE MATCH($champ) AGAINST($text IN BOOLEAN MODE)";

Pour requête préparée cela fonctionne si je remplace $text par ?

// Fonctionne
$requete = "SELECT * FROM base WHERE MATCH($champ) AGAINST(? IN BOOLEAN MODE)";
$stmt = mysqli_prepare ($connexion, $requete);
mysqli_stmt_bind_param ($stmt, 's', $text);


Mais cela ne fonctionne plus si je remplace $champ par ?

// Ne fonctionne pas
$requete = "SELECT * FROM base WHERE MATCH(?) AGAINST(? IN BOOLEAN MODE)";
$stmt = mysqli_prepare ($connexion, $requete);
mysqli_stmt_bind_param ($stmt, 'ss', $champ, $text);


Avez-vous une idée ?
Je pense qu'il est difficile de réaliser une injection SQL efficace dans MATCH mais quitte à faire une requête préparée autant qu'elle soit complète.
Bonjour,

$requete = "INSERT INTO base (un, deux, trois, quatre, cinq, six, sept, huit, neuf, dix, onze) VALUES (? ? ? ? ? ? ? ? ? ? ?)";

$stmt = mysqli_prepare ($connexion, $requete);


Voyez-vous une erreur de syntaxe ?

Uncaught mysqli_sql_exception: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '"INSERT INTO...

Je ne vois rien, merci d'avance.
Modifié par boteha_2 (12 Nov 2024 - 21:16)
Les marqueurs dans une requête préparée via mysqli ne sont pas autorisés pour les noms de table ou les noms de colonne.
Si tu connais la liste des colonnes possibles pour la variables $champ, utilise une liste blanche pour contrôler les valeurs puis concaténer la variable dans la requête sql.

Il manque les virgules entre chaque marqueur dans ton dernier message.
Modifié par Pitet (13 Nov 2024 - 15:24)
Bonjour,

$requete = "INSERT INTO base (un, deux, trois) VALUES (?, ?, ?)";

$stmt = mysqli_prepare ($connexion, $requete);


Ensuite, pour lier les variables, il me semble que j'ai le choix entre deux approches.

$tab = array ($un, $deux, $trois);

mysqli_stmt_bind_param ($stmt, 'sss', $tab[0] $tab[1], $tab[2]);

// ou

foreach ($tab AS $v)
{
mysqli_stmt_bind_param ($stmt, 's', $v);
}


C'est kif_kif, non ?

Petite question subsidiaire je me demande s'il est possible d'avoir comme valeur une fonction MySQL, par exemple CURDATE().
Je ne suis pas arrivé à faire fonctionner CURDATE(), obligé de créer la date côté PHP.
Modifié par boteha_2 (15 Nov 2024 - 19:59)
Bonjour,

Juste deux mots :

1) mysqli_stmt_bind_param ()
Sauf erreur ne passe dans une boucle, il faut renseigner toutes les valeurs dans une seule ligne ::
mysqli_stmt_bind_param ($stmt, 'sss', $tab[0] $tab[1], $tab[2]);


Pas très souple quand le nombre de variables est variable pour la même requête.

2) Une fonction SQL ne doit pas être déclarée comme variable mais écrite en dur dans la requête.
Je n'ai pas testé pour INSERT, seulement pour UPDATE :

"UPDATE base SET date=curdate(), champ_1 = ?, champ_2 = ?";

Modifié par boteha_2 (17 Nov 2024 - 19:17)