Outils pour utilisateurs

Outils du site


php:ptl

Lire des données

Retour d'un niveau
Retour à l'accueil


Pour pouvoir travailler avec la base de données en PHP, il faut d'abord s'y connecter.

Nous allons apprendre dans ce chapitre à lire des données dans une BDD (base de données).
Or, PHP doit faire l'intermédiaire entre vous et MySQL. Problème : PHP ne peut pas dire à MySQL dès le début « Récupère-moi ces valeurs ». En effet, MySQL demande d'abord un nom d'utilisateur et un mot de passe. S'il ne le faisait pas, tout le monde pourrait accéder à votre BDD et lire les informations (parfois confidentielles !) qu'elle contient.

Il va donc falloir que PHP s'authentifie : on dit qu'il établit une connexion avec MySQL. Une fois que la connexion sera établie, vous pourrez faire toutes les opérations que vous voudrez sur votre base de données !

1. Se connecter à la base de données en PHP


Comment se connecter ?

PHP propose 3 extensions :

  • l’extension mysql_ : c'est une série de fonctions qui permettent d'accéder à la base de données MySQL et de communiquer avec MySQL. C'est fonctions sont dépassées, “deprecated” donc obsolètes.
  • l'extension msqli_ : ce sont des fonctions améliorées d'accès à MySQL. Elles proposent plus de fonctionnalités et sont plus à jour.
  • l'extension PDO : c'est un outil complet qui permet d'accéder à n'importe quel type de base de données, que ce soit, MySQL, PostgreSQL ou Oracle.

On va plutôt utiliser la méthode d'accès PDO qui est permet de se connecter à n'importe quel autre type de base de données et qui va devenir la plus utilisée dans les prochaines versions de PHP.

Activer PDO

Normalement PDO est activé par défaut.

Le fichier de configuration de PHP est php.ini et il se retrouve aussi bien sur Mac, Windows, Linux et BSD.

  • WAMP sous Windows : allez dans le menu PHP / Extensions PHP et vérifiez que php_pdo_mysql est bien coché
  • MAMP sous Mac : allez dans /Applications/MAMP/conf/PHP5XXX/php.ini, recherchez la ligne qui contient pdo_mysql, enlevez le point-virgule devant s'il y en a un pour activer l'extension pour avoir extension=php_pdo_mysql.dll
  • XAMPP sous Linux : recherchez la ligne qui commence par pdo_mysql.default_socket et complétez-la comme ceci, pdo_mysql.default_socket = /opt/lampp/var/mysql/mysql.sock

Se connecter à MySQL avec PDO

Il faut renseigner 4 informations à PDO pour se connecter :

  1. le nom de l'hôte : le nom du serveur, son adresse ip, ou localhost (en local)
  2. la base de données : c'est le nom de la DB ou on veut se connecter, exemple test
  3. le login : c'est le login de l'utilisateur qui à les droits de s'y connecter
  4. le mot de passe : ;-)

Exemple :

<?php
// Sous WAMP (Windows)
$bdd = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
 
// Sous MAMP (Mac)
$bdd = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', 'root');
?>

PDO est ce que l'on appel une extension orientée objet. La ligne de code que l'on viens de voir crée ce que l'on appelle un objet $bdd. C'est un objet qui représente la connexion à la base de données.

On crée la connexion en indiquant dans l'ordre les paramètres:

  1. le nom d'hôte içi (localhost);
  2. la base de données (test);
  3. le login (root);
  4. le mot de passe (sous WAMP pas de passe donc chaîne vine, sous MAMP = root)

Le premier paramètre (qui commence par mysql) s'appelle le DSN : Data Source Name.
C'est généralement le seul qui change en fonction du type de base de données auquel on se connecte.

Tester la présence d'erreurs avec try et catch

Si on à bien renseigner tout ce qu'il faut, on devrai voir apparaitre une page blanche. :-)

