Construire des requêtes en fonction de variables



Les requêtes que nous avons étudiées jusqu'ici étaient simples et effectuaient toujours la même opération. Or les choses deviennent intéressantes quand on utilise des variables de PHP dans les requêtes.


La mauvaise idée : concaténer une variable dans une requête



Prenons cette requête qui récupère la liste des jeux appartenant à Patrick :

Code : PHP 

1

2

3

<?php

$reponse = $bdd->query('SELECT nom FROM jeux_video WHERE possesseur=\'Patrick\'');

?>



Au lieu de toujours afficher les jeux de Patrick, on aimerait que cette requête soit capable de s'adapter au nom de la personne défini dans une variable, par exemple $_GET['possesseur']. Ainsi la requête pourrait s'adapter en fonction de la demande de l'utilisateur !

Nous pourrions être tentés de concaténer la variable dans la requête, comme ceci :

Code : PHP 

1

2

3

<?php

$reponse = $bdd->query('SELECT nom FROM jeux_video WHERE possesseur=\'' . $_GET['possesseur'] . '\'');

?>


Il est nécessaire d'entourer la chaîne de caractères d'apostrophes comme je vous l'ai indiqué précédemment, d'où la présence des antislashs pour insérer les apostrophes : \'



Bien que ce code fonctionne, c'est l'illustration parfaite de ce qu'il ne faut pas faire et que pourtant beaucoup de sites font encore. En effet, si la variable $_GET['possesseur'] a été modifiée par un visiteur (et nous savons à quel point il ne faut pas faire confiance à l'utilisateur !), il y a un gros risque de faille de sécurité qu'on appelle injection SQL. Un visiteur pourrait s'amuser à insérer une requête SQL au milieu de la vôtre et potentiellement lire tout le contenu de votre base de données, comme par exemple la liste des mots de passe de vos utilisateurs.

Le sujet des injections SQL est un peu complexe pour être détaillé ici. Si vous souhaitez en apprendre plus à ce sujet, je vous invite à consulter Wikipédia.



Nous allons utiliser un autre moyen plus sûr d'adapter nos requêtes en fonction de variables : les requêtes préparées.


La solution : les requêtes préparées



Le système de requêtes préparées a l'avantage d'être beaucoup plus sûr mais aussi plus rapide pour la base de données si la requête est exécutée plusieurs fois. C'est ce que je préconise d'utiliser si vous voulez adapter une requête en fonction d'une ou plusieurs variables.

Avec des marqueurs « ? »



Dans un premier temps, on va « préparer » la requête sans sa partie variable, que l'on représentera avec un marqueur sous forme de point d'interrogation :

Code : PHP 

1

2

3

<?php

$req = $bdd->prepare('SELECT nom FROM jeux_video WHERE possesseur = ?');

?>



Au lieu d'exécuter la requête avec query() comme la dernière fois, on appelle ici prepare().

La requête est alors prête, sans sa partie variable. Maintenant, nous allons exécuter la requête en appelant execute et en lui transmettant la liste des paramètres :

Code : PHP 

1

2

3

4

<?php

$req = $bdd->prepare('SELECT nom FROM jeux_video WHERE possesseur = ?');

$req->execute(array($_GET['possesseur']));

?>



La requête est alors exécutée à l'aide des paramètres que l'on a indiqués sous forme d'array.

S'il y a plusieurs marqueurs, il faut indiquer les paramètres dans le bon ordre :

Code : PHP 

1

2

3

4

<?php

$req = $bdd->prepare('SELECT nom FROM jeux_video WHERE possesseur = ? AND prix <= ?');

$req->execute(array($_GET['possesseur'], $_GET['prix_max']));

?>



Le premier point d'interrogation de la requête sera remplacé par le contenu de la variable $_GET['possesseur'], et le second par le contenu de $_GET['prix_max']. Le contenu de ces variables aura été automatiquement sécurisé pour prévenir les risques d'injection SQL.

Essayons de construire une page capable de lister les jeux appartenant à une personne et dont le prix ne dépasse pas une certaine somme :

Code : PHP 

 1

 2

 3

 4

 5

 6

 7

 8

 9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

<?php

try

{

       $bdd = new PDO('mysql:host=localhost;dbname=test', 'root', '');

}

catch(Exception $e)

{

        die('Erreur : '.$e->getMessage());

}


$req = $bdd->prepare('SELECT nom, prix FROM jeux_video WHERE possesseur = ?  AND prix <= ? ORDER BY prix');

$req->execute(array($_GET['possesseur'], $_GET['prix_max']));

          

echo '<ul>';

while ($donnees = $req->fetch())

{

       echo '<li>' . $donnees['nom'] . ' (' . $donnees['prix'] . ' EUR)</li>';

}

echo '</ul>';

              

$req->closeCursor();


?>


Bien que la requête soit « sécurisée » (ce qui élimine les risques d'injection SQL), il faudrait quand même vérifier que $_GET['prix_max'] contient bien un nombre et qu'il est compris dans un intervalle correct. Vous n'êtes donc pas dispensés d'effectuer des vérifications supplémentaires si vous estimez que cela est nécessaire.



Essayez d'appeler cette page (que l'on nommera par exemple selection_jeux.php) en modifiant les valeurs des paramètres. Vous allez voir que la liste des jeux qui ressort change en fonction des paramètres envoyés ! 

Avec des marqueurs nominatifs



Si la requête contient beaucoup de parties variables, il peut être plus pratique de nommer les marqueurs plutôt que d'utiliser des points d'interrogation.

Voici comment on s'y prendrait :

Code : PHP

1

2

3

4

<?php

$req = $bdd->prepare('SELECT nom, prix FROM jeux_video WHERE possesseur = :possesseur AND prix <= :prixmax');

$req->execute(array('possesseur' => $_GET['possesseur'], 'prixmax' => $_GET['prix_max']));

?>



Les points d'interrogation ont été remplacés par les marqueurs nominatifs :possesseur et :prixmax (ils commencent par le symbole deux-points, comme vous le voyez).

Cette fois-ci, ces marqueurs sont remplacés par les variables à l'aide d'un array associatif. Quand il y a beaucoup de paramètres, cela permet parfois d'avoir plus de clarté. De plus, contrairement aux points d'interrogation, nous ne sommes cette fois plus obligés d'envoyer les variables dans le même ordre que la requête.

Créé avec HelpNDoc Personal Edition: Élevez votre documentation vers de nouveaux sommets avec le référencement intégré de HelpNDoc