Chapitre 20 - PDO
PDO (PHP Data Object) est une couche d'abstraction intégrée à PHP depuis sa version 5.1
Elle permet de relier le langage PHP à la BDD (base de données), et ainsi l'exploiter, l'alimenter, pour les besoins du site
PDO est une classe prédéfinie de PHP, qui possède ses propres méthodes/fonctions qui permettent de faire des requetes SQL
Voici, a titre indicatif, leur liste (la variable $pdo dans le code ci-dessous étant un objet de la classe PDO, voir le sous-chapitre suivant):
echo "<div class='blockquote'><pre>"; print_r(get_class_methods($pdo)); echo "</pre></div>";
Array ( [0] => __construct [1] => beginTransaction [2] => commit [3] => errorCode [4] => errorInfo [5] => exec [6] => getAttribute [7] => getAvailableDrivers [8] => inTransaction [9] => lastInsertId [10] => prepare [11] => query [12] => quote [13] => rollBack [14] => setAttribute )
Au préalable, je vais devoir créer un nouvel objet de ma classe PDO (voir le chapitre 14 sur les Classes / Objet )
Voici un exemple de syntaxe basique pour se connecter à une BDD (pour mon exemple, je vais utiliser celle nommée/dbname "entreprise")
$pdo = new PDO('mysql:host=localhost;dbname=entreprise', 'root', '', array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING,
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8')
);
Plusieurs points à détailler ici
J'ai donc crée un objet $pdo de ma classe PDO, mais j'aurais pu tout aussi bien le nommer $bdd, par exemple. Son nom n'a pas d'importance, comme souvent pour une variable, tant qu'il est évocateur
Je travaille pour l'instant en local, donc host (qui renseigne sur le serveur) sera bien égal à localhost. Idem pour le dbuser, en localhost, ça sera root
Et pour terminer, mon mot de passe restera vide, d'où '' (quote sans aucune donnée), tant que je suis en local
PDO::ATTR_ERRMODE (pour attribut mode erreur) me sert a définir sous quel attribut PDO va m'envoyer ses messages d'erreurs. Avec cette syntaxe, je choisi une alerte E_WARNING
Concernant PDO::MYSQL_ATTR_INIT_COMMAND, cela permet de sélectionner le type de codage de caractères qui sera automatiquement interprété a chaque connexion. Ici en utf8, comme dans mon DOCTYPE
La méthode query() de PDO permet de faire différentes requetes
Néanmoins, il vaut mieux la réserver à la requete SELECT et faire des requetes préparées si je veux ajouter (INSERT INTO), modifier (requete de type UPDATE) ou supprimer (DELETE) des données dans ma BDD
Cela permettra de sécuriser l'envoi de données ( voir le chapitre 17 sur la sécurité PHP )
$afficheClient = $pdo->query("SELECT * FROM client WHERE prenom = 'Assia' ;");
echo "<pre>"; var_dump($afficheClient); echo "</pre>";
En faisant un var_dump de ma variable $afficheClient, je peux voir que c'est un objet de la classe PDOStatement
object(PDOStatement)#1 (1) { ["queryString"]=> string(45) "SELECT * FROM client WHERE prenom = 'Assia' ;" }
Remarque
Je fais un aparté sur la methode exec() (listée dans le print_r du sous-chapitre 20.1), pour dire qu'elle permet de faire les INSERT, UPDATE et DELETE (comme query()). Par contre, le SELECT est reservé a query(). De plus,exec() ne retournera pas un résultat de données, mais un integer, qui indiquera le nombre des modifications impactées par ma requete
Une fois éxécuté la requete query(), je vais lui adjoindre la méthode fetch(), pour pouvoir parcourir et récupérer les données ciblées par mon SELECT
fetch() permet de faire une recherche sur le nom de la colonne (c'est souvent suffisant) et pour cela j'utiliserai PDO::FETCH_ASSOC. Je pourrai aussi faire une recherche combinée sur l'indice de la colonne + son nom, et dans ce cas, j'opterai pour PDO::FETCH_BOTH. Il existe aussi l'option pour cibler uniquement l'indice, c'est FETCH_NUM
Remarque
Dans PDO::FETCH_ASSOC, les :: permettent de faire appel à une constante de la classe PDO sans devoir l'instancier (créer un objet)
Voici la syntaxe pour afficher les données, ainsi que le print_r pour récupérer tout ce qui concerne le client dont le prénom est Assia
$client = $afficheClient->fetch(PDO::FETCH_ASSOC);
echo "<p class='font-weight-bold'>" . $client['civilite_id'] . " " . $client['prenom'] . " " .
$client['nom'] . ", résidant au " . $client['voie'] . " " .
$client['code_postal'] . " " . $client['commune'] . '</p>';
echo "<pre>"; print_r($client); echo "</pre>";
MME Assia Abbas, résidant au 11 rue de la République 94140 ALFORTVILLE
Array ( [id] => 5 [civilite_id] => MME [nom] => Abbas [prenom] => Assia [voie] => 11 rue de la République [code_postal] => 94140 [commune] => ALFORTVILLE [pays_id] => FR )
Je pourrai aussi afficher tous les clients en base de données, sans restriction de prénom, sous forme de liste
Et pour cela j'utiliserai la boucle While
<?php
$afficheClient = $pdo->query("SELECT * FROM client");
?>
<ul class="list-group">
<?php while($client = $afficheClient->fetch(PDO::FETCH_ASSOC)): ?>
<li class="list-group-item"><?= $client['civilite_id'] . " " . $client['prenom']
. " " . $client['nom'] ?>, résidant au <?= $client['voie'] . " " .
$client['code_postal'] . " " . $client['commune'] ?></li>
<?php endwhile; ?>
</ul>
- MME Séverine Dulac, résidant au 53 rue des Fleurs 94000 CRETEIL
- M Damien Gomez, résidant au 15 avenue Louis Pasteur 94220 CHARENTON LE PONT
- MME Marie Diallo, résidant au 45 rue du Centre 94500 CHAMPIGNY SUR MARNE
- M Paul Lefort, résidant au 32 boulevard Jean Jaurès 94230 CACHAN
- MME Assia Abbas, résidant au 11 rue de la République 94140 ALFORTVILLE
- M Jan Bakker, résidant au 26 Boomsteeg 2018 AMSTERDAM
Pour un affichage dans un tableau, pour mon Back-Office par exemple, j'aurai alors besoin de combiner For, While et Foreach
Si ma requete Select reste similaire, ma syntaxe à l'intérieur des boucles va etre légèrement plus complexe
Je vais la détailler après l'affichage du code et du tableau
<table class="table col-md-11 text-center mx-auto">
<?php
$afficheClient = $pdo->query("SELECT * FROM client");
?>
<thead>
<tr>
<?php for($i = 0; $i < $afficheClient->columnCount(); $i++):
$colonne = $afficheClient->getColumnMeta($i) ?>
<th><?= $colonne['name'] ?></th>
<?php endfor; ?>
</tr>
</thead>
<tbody>
<?php while($client = $afficheClient->fetch(PDO::FETCH_ASSOC)): ?>
<tr>
<?php foreach($client as $key => $value): ?>
<td><?= $value ?></td>
<?php endforeach; ?>
</tr>
<?php endwhile; ?>
</tbody>
</table>
id | civilite_id | nom | prenom | voie | code_postal | commune | pays_id |
---|---|---|---|---|---|---|---|
1 | MME | Dulac | Séverine | 53 rue des Fleurs | 94000 | CRETEIL | FR |
2 | M | Gomez | Damien | 15 avenue Louis Pasteur | 94220 | CHARENTON LE PONT | FR |
3 | MME | Diallo | Marie | 45 rue du Centre | 94500 | CHAMPIGNY SUR MARNE | FR |
4 | M | Lefort | Paul | 32 boulevard Jean Jaurès | 94230 | CACHAN | FR |
5 | MME | Abbas | Assia | 11 rue de la République | 94140 | ALFORTVILLE | FR |
6 | M | Bakker | Jan | 26 Boomsteeg | 2018 | AMSTERDAM | NL |
Dans ma boucle For, rien de particulier concernant l'initialisation et l'incrémentation. Je vais par contre détailler la condition $i < columnCount
J'utilise la fonction prédéfinie columnCount (de la classe PDOStatement), qui va me permettre de stopper ma boucle For
Je vais ainsi "créer" autant de <th> que j'ai de colonnes dans ma Table en BDD
Ensuite, avec getColumnMeta, une autre fonction prédéfinie de la classe PDOStatement, je vais récupérer les noms de mes colonnes
Le print_r de $colonne (volontairement limité a 2), ci dessous, montre que si je crochète à l'indice $colonne['name'], ce n'est pas de manière arbitraire (voir les deux lignes 12 ci-dessous)
for($i = 0; $i < 2; $i++){
$colonne = $afficheClient->getColumnMeta($i);
echo "<div class='blockquote'><pre>"; print_r($colonne); echo "</pre></div>";
}
L'indice name est l'intitulé qui permet de récupérer le nom de chaque colonne
Array ( [native_type] => LONG [pdo_type] => 1 [flags] => Array ( [0] => not_null [1] => primary_key ) [table] => client [name] => id [len] => 11 [precision] => 0 )
Array ( [native_type] => VAR_STRING [pdo_type] => 2 [flags] => Array ( [0] => not_null ) [table] => client [name] => civilite_id [len] => 30 [precision] => 0 )
Concernant la récupération des valeurs pour chaque client, dans les <td>, je vais d'abord faire appel à la méthode fetch (avec PDO::FETCH_ASSOC), dans une boucle While
Suivie, après les <tr>, d'une boucle Foreach pour récupérer chaque $value dans son <td>
Dans le cas où je dois récupérer une photo (pour un portrait, un article de presse, un produit etc...) ou un prix, j'utiliserai la condition if() dans ma boucle Foreach
Mon premier if concernera le cas où l'indice/$key (nom de la colonne) sera égal a photo. Si cette condition est remplie, la valeur/$value servira de nom de fichier (dans l'attribut src de ma balise img, précédée du chemin physique vers le dossier img)
Le elseif qui le suit ciblera l'indice/$key intitulé prix. Pour pouvoir le faire suivre du sigle €
Enfin, le else servira pour tous les autres cas de figure, pour tous les autres indices/valeurs.
<?php while($client = $afficheClient->fetch(PDO::FETCH_ASSOC)): ?>
<?php foreach($produit as $key => $value): ?>
<?php if($key == "photo"): ?>
<td><img src="img/<?= $value ?>"></td>
<?php elseif($key == "prix"): ?>
<td> <?= $value ?> €</td>
<?php else ?>
<td> <?= $value ?> </td>
<?php endif; ?>
<?php endforeach; ?>
<?php endwhile; ?> </tbody>
Il existe une autre méthode; fetchAll, qui m'aurait permis de faire l'économie de la boucle while dans le même code ci-dessus
<?php $client = $afficheClient->fetchAll(PDO::FETCH_ASSOC) ?>
<?php foreach($produit as $key => $value): ?>
... même code ...
<?php endforeach; ?>
Mais cela ne fonctionne qu'avec une base de données comportant un faible volume de données. Si ce n'est pas le cas, fetch sera plus approprié car elle récupère les données ligne par ligne, au lieu de renvoyer l'intégralité des données dans un tableau qui consommerait beaucoup de mémoire