Mais en cas d'erreur, PHP risque d'afficher toute la ligne qui pose problème dévoilant par la même occasion tout les paramètres de connexions de l'objet $bdd contenant les renseignement de l'objet PDO (serveur, base de données, login et passe) ! :-(

Pour y remédier on va donc mettre notre objet $bdd dans une sorte de condition try{} et catch{} ce qui va avoir pour effet de traiter l'erreur.
En cas d'erreur, PDO renverra ce que l'on appel une exception qui permettra de capturer l'erreur.

<?php
try
{
$bdd = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
}
catch (Exception $e)
{
die('Erreur : ' . $e->getMessage());
}
?>

Que font exactement try et catch ?

En cas d'erreur :-(
PHP essaie d'exécuter les instructions à l'intérieur du bloc try, si il y a une erreur, il rentrera directement dans le bloc catch et fera ce qu'on lui demande de faire dans ce bloc.
Ici on arrête l’exécution de la page en affichant une message décrivant l'erreur.

Si tout se passe bien :-)
PHP poursuit l'exécution du code et ne lit pas ce qu'il y a dans le bloc catch, la page PHP ne devrait donc rien afficher.

2. Récupérer les données


Exemple d'une table ayant le nom jeux_video

Pour récupérer les données nous avons besoin de requête que nous allons préparer à l'avance.

Faire une requête

C'est ici que l'on va parler à MySQL, par le biais d'une requête “query” en langage SQL. :-)
On va demander à MySQL de nous dire tout ce que contient la table jeux_video

Pour cela on besoin de notre objet représentant la connexion à la base de données, donc $bdd

$reponse = $bdd->query('Tapez votre requête SQL ici');
  1. on demande ainsi à effectuer une requête sur la base de données. Ici ce sera un SELECT.
  2. on récupère ce que la base de données nous renvoie dans un autre objet $reponse

La première requête

SELECT * FROM jeux_video

Prend tout ce qu'il y a dans la table jeux_video et affiche le moi !

  • SELECT : indique quel type d'opération doit effectuer MySQL, mot clé demandant à MySQL d'afficher ce que contient la table
  • * : indique de sélectionner tout les champs de la table
  • FROM : c'est un mot de liaison qui se traduit par « dans ». FROM fait la liaison entre le nom des champs et le nom de la table.
  • jeux_video : c'est le nom de la table dans laquelle il faut aller piocher

Ce qui donne :

<?php
$reponse = $bdd->query('SELECT * FROM jeux_video');
?>

$reponse contient maintenant la réponse de MySQL

Afficher le résultat de la requête avec fetch

$reponse contient des données qui sont inexploitable car MySQL à renvoyé beaucoup d'information qu'il va falloir organiser.

Si on a une table de 10 camps avec 200 entrées on se retrouve donc avec 2000 informations.

Pour ne pas tout traiter d'un coup, on va donc extraire ligne par ligne, c'est à dire ligne par ligne.

<?php
$donnees = $reponse->fetch();
?>

Il va donc falloir ressortir les données avec la méthode fetch() (va chercher) qui va nous renvoyer à la première ligne, c'est à dire au premier résultat de ce que contient l'objet $reponse pour le placer dans l'array $donnees

Va chercher la ligne dans l'objet $reponse et met le dans l'array $donnees ;-)

$donnees est un array qui contiendra champ par champ les valeurs de la première entrée.
Donc si on s'intéresse au champ console il faudra utiliser la syntaxe suivante $donnees['console']

Pour parcourir les données une à une il faudra utiliser une boucle.
Chaque fois que l'on appellera $reponse→fetch() , on passera à l'entrée suivante, donc la boucle sera répétée autant de fois qu'il y aura d’entrées dans la table.

<?php
try
{
	// On se connecte à MySQL
	$bdd = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
}
catch(Exception $e)
{
	// En cas d'erreur, on affiche un message et on arrête tout
        die('Erreur : '.$e->getMessage());
}
// Si tout va bien, on peut continuer
// On récupère tout le contenu de la table jeux_video
$reponse = $bdd->query('SELECT * FROM jeux_video');
// On affiche chaque entrée une à une
while ($donnees = $reponse->fetch())
{
?>
    <p>
    <strong>Jeu</strong> : <?php echo $donnees['nom']; ?><br />
    Le possesseur de ce jeu est : <?php echo $donnees['possesseur']; ?>, et il le vend à <?php echo $donnees['prix']; ?> euros !<br />
    Ce jeu fonctionne sur <?php echo $donnees['console']; ?> et on peut y jouer à <?php echo $donnees['nbre_joueurs_max']; ?> au maximum<br />
    <?php echo $donnees['possesseur']; ?> a laissé ces commentaires sur <?php echo $donnees['nom']; ?> : <em><?php echo $donnees['commentaires']; ?></em>
   </p>
<?php
}
$reponse->closeCursor(); // Termine le traitement de la requête
?>

