8770 sujets

Développement web côté serveur, CMS

Pages :
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)
Bonjour,

J'ai pas mal avancé mais j'ai un problème avec des requêtes SELECT qui sont construites par le script PHP avec un nombre de valeurs a priori inconnu.

La clause WHERE est du genre :
WHERE point LIKE '%mad%' AND (dim LIKE '17.26752113%' OR  dim LIKE '1017.26752113%') AND (fab=renaulr' OR fab='peugeot')


mysqli_stmt_bind_param n'acceptant pas un array en argument 2 cela devient très compliqué.

Je me dis qu'il est possible de partir de la requête simple telle qu'elle est produite par le script et la transformer en requête préparée.

Les marqueurs sont les apostrophes simples qui encadrent les valeurs.

Mais je ne vois pas de fonction PHP pour récupérer le contenu à l’intérieur d'une paire de marqueurs.

Dans l'idéal :

function Explode_par_paire_de_marqueur (', $str)

// Retourne :
var_1 =  '%mad%';
ver-2 = '17.26752113%':
var_3 = '1017.26752113%';
var-4 = 'renault':
var_5 = 'peugeot';


Avec ça je pourrais me débrouiller.

Avez-vous une piste pour function Explode_par_paire_de_marqueur (', $str) ?
Modifié par boteha_2 (27 Nov 2024 - 21:19)
Bonjour,

1) Progressant dans les requêtes préparées je commence par corriger une affirmation fausse :

boteha_2 a écrit :
mysqli_stmt_bind_param n'acceptant pas un array en argument 2 cela devient très compliqué.


J'ai découvert que depuis PHP 7.4 il existe pour les tableaux un opérateur dit de "déballage" : ...

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

mysqli_stmt_bind_param ($stmt, 'sss', ...$tab);


Le problème des tableaux de longueur inconnue est réglé !

Avant cet opérateur c'était assez galère car dans mysqli_stmt_bind_param les variables doivent être passées par référence et les solutions proposées sur PHP.net sont assez tordues.

2) col LIKE '%15'
Pour la requête : LIKE ?
Pour la valeur : '%15'
Pour le type : s
Pas logique si la col est de type INT mais seule façon de faire marcher.

3)
boteha_2 a écrit :
Mais je ne vois pas de fonction PHP pour récupérer le contenu à l’intérieur d'une paire de marqueurs


J'ai développé une fonction qui fonctionne bien sauf quand il y a des espaces blancs dans les valeurs.

$text = WHERE col_1='Charles' OR col_1='Pierre Henri';

Je commande par un explode (' ', $text);
Puis dans une boucle je récupère ce qui est ente apostrophe simples.
J'ai un problème avec Pierre Henri, j'imagine des solutions compliquées.

Voyez vous une façon simple de demander un explode sur ' ' mais pas si les ' ' sont entre =' et ' ?
boteha_2 a écrit :
Bonjour,

3)


J'ai développé une fonction qui fonctionne bien sauf quand il y a des espaces blancs dans les valeurs.

$text = WHERE col_1='Charles' OR col_1='Pierre Henri';

Je commande par un explode (' ', $text);
Puis dans une boucle je récupère ce qui est ente apostrophe simples.
J'ai un problème avec Pierre Henri, j'imagine des solutions compliquées.

Voyez vous une façon simple de demander un explode sur ' ' mais pas si les ' ' sont entre =' et ' ?


Salut

Explode est trop basique. Il te faut utiliser des expressions régulières. Voici un exemple :

(Pas ouf le nom de la fonction, à changer)
function extraireValeursEntreApostrophes($texte) {
    $pattern = "/='([^']+)'/";
    preg_match_all($pattern, $texte, $matches);
    return $matches[1]; // dans ton exemple, ça return Array ( [0] => Charles [1] => Pierre Henri )
}

Modifié par JENCAL (03 Dec 2024 - 14:11)