Utiliser MySQL avec PHP

Intérêt de MySQL ?

MySQL est un SGBDR, c.à.d. qu'il contient des tables, lesquelles ont des attributs et une suite d'enregistrements. L'intérêt d'utiliser une base de données se calcule ainsi :

  • Le gain de temps vis-à-vis d'une autre méthode (l'écriture dans un fichier pour PHP) ;
  • Le prix (MySQL existe en version gratuite chez tous les hébergeurs qui le supportent) ;
  • Le gain de puissance, 1000 interrogations + 1000 écritures dans MySQL sont plus rapides et bien moins gourmandes en ressources CPU et mémoire que 1000 lectures de fichiers, puis 1000 modifications et réécritures de fichiers.

Une base de données, c'est un ensemble de données brutes, sans mise en forme, mais enregistrées de manière ordonnée. L'intérêt d'utiliser MySQL dans un site web est celui de pouvoir gérer des données en masse (suivi d'un stock, messages d'un forum ...) et d'effectuer des opérations dessus (commander au fournisseur tous les articles ayant un nombre restant en stock < 50 par exemple).

Connexion MySQL

La connexion MySQL via PHP se fait en 2 étapes. La première étape est la connexion au serveur MySQL avec un identifiant et son mot de passe, et la seconde est le choix d'une base de données de travail. Un exemple de connexion est montré par le script connexion.php ci-dessous :

<?php
	// On définit les 4 variables nécessaires à la connexion MySQL :
	$hostname = "localhost";
	$port = 3306;
	$user     = "user_mysql";
	$password = "password_mysql";
	$nom_base_donnees = "newslettux";


	// VIEILLE METHODE (sans PDO)
	// A ne plus utiliser normalement ...
	// compatible PHP <= 5.1
	//		$conn = mysql_connect($hostname, $user, $password) or die(mysql_error());

	//		// Choix de la base sur laquelle travailler
	//		mysql_select_db($nom_base_donnees, $conn);

	//		// pour ceux qui ont l'extension mysqli_* on peut remplacer tous les mysql_* par mysqli_*
	//



	// NOUVELLE METHODE (depuis PHP 5.1 et avec PDO)
	// depuis PHP 5.1 et a fortiori en PHP 7, on doit utiliser la classe "PDO" (PHP Data Object, un objet de connexion)
	// connexion au serveur MySQL avec PDO (la forme plus récente, et plus sécurisée : NOUVELLE METHODE
	try
	{
		$connexion = new PDO('mysql:host='.$hostname.';port='.$dbport.';dbname='.$nom_base_donnees, $user, $password,array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));
		$connexion->SetAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
	}
	catch(Exception $e)
	{
		echo 'Erreur : '.$e->getMessage().'<br />';
		echo 'N° : '.$e->getCode();
		exit();
	}

?>

En faisant un include('connexion.php'); dans vos pages PHP, vous aurez ainsi la connexion à MySQL.