Explications :

  • on fait une boucle pour chaque entrée de la table en commencant par l'entrée n°1, n°2, etc ….
  • $reponse contient toute la réponse de MySQL en vrac mais sous forme d'objet
  • $données est un array renvoyé par le fetch(), chaque fois que l'on fait une boucle, fetch() va chercher l'entrée suivante dans $reponse et organise les champs dans l'array $données
  • while ($donnees = $reponse→fetch())… (elle fait 2 choses à la fois)
    • elle récupère une nouvelle entrée et place son contenu dans $donnees ;
    • elle vérifie si $donnees vaut vrai ou faux.
    • le fetch renvoie faux (false) dans $donnees lorsqu'il est arrivé à la fin des données, c'est-à-dire que toutes les entrées ont été passées en revue. Dans ce cas, la condition du while vaut faux et la boucle s'arrête.
  • <?php $reponse→closeCursor(); ?> va provoquer la « fermeture du curseur d'analyse des résultats » cela signifie qu'il faut effectuer cet appel à closeCursor() chaque fois que l'on a fini de traiter le retour d'une requête, afin d'éviter d'avoir des problèmes à la requête suivante. Cela signifie qu'on a terminé le travail sur la requête.


Afficher seulement le contenu de quelques champs

Si je veux juste lister les noms des jeux, j'aurais utilisé la requête SQL suivante :

SELECT nom FROM jeux_video
<?php
try
{   // On se connecte à MySQL
    $bdd = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
}
catch(Exception $e)
{       // En cas d'erreur, on affiche un message et on arrête tout
        die('Erreur : '.$e->getMessage());
}
 
// On récupère tout le contenu de la table jeux_video
$reponse = $bdd->query('SELECT nom FROM jeux_video');
 
// On affiche chaque entrée une à une
while ($donnees = $reponse->fetch())
{
    echo $donnees['nom'] . '<br />';
}
 
$reponse->closeCursor();// Termine le traitement de la requête
?>

Important :

  • la connexion à la base de données n'a besoin d'être faite qu'une seule fois, au début de la page ;
  • il faut fermer les résultats de recherche avec closeCursor() après avoir traité chaque requête.


3. Les critères de sélection


Les mots-clés suivants du langage SQL :

  • WHERE;
  • ORDER BY;
  • LIMIT;

WHERE

Grâce au mot-clé WHERE, vous allez pouvoir trier vos données.

SELECT * FROM jeux_video WHERE possesseur='Patrick'

Traduction : « Sélectionner tous les champs de la table jeux_video lorsque le champ possesseur est égal à Patrick ».

<?php
try
}   // On se connecte à MySQL
    $bdd = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
}
catch(Exception $e)
{       // En cas d'erreur, on affiche un message et on arrête tout
        die('Erreur : '.$e->getMessage());
}
// Si tout va bien, on peut continuer
 
// On récupère tout le contenu de la table jeux_video
$reponse = $bdd->query('SELECT nom, possesseur FROM jeux_video WHERE possesseur=\'Patrick\'');
 
// On affiche chaque entrée une à une
while ($donnees = $reponse->fetch())
{
    echo $donnees['nom'] . ' appartient à ' . $donnees['possesseur'] . '<br />';
}
$reponse->closeCursor(); // Termine le traitement de la requête
?>

Il est par ailleurs possible de combiner plusieurs conditions. Par exemple, si je veux lister les jeux de Patrick qu'il vend à moins de 20 euros, je combinerai les critères de sélection à l'aide du mot-clé AND (qui signifie « et ») :

SELECT * FROM jeux_video WHERE possesseur='Patrick' AND prix < 20

