Mis à jour le jeudi 31 octobre 2013
  • Facile
Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Introduction du cours

Bonjour et bienvenue sur ce tutoriel.

Vous êtes sans doute au courant que les erreurs sont les cauchemars des développeurs PHP et/ou MySQL.
Vous en avez sûrement déjà rencontré, et ce n'est pas fini. :p

Tout au long de ce tutoriel, vous apprendrez à déceler les erreurs de base dans une requête SQL qui provoquent des erreurs PHP.

Des erreurs PHP ? Tu voulais dire SQL !

Non, non, une erreur SQL entraîne forcément par la suite une erreur PHP de la fonction qui gère le retour des données (mysql_fetch_array, mysql_fetch_assoc, mysql_result...) :

Allez, c'est parti...

On commence...

Tout au long de ce tutoriel, je vais vous expliquer ce que je vous dis à travers des exemples concrets.
Pour éviter de vous perdre, je vais garder la même structure SQL et vous pourrez ainsi suivre l'évolution de notre script.
La base de données s'appelle monsite, la table s'appelle membres et voici sa structure :

CREATE TABLE membres (
  id int(6) NOT NULL auto_increment,
  pseudo varchar(20) collate latin1_bin NOT NULL,
  motdepasse varchar(32) collate latin1_bin NOT NULL,
  datenaissance date NOT NULL,
  dateconnexion datetime NOT NULL,
  email varchar(40) collate latin1_bin NOT NULL,
  PRIMARY KEY  (id),
  UNIQUE KEY pseudo (pseudo)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_bin AUTO_INCREMENT=1 ;

Il s'agit d'une table très simplifiée pour les besoins de ce tutoriel, inutilisable en production (trop peu de champs).

Nous allons la remplir de quelques enregistrements :

INSERT INTO membres (id, pseudo, motdepasse, datenaissance, dateconnexion, email) VALUES
(1, 'S0pra', 'f71dbe52628a3f83a77ab494817525c6', '1991-06-28', '2008-04-19 08:45:37', 'tartampion@gmail.com'),
(2, 'L''alter-ego', 'ea7d0b804bf0846c549d1b166526a88c', '1985-04-08', '2008-03-25 08:46:38', 'alter_ego@yahoo.fr'),
(3, 'Martine Du 14', '2e3817293fc275dbee74bd71ce6eb056', '1956-11-20', '2008-04-13 09:09:12', 'titine_volley@hotmail.fr'),
(4, 'Sénèque', '925d7518fc597af0e43f5606f9a51512', '1981-08-17', '2008-04-17 09:10:37', 'jc.balala@orange.fr');

Toutes les informations ont été choisies aléatoirement. Si vous vous reconnaissez, ce n'est que le pur hasard. :p

Bien sûr, cette table sera « alimentée » à chaque inscription sur votre site.

Dans la prochaine partie, vous découvrirez certaines fonctionnalités « cachées » et pourtant très utiles de PHP !

On découvre des fonctionnalités méconnues...

Voilà : on a notre table, nos enregistrements, nous sommes prêts à faire un code PHP.
Si vous avez bien suivi les tutoriels de M@teo21, vous pouvez le faire et généralement, le script ressemblera plus ou moins à ceci :

<?php
mysql_connect("localhost", "root", ""); // Connexion à MySQL (identifiants par défaut de WAMP)
mysql_select_db("monsite"); // Sélection de la base monsite

// Travail sur la BDD

// On a fini de travailler, on ferme la connexion.
mysql_close(); // Déconnexion de MySQL
?>

Par la suite, nous nous occuperons de la gestion des erreurs, surtout dans la partie « Travail sur la BDD », mais découvrons ensemble quelques fonctionnalités malheureusement méconnues de PHP.

La fonction die

Quoi, déjà la fin, je vais mourir ?

Oui, die en anglais veut dire mourir, mais ne vous inquiétez pas, vous resterez en vie jusqu'à la fin de ce tutoriel. ^^ La fonction die est un alias de la fonction exit en PHP.

C'est quoi, un alias ? C'est la série ?

Non, non, quand on développe en PHP, on ne regarde pas la télévision... :-° Un alias est en quelque sorte un surnom. C'est une fonction qui a exactement les mêmes caractéristiques, paramètres... que la fonction originale, mais elle n'a pas le même nom. Voici ce qu'en dit la documentation PHP :

Citation : Documentation PHP

Ces modifications de noms ont été réalisées en général à cause d'un changement dans l'API d'origine ou pour d'autres raisons et les anciens noms sont conservés uniquement pour des raisons de compatibilité ascendante.

Un peu compliqué pour vous dire que si une fonction change de nom au cours de l'évolution de PHP, on conserve l'ancien nom pour que les anciens scripts ne partent pas à la dérive.
Vous trouverez une liste complète des alias en PHP dans la documentation.

Tout ça pour vous annoncer que die() est un alias.

Et alors ?

Voici la réponse officielle :

Citation : Documentation PHP

C'est une très mauvaise habitude d'utiliser ces alias, car ils risquent à tout moment de disparaître.

L'utilisation d'exit() la plus simple est la suivante :

<?php
// Script, partie 1
exit('Fin du Script');
// Script, partie2
?>

Avec ce code, PHP exécutera la partie 1 du script et cessera son exécution dès qu'il rencontrera exit. Il affichera alors (en effaçant ce qui avait été éventuellement affiché précédemment) la chaîne de caractères passée en paramètres. La partie 2 ne sera jamais exécutée !

Voilà pour la fonction exit() ; passons ensuite à une seconde fonctionnalité « cachée ».

Les opérateurs booléens

Vous les connaissez sûrement et vous les utilisez sans le savoir, c'est tout simplement ET, OU...

Mais ce n'est pas caché puisqu'on s'en sert dans toutes les conditions ! Tu nous prends pour des billes...

Attendez... vous allez voir qu'on peut les utiliser en dehors des conditions.
Vous verrez dans la documentation PHP que certaines fonctions renvoient FALSE en cas d'erreur, et notamment la majorité des fonctions MySQL !
Si en PHP j'écris ceci :

<?php
$var1 = 'Toto aime les frites';
$var2 = 'Toto aime les frites';
$var3 = 'Nina aime les frites';

if ($var1 == $var2 OR $var2 == $var3) {
	echo 'OK';
}
else {
	echo 'Non OK';
}
?>

vous savez bien évidemment que le script affichera OK.
Mais comme PHP est un peu fainéant, :-° puisque la première condition est vraie (dans le OR), pourquoi s'embêter à aller vérifier la deuxième ? En effet, TRUE OR FALSE et TRUE OR TRUE renverront TRUE (cf. algèbre de Boole).

Si maintenant j'écris ceci :

<?php
// Connexion à MySQL
mysql_connect('localhost', 'root', '') or exit('Erreur à la connexion à MySQL');
?>

on sait déjà que mysql_connect renvoie false au cas où la connexion échoue. Ainsi, dans ce cas-là (et uniquement dans ce cas), PHP ira exécuter la fonction qui se trouve de l'autre côté du or.

... et comment les utiliser efficacement

Si vous avez bien suivi la sous-partie précédente, vous connaissez l'utilité d'ajouter or exit('Erreur de MySQL'), comme ceci :

<?php
//Connexion à MySQL
mysql_connect('localhost', 'root', '') or exit('Erreur à la connexion à MySQL');
//Suite du script
?>

Toute la subtilité est là : si le script rencontre une erreur à la connexion, on affiche « Erreur à la connexion à MySQL » et on s'arrête là. Il est inutile de continuer, d'exécuter des requêtes si la connexion a échoué.
Le « problème », c'est que vous n'êtes pas tellement plus avancés. Vous avez localisé l'erreur mais seulement en partie.
Oui, c'est vrai, avec cet exemple, vous savez que l'erreur se situe à la connexion, mais vous ne savez pas ce qui cloche !

Et alors, déjà ce n'est pas mal, je sais que c'est là. On ne va pas s'embêter à écrire des lignes de script juste pour déceler une petite erreur !

Petite ? Oui, mais croyez-moi, il est très utile de savoir avec précision où se situe l'erreur. En plus, pas besoin de nombreuses lignes de code, une seule fonction suffit.

Quoi ? Mais c'est fou, ça !

Eh oui, magie, magie... :magicien: mysql_error()
Elle retourne le texte de la dernière erreur de MySQL.
Son utilisation est très simple, dans le cadre de l'affichage d'erreur :

<?php
// Connexion à MySQL
mysql_connect('localhost', 'root', '') or exit(mysql_error());
mysql_select_db('monsite') or exit(mysql_error());
?>

En savoir plus sur exit()

exit() et include()

Si votre script est inclus dans une page, le exit() n'effacera pas le texte précédemment inséré par les autres pages. Exemple :
index.php

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr">
	<head>
		<title>
		Titre de ma page
		</title>
		<link href="css/design.css" rel="stylesheet" title="Feuille de Style standard" type="text/css" media="screen" />
	</head>
	
	<body>
		<?php
		include ('mysql.php');
		include('accueil.php');
		?>
	</body>
</html>

mysql.php

<?php
// Connexion à MySQL
mysql_connect('localhost', 'root', '') or exit(mysql_error());
mysql_select_db('monsite') or exit(mysql_error());
?>

Au cas où une erreur se présente dans mysql.php, le HTML inséré avant l'appel de include('mysql.php') sera conservé. Le script affichera ensuite l'erreur et cessera son exécution (la page accueil.php ne sera pas incluse et </body></html> ne sera pas affiché).

Les constantes __LINE__ et __FILE__

Quésaco ? Qu'est-ce que tu vas nous apprendre, encore ?

Peut-être n'êtes-vous pas au courant mais il existe des « constantes magiques » : voir la documentation PHP à ce sujet.
Les constantes __FILE__ et __LINE__ sont sûrement les plus utilisées dans les scripts de base. Je ne sais pas pourquoi on les appelle constantes puisqu'elles varient.

Oh ! là ! là !... PHP, c'est tordu tout de même ! Tu nous dis que c'est constant, mais que ça varie !

Eh oui, elle est dure la vie ! :lol: En fait, __LINE__ contient le numéro de la ligne. Un script comme celui-ci :

<?php
echo __LINE__;
?>

affichera 2.
__FILE__ contient l'adresse absolue du fichier. Si cette constante est utilisée dans une fonction include, c'est l'adresse du fichier inclus qui est retournée.
Reprenons l'exemple précédent, avec la même page index.php et changeons un peu le fichier mysql.php :

<?php
// Connexion à MySQL
mysql_connect('localhost', 'root', '') or exit('Erreur dans le fichier '.__FILE__.'<br />Ligne '.__LINE__.'<br />MySQL dit : '.mysql_error());
mysql_select_db('monsite') or exit('Erreur dans le fichier '.__FILE__.'<br />Ligne '.__LINE__.'<br />MySQL dit : '.mysql_error());
?>

Et là, dès que vous aurez une erreur SQL, vous verrez quelque chose du style :

Citation

Erreur dans le fichier C:\wamp\www\mysql.php
Ligne 3
MySQL dit : ERREUR SQL

Note : l'erreur SQL n'est pas affichée, vous en verrez quelques-unes dans la prochaine sous-partie.

Bien sûr, pour la connexion à la base de données, étant donné que c'est souvent le même script qui la fait, ce n'est pas d'une utilité extrême.
Mais quand vous avez un site complexe, avec des requêtes SQL pour le membre qui visite, pour l'affichage des messages et des catégories dans un forum (par exemple), croyez-moi, c'est appréciable de savoir quelle requête pose problème.
Justement, la prochaine sous-partie est consacrée aux différents problèmes rencontrés.

Des erreurs... et des solutions !

Des erreurs, il en existe de toutes sortes et c'est pour cela qu'il n'est pas facile de s'y retrouver pour un débutant.
Je ne pouvais pas faire une partie contenant TOUTES les erreurs SQL. Elles ne constituent pas une quantité infinie, mais certaines ne sont que très peu rencontrées et il serait inutile de toutes les retenir. Retenez les principales pour les corriger rapidement et ne pas buter sur une simple erreur alors que des lignes de code attendent d'être écrites.

À la connexion

Cette erreur apparaît quand MySQL ne trouve pas le serveur passé en premier paramètre de la fonction mysql_connect(). Si vous êtes en local, il s'agit de localhost. Sinon, c'est votre hébergeur qui vous donne le nom de son serveur MySQL. Notez ici la faute de frappe qui entraîne cette erreur.

Cette fois-ci, le nom du serveur semble correct mais il est impossible de s'y connecter. Si vous êtes en local, vérifiez que le service « MySQL » est lancé, c'est-à-dire que l'icône de WAMP est blanche. Redémarrez WAMP pour en être sûr. Si vous êtes sur un hébergeur distant, vérifiez que l'hébergeur a ouvert votre compte et que le serveur MySQL n'a pas de problèmes ces temps-ci.

Ici, c'est le deuxième et/ou le troisième paramètre de mysql_connect qui est en cause. En local, il faut mettre root et non pas de mot de passe, par défaut. Vous avez peut-être changé ces informations et dans ce cas, vous en connaissez les valeurs. Sur un hébergeur distant, là encore, c'est lui qui vous en informe à votre inscription.

À la sélection de la base de données

Vérifiez que le nom de la base de données, passé en paramètre à mysql_select_db est correct. Notez ici la faute de frappe qui engendre cette erreur.

Lors d'une requête

C'est dans cette partie qu'elles sont les plus nombreuses. Des règles d'or régissent la création de requêtes afin d'en éliminer d'ores et déjà 80 %.

Tu nous sors encore ta baguette magique, et une fonction qui les enlève ?

Hélas, non ! Au risque de vous décevoir, cette fois-ci, c'est à vous de travailler.
On commence alors par les noms de tables et de champs.
Il ne faut pas entourer les noms de champs et de tables par des backquotes (`), sauf s'ils font partie des mots réservés de MySQL. Vous en trouverez une liste ici.
Quoi qu'il en soit, je ne vous conseille pas d'utiliser ces mots dans les noms de tables et de champs.
Si vous vous rappelez bien la structure donnée au premier paragraphe, vous ferez donc une requête du style :

<?php
mysql_query('SELECT pseudo FROM membres') or exit('Erreur SQL ligne '.__LINE__.' : '.mysql_error());
?>

Voilà pour les noms de tables et de champs. Passons aux valeurs des champs (dans un WHERE par exemple).

Chaque entrée provenant de l'utilisateur (par exemple dans un formulaire donc $_GET ou $_POST) doit passer par une fonction de sécurisation avant d'être insérée dans la base de données.

Les nombres entiers

Si le champ MySQL attend des nombres entiers (de type INT, BIGINT...) : intval() est votre ami !
Exemples tirés de la documentation :

<?php
echo intval(42);			// 42
echo intval(4.2);			// 4
echo intval('42');			// 42
echo intval('+42');			// 42
echo intval('042');			// 42
echo intval('Toto a mangé 4 fruits');	// 0	
echo intval('4Toto');			// 4
echo intval('u4u4u4o3');		// 0
?>

Vous voyez, elle retourne toujours un nombre entier et c'est ce que vous voulez.
De plus, dans une requête, ne mettez jamais de quotes (ni doubles, ni simples, ni backquotes) autour de la valeur d'un champ attendant des nombres entiers. Donc, par exemple :

<?php
mysql_query('SELECT `pseudo` FROM `membres` WHERE `id` = '.intval($_GET['id'])) or exit('Erreur SQL ligne '.__LINE__.' : '.mysql_error());
?>

Les chaînes de caractères

Si le champ est de type TEXT, VARCHAR, MySQL n'attend pas forcément une valeur numérique, mais la plupart du temps une chaîne de caractères : on sécurise avec mysql_real_escape_string().
Il faudra aussi la délimiter par des guillemets simples (simples quotes) « ' ».
En effet, sinon MySQL pourrait la confondre avec un champ. Imaginons qu'un membre s'appelle mot_passe (pas très original...) :
Une requête du type :

<?php
//$_POST['pseudo'] = 'mot_passe';
$pseudo = mysql_real_escape_string($_POST['pseudo']);
mysql_query('SELECT `email` FROM `membres` WHERE `pseudo` = '.$pseudo) or exit('Erreur SQL ligne '.__LINE__.' : '.mysql_error());
?>

Là, bien que la variable soit sécurisée, MySQL cherche les enregistrements où le champ pseudo vaut la même chose que le champ mot_passe (puisque par hasard, le champ mot_passe existe) et c'est la catastrophe.

On entoure donc de guillemets :

<?php
//$_POST['pseudo'] = 'mot_passe';
$pseudo = mysql_real_escape_string($_POST['pseudo']);
mysql_query("SELECT `email` FROM `membres` WHERE `pseudo` = '$pseudo'") or exit('Erreur SQL ligne '.__LINE__.' : '.mysql_error());
?>

Et voilà !

Outre les injections SQL que vous risquez de ne pas voir au premier coup d'œil, l'oubli de mysql_real_escape_string peut vous causer un autre souci.
Imaginons que vous cherchiez l'adresse e-mail du membre L'alter ego :

<?php
//$_POST['pseudo'] = "L'alter ego";
mysql_query("SELECT `email` FROM `membres` WHERE `pseudo` = '$pseudo'") or exit('Erreur SQL ligne '.__LINE__.' : '.mysql_error());
?>

Une belle erreur SQL !
Regardons ce que vaut la requête et la coloration qui est faite :

SELECT `email` FROM `membres` WHERE `pseudo` = 'L'alter ego'

Vous voyez ? Un carré rouge qui vous avertit qu'il y a un problème. En effet, dans une chaîne délimitée par des « ' », il y a un « ' » non échappé. Comme en PHP, cela génère une erreur. Avec mysql_real_escape_string, la requête devient :

SELECT `email` FROM `membres` WHERE `pseudo` = 'L\'alter ego'

Voilà qui est mieux, le fait qu'il y ait deux guillemets côte à côte sera interprété comme un échappement par MySQL : il n'en considèrera qu'un seul => parfait !

S'il y a une chose essentielle à se souvenir du tutoriel :

Élément

Fonction de sécurisation

Délimité par

Nom de table

Ø

Ø

Nom de champ

Ø

Ø

Nombre entier

intval()

Ø

Chaîne de caractères

mysql_real_escape_string()

'

Voilà : j'espère que ce tutoriel n'a pas été trop long, que j'ai été clair et que vous avez tout compris.

L'idée d'un tel tutoriel m'est venue quand j'ai vu sur le forum de très nombreuses questions concernant une telle erreur.
Je n'ai pas pu faire une liste exhaustive de tous les problèmes éventuellement rencontrés : si vous en avez un qui n'est pas dans la liste, faites une recherche ; puis si elle est infructueuse, postez dans le forum PHP.

Merci de m'avoir suivi, et peut-être à bientôt pour de nouvelles aventures. ;)

Je remercie les personnes qui ont commenté ce tutoriel et ont permis de le faire évoluer vers plus de précisions, notamment sur les backquotes.

Exemple de certificat de réussite
Exemple de certificat de réussite