ATTENTION ! il est déconseillé, comme le font les "programmeurs du dimanche", de mettre ces identifiants dans chaque fichier qui en a besoin, ceci pour 2 raisons :

  • D'abord parce qu'un programmeur travaille correctement ;-)
  • Ensuite parce que vous pouvez être amené à changer de serveur (changement d'hébergement, par exemple) et vous n'aurez qu'un seul fichier à retoucher, au lieu de reprendre toutes vos pages une à une;
  • Enfin parce qu'il vaut mieux, question sécurité, placer ce connexion.php seul dans un dossier, protégé par un htaccess : si on vous pirate votre site et que ce dossier est bien protégé, les pirates n'auront pas accès à votre base de données...

Oui, ça fait 3 raisons, donc ? Oui, et raison de plus !

La connexion MySQL se termine par mysql_close(); ou, via PDO par un $stmt->closeCursor();.

En "vieille" méthode mysql, il y avait la possibilité d'établir une connexion permanente (pconnect) : ceci n'est pas accepté par tous les hébergeurs. Cela sert à éviter d'ouvrir une nouvelle connexion à chaque appel de ce fichier. Attention, si l'instruction mysql_close() est trouvée, vous ne pourrez plus effectuer de requête après, à moins de réouvrir une connexion.

Lecture de données

Pour enregistrer, lire, modifier ou supprimer des données, il faut au préalable établir une connexion MySQL comme indiqué plus haut via un fichier ressemblant à connexion.php.

Dataset, datarow

Derrière ces 2 termes anglais se cachent des concepts. Le "dataset" se traduit en français par "jeu de données", c'est la réponse directe de MySQL à une requête. Le dataset contient 0, 1, 2 ... n enregistrements. Chaque enregistrement est un "datarow", datarow étant la ligne de données. En réalité, lorsqu'on interroge MySQL, il retourne un bloc de réponses (dataset) que l'on va éplucher avec php (datarow par datarow)

Pour mes exemples ci-après, je suppose cette table MySQL nommée 'pieces' décrite ci-après. Elle contient 5 colonnes, respectivement l'identifiant, la référence du produit, la désignation, le fabricant et la quantité en stock :

id_piece     reference     designation     fabricant     stock
--------     ---------     -----------     ---------     -----
10           PN-001        pneu            K-Outchou     25
12           VS-008        vis             Vis Corp.     1500
15           EJV-405       enjoliveur      Voiture Inc.  7
250          PLQ-031       plaque type 31  Placo et Cie  2
251          PH-100        phare 100W      Lumen         1

Les requêtes des exemples suivants

Reportez-vous aux articles SQL pour plus d'informations, on imagine ici 2 requêtes. Ces deux requêtes vont me permettre d'illustrer 2 exemples très courants de la lecture de données MySQL.

  • [Requête 1] => $req_piece_infos = "SELECT reference, designation, fabricant FROM pieces WHERE id_piece='15';";
  • [Requête 2] => $req_stock_commande = "SELECT reference FROM pieces WHERE stock < 10;";

Requête 1

Elle extrait de la base des infos sur une pièce. Le dataset contient 1 seule ligne :

id_piece     reference     designation     fabricant     stock
--------     ---------     -----------     ---------     -----
15           EJV-405       enjoliveur      Voiture Inc.  7

Pour exploiter via PHP cette ligne, on utilise la commande FETCH_ASSOC : cette commande crée un tableau associatif où chaque clé correspond à chaque colonne de la table, et chaque valeur à chaque valeur lue du dataset :

<?php
	// on ouvre la connexion MySQL
	include('connexion.php');

	$id_piece = 15; // par exemple variable passée par URL

	// on fait les opérations nécessaires
	$req_piece_infos = 'SELECT reference, designation, fabricant FROM pieces WHERE id_piece=:id_piece;';

	$requete_prepare_1 = $connexion->prepare($req_piece_infos);

	// il y a un marqueur dans la requête (:id_piece),
	// le tableau ci-dessous liste tous les marqueurs et leur valeur à insérer
	$array_MarqueursValeurs = array( ':id_piece' => $id_piece );

	// on exécute maintenant la requête
	$requete_prepare_1->execute($array_MarqueursValeurs);

	// on récupère le résultat en tableau PHP
	$dataset_piece_infos=$requete_prepare_1->fetch(PDO::FETCH_ASSOC);
	$nb_lignes = $requete_prepare_1->rowCount();

	print_r($dataset_piece_infos);
?>

ce print_r va afficher dans le code source :

Array(
	[id_piece] => 15
	[reference] => EJV-405
	[designation] => enjoliveur
	[fabricant] => Voiture Inc.
	[stock] => 7
)

Attention : les clés du tableau sont sensibles à la casse, si la colonne MySQL s'appelle Fabricant ou fabricant, ce n'est pas la même chose !

Il ne reste plus qu'à afficher en PHP les infos voulues (exemple de code html dans une page nommée pieces.php) :

<ul>
	<li>Pièce : <?php echo $piece['designation']; ?></li>
	<li>Fabricant : <?php echo $piece['fabricant']; ?></li>
	<li>Quantité en stock : <?php echo $piece['stock']; ?></li>
</ul>

Seul souci : si le dataset a plus d'une ligne (ici il n'y a que la pièce d'ID 15 qui est retournée par MySQL), que se passe-t-il ?

Requête 2

Lorsqu'elle sera exécutée, MySQL va retourner 3 lignes de la table. Il faudra donc éplucher les lignes du dataset : si on applique un FETCH_ASSOC directement, le tableau $piece ne contiendra que la dernière ligne du dataset ! (En réalité, PHP va éplucher le dataset, mais va, à chaque nouvelle ligne (datarow) lue, écraser la précédente, ce qui fait que seule la dernière ligne survit ...)

La solution : boucler la lecture. Lire "tant que la fin n'est pas atteinte". voici en code la solution :

<?php
	// on ouvre la connexion MySQL
	include('connexion.php');

	// on fait les opérations nécessaires
	$req_piece_infos = 'SELECT id, reference, stock FROM pieces WHERE stock < :valeur_seuil;';

	$requete_prepare_1 = $connexion->prepare($req_piece_infos);

	// il y a un marqueur dans la requête (:valeur_seuil),
	// le tableau ci-dessous liste tous les marqueurs et leur valeur à insérer
	$array_MarqueursValeurs = array( ':valeur_seuil' => 10 );

	// on exécute maintenant la requête
	$requete_prepare_1->execute($array_MarqueursValeurs);

	// on récupère le résultat en tableau PHP
	$array_ListeDesPieces = array(); // le tableau final où seront stockés les résultats de MySQL


	// Maintenant on affiche les valeurs récupérées :
?>
	<p>Pièces à commander :</p>
	<ul>

<?php
	while($piece = $requete_prepare_1->fetch(PDO::FETCH_ASSOC))
	{
		echo '	<li>Référence : '.$piece['reference'].' (reste en stock : '.$piece['stock'].')</li>';
	}
?>
	</ul>

Ceci va afficher :

<p>Pièces à commander :</p>
<ul>
	<li>Référence : EJV-405 (reste en stock : 7)</li>
	<li>Référence : PLQ-031 (reste en stock : 2)</li>
	<li>Référence : PH-100 (reste en stock : 1)</li>
</ul>

Vous savez lire des informations d'une base MySQL ! Pour toutes les opérations de tris, reportez-vous aux articles SQL : les tris ne sont que des requêtes SQL qui renvoient un dataset que vous savez désormais lire !

Manipulation de données

Manipuler des données, c'est les lire (nous venons de le voir), les mettre à jour, en insérer ou en supprimer. La manipulation est toujours la même :

  • Vérifier qu'une connexion MySQL est ouverte, en ouvrir une si besoin ;
  • Préparer la requête ;
  • Exécuter la requête.

Séparation des couches

Cet article est assez simplissime et n'a pour but que d'illustrer les commandes de base de lecture de données. Il y a d'autres aspects à prendre en compte, il est possible, par exemple, d'optimiser la connexion MySQL, ou de mieux séparer les couches de travail (MySQL => PHP => HTML). Ici, j'ai mélangé des <ul>, <li> avec des <?php echo '...'; ?> et ce n'est pas une bonne chose ... ! En réalité, il vaut mieux séparer les différents domaines dans un souci d'optimisation du code, d'une part, et de mise à jour future, d'autre part... Par exemple :

  1. Tout d'abord, on se place coté PHP pour préparer les requêtes, les exécuter et récupérer les données brutes ;
  2. Ensuite, on affiche les données en HTML une fois toutes les opérations PHP effectuées ;
  3. Enfin, une couche de CSS vient donner un autre aspect à la mise en page de l'HTML.

Voici un embryon d'application à mon exemple des pièces à commander (bien des optimisations sont encore possibles) :

<?php
	// On prépare la requête
	$req_pieces_a_commander = 'SELECT
					id_piece,
					reference,
					designation,
					fabricant,
					stock
				FROM
					pieces
				WHERE
					stock < :id_stock;';

	include('connexion.php');


	// il y a un marqueur dans la requête (:valeur_seuil),
	// le tableau ci-dessous liste tous les marqueurs et leur valeur à insérer
	$array_MarqueursValeurs = array( ':valeur_seuil' => 10 );

	// on exécute maintenant la requête
	$requete_prepare_1->execute($array_MarqueursValeurs);

	// on récupère le résultat en tableau PHP
	$array_ListeDesPieces = array(); // le tableau final où seront stockés les résultats de MySQL

	while($piece = $requete_prepare_1->fetch(PDO::FETCH_ASSOC))
	{
		$array_ListeDesPieces[$piece['id']] = $piece;
	}

	$array_pieces = array();

	// lecture du dataset et stockage en tableau PHP
	while($piece = mysql_fetch_array($pieces_a_commander))
	{
		$array_pieces[$piece['id_piece']]['reference'] = $piece['reference'];
		$array_pieces[$piece['id_piece']]['designation'] = $piece['designation'];
		$array_pieces[$piece['id_piece']]['fabricant'] = $piece['fabricant'];
		$array_pieces[$piece['id_piece']]['stock'] = $piece['stock'];
	};

	// maintenant que les opérations PHP sont finies, on affiche le résultat
	echo '
		<table summary="Liste des pièces à commander">
		<thead>
			<tr>
				<th>ID Pièce</th>
				<th>Réf.</th>
				<th>Désignation</th>
				<th>Fabricant</th>
				<th>Stock</th>
			</tr>
		</thead>
		<tbody>';

		foreach($array_pieces as $id_piece => $p)
		{
			echo '
				<tr>
					<td>'.$id_piece.'</td>
					<td>'.$p['reference'].'</td>
					<td>'.$piece['designation'].'</td>
					<td>'.$piece['fabricant'].'</td>
					<td>'.$piece['stock'].'</td>
				</tr>';
		};

	echo '
		</tbody>
		</table>';
?>

Il reste à supposer que cette page bénéficie de styles CSS que je n'ai pas illustrés ici. Mais on ne passe pas son temps à ouvrir php avec <?php et à le fermer avec ?> : on l'ouvre une fois, on fait tous les calculs nécessaires puis on le ferme. On peut imaginer bien des conséquences par la suite : une fonction (ou une méthode) pour afficher les pièces, des tris sur les pièces, etc.