Traduction : « Sélectionner tous les champs de jeux_video lorsque le possesseur est Patrick ET lorsque le prix est inférieur à 20 ».

Il existe aussi le mot-clé OR qui signifie « ou ».


ORDER BY

ORDER BY nous permet d'ordonner nos résultats. Nous pourrions ainsi classer les résultats en fonction de leur prix ! La requête SQL serait :

SELECT * FROM jeux_video ORDER BY prix

Traduction : « Sélectionner tous les champs de jeux_video et ordonner les résultats par prix croissants ».

<?php
try
{   // On se connecte à MySQL
    $bdd = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
}
catch(Exception $e)
{       // En cas d'erreur, on affiche un message et on arrête tout
        die('Erreur : '.$e->getMessage());
}
 
// Si tout va bien, on peut continuer
 
// On récupère tout le contenu de la table jeux_video
$reponse = $bdd->query('SELECT nom, prix FROM jeux_video ORDER BY prix');
 
while ($donnees = $reponse->fetch())
{
    echo $donnees['nom'] . ' coûte ' . $donnees['prix'] . ' EUR<br />';
}
$reponse->closeCursor(); // Termine le traitement de la requête
?>

Pour classer par ordre décroissant, il suffit de rajouter le mot-clé DESC à la fin :

SELECT * FROM jeux_video ORDER BY prix DESC

Traduction : « Sélectionner tous les champs de jeux_video, et ordonner les résultats par prix décroissants ».

Si on avait utilisé ORDER BY sur un champ contenant du texte, le classement aurait été fait par ordre alphabétique.


LIMIT

