logo du langage PHP Les bases du langage PHP

Chapitre 20 - PDO

Documentation officielle

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>
Remarque

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