LIMIT nous permet de ne sélectionner qu'une partie des résultats (par exemple les 20 premiers).
C'est très utile lorsqu'il y a beaucoup de résultats et que vous souhaitez les paginer (c'est-à-dire par exemple afficher les 30 premiers résultats sur la page 1, les 30 suivants sur la page 2, etc).

À la fin de la requête, il faut ajouter le mot-clé LIMIT suivi de deux nombres séparés par une virgule.
Par exemple :

SELECT * FROM jeux_video LIMIT 0, 20

Ces deux nombres ont un sens bien précis.

  • On indique tout d'abord à partir de quelle entrée on commence à lire la table. Ici, j'ai mis « 0 », ce qui correspond à la première entrée. Attention, cela n'a rien à voir avec le champ ID ! Imaginez qu'une requête retourne 100 résultats : LIMIT tronquera à partir du premier résultat si vous indiquez 0, à partir du 21ème si vous indiquez 20, etc.
  • Ensuite, le deuxième nombre indique combien d'entrées on doit sélectionner. Ici, j'ai mis « 20 », on prendra donc vingt entrées.

Quelques exemples :

  • LIMIT 0, 20 : affiche les vingt premières entrées ;
  • LIMIT 5, 10 : affiche de la sixième à la quinzième entrée ;
  • LIMIT 10, 2 : affiche la onzième et la douzième entrée.
<?php
try
{       // On se connecte à MySQL
	$bdd = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
}
catch(Exception $e)
{       // En cas d'erreur, on affiche un message et on arrête tout
        die('Erreur : '.$e->getMessage());
}
// Si tout va bien, on peut continuer
 
// On récupère tout le contenu de la table jeux_video
$reponse = $bdd->query('SELECT nom FROM jeux_video LIMIT 0, 10');
 
echo '<p>Voici les 10 premières entrées de la table jeux_video :</p>';
while ($donnees = $reponse->fetch())
{
	echo $donnees['nom'] . '<br />';
}
$reponse->closeCursor(); // Termine le traitement de la requête
?>

Il faut utiliser les mots-clés dans l'ordre que donné : WHERE puis ORDER BY puis LIMIT, sinon MySQL ne comprendra pas votre requête.

SELECT nom, possesseur, console, prix FROM jeux_video WHERE console='Xbox' OR console='PS2' ORDER BY prix DESC LIMIT 0,10

4. Construire des requêtes en fonction de variables (à ne jamais faire)


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

Exemple, une requête qui récupère la liste des jeux appartenant à Patrick :

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

mais au lieu d'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 !

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

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

Cela va fonctionner mais c'est l'illustration de ce qu'il ne faut jamais faire !!

Pourquoi ??
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.

Il a un autre moyen beaucoup plus sûr !

La solution : les requêtes préparées (prepare)

L'avantages de requêtes préparées est 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 type de requête qu'il faut utiliser si on veut adapter une requête en fonction d'une ou plusieurs variables.

Avec des marqueurs « ? »
On va préparer la requête sans sa partie variable que l'on représentera sous forme de point d’interrogation.

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

Au lieu de 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 :

<?php  // On se connecte à MySQL
$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 :

<?php  // On se connecte à MySQL
$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 :

<?php
try
{   // On se connecte à MySQL
    $bdd = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
}
catch(Exception $e)
{       // En cas d'erreur, on affiche un message et on arrête tout
        die('Erreur : '.$e->getMessage());
}
// Si tout va bien, on peut continuer
// On récupère tout le contenu de la table jeux_video
$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>';
// On affiche chaque entrée une à une
while ($donnees = $req->fetch())
 
{
    echo '<li>' . $donnees['nom'] . ' (' . $donnees['prix'] . ' EUR)</li>';
}
echo '</ul>';
$req->closeCursor(); // Termine le traitement de la requête
?>

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.


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 :

<?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.

5. Traquer les erreurs


Lorsqu'une requête SQL « plante », bien souvent PHP vous dira qu'il y a eu une erreur à la ligne du fetch() : Fatal error: Call to a member function fetch() on a non-object in C:\wamp\www\tests\index.php on line 13

Ce n'est pas très précis, je pense que vous êtes d'accord avec moi.
Ce n'est pas la ligne du fetch() qui est en cause : c'est souvent vous qui avez mal écrit votre requête SQL quelques lignes plus haut.

Pour afficher des détails sur l'erreur, il faut activer les erreurs lors de la connexion à la base de données via PDO.

Vous vous souvenez de cette ligne ?

<?php  // On se connecte à MySQL
       $bdd = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
?>
</php>

Ajoutez-y un paramètre à la fin pour activer les erreurs :

<?php
$bdd = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '', array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
?>
</php>

Désormais, toutes vos requêtes SQL qui comportent des erreurs les afficheront avec un message beaucoup plus clair.

Supposons par exemple que j'écrive mal le nom du champ :

<?php
$reponse = $bdd->query('SELECT champinconnu FROM jeux_video');
?>
</php>

L'erreur suivante s'affichera alors :
Unknown column 'champinconnu' in 'field list'

C'est de l'anglais, certes, mais c'est déjà beaucoup plus précis que l'erreur que l'on avait tout à l'heure. Si on traduit, cela signifie : « La colonne champinconnu est introuvable dans la liste des champs ».
En effet, il n'y a aucun champ qui s'appelle champinconnu.

Lorsque vous avez un problème avec une requête et que vous voulez demander de l'aide sur les forums du Site du Zéro, pensez toujours à activer les erreurs lors de la connexion à la base de données comme je viens de vous montrer, cela vous permettra d'avoir un message d'erreur détaillé. N'oubliez pas que personne ne peut vous aider si vous donnez juste le message par défaut Call to a member function fetch() on a non-object !

En résumé

  • Pour dialoguer avec MySQL depuis PHP, on fait appel à l'extension PDO de PHP.
  • Avant de dialoguer avec MySQL, il faut s'y connecter. On a besoin de l'adresse IP de la machine où se trouve MySQL, du nom de la base de données ainsi que d'un login et d'un mot de passe.
  • Les requêtes SQL commençant par SELECT permettent de récupérer des informations dans une base de données.
  • Il faut faire une boucle en PHP pour récupérer ligne par ligne les données renvoyées par MySQL.
  • Le langage SQL propose de nombreux outils pour préciser nos requêtes, à l'aide notamment des mots-clés WHERE (filtre), ORDER BY (tri) et LIMIT (limitation du nombre de résultats).
  • Pour construire une requête en fonction de la valeur d'une variable, on passe par un système de requête préparée qui permet d'éviter les dangereuses failles d'injection SQL.

Retour d'un niveau
Retour à l'accueil

php/ptl.txt · Dernière modification : de 127.0.0.1