Grégory Bourguin
SysReIC - LISIC - ULCO
PHP (Bases de Données)

PHP & Bases de Données

Introduction

La plupart des applications Web manipulent des données qui sont stockées dans une Base de Données (BD) au sein d'un Système de Gestion de Base de Données (SGBD).

Il est important de comprendre que, même si des solutions telles que WAMP, MAMP, ou encore XAMPP permettent d'installer et d'administrer simplement à la fois un serveur Web (HTTP) et un SGBD, le serveur Web et le SGBD correspondent à 2 entités/serveurs différents. Ils peuvent donc tout à fait être installés sur la même machine (ce qui est généralement le cas avec les solutions "tout en un" évoquées ci-avant), mais aussi être installés sur des machines différentes..

Le serveur Web a pour mission (entre autres) d'exécuter les scripts PHP.
Le SGBD s'occupe quant à lui de la gestion de la Base de Données.

Dans ce contexte, le rôle de PHP est de contacter le SGBD afin :
  • d'extraire des données de la BD, puis de les mettre en forme avant de les envoyer au navigateur de l'utilisateur par exemple sous forme de pages HTML.
  • de traîter des données par exemple fournies par les utilisateurs grâce à des formulaires, ces nouvelles données venant elles-mêmes alimenter la BD.

Il existe de nombreux (types de) SGBD. L'objectif de ce cours n'est ni de fournir un cours sur les Bases de Données, ni de présenter les différents SGBD. De fait, pour comprendre ce cours, il est nécessaire que vous en ayez déjà acquis les notions de base.

Nous utiliserons dans ce cours le SGBD MySQL.

BD de Tests : gamesdb

Afin de pouvoir découvrir les fonctionnalités permettant à PHP d'interagir avec un SGBD, vous allez devoir créer une petite BD qui nous permettra d'effectuer des tests.

Nous n'aborderons pas ici l'administration de MySQL. Vous pourrez le faire avec les connaissances que vous avez acquises en dehors de ce cours, et avec les outils de votre choix (ex. phpMyAdmin) ;).

  1. Dans votre serveur MySQL, créez une BD nommée gamesdb.
    Il s'agit une base de données contenant des informations à propos de jeux vidéo.
  2. Dans gamesdb, il vous faut ensuite créer et remplir la table games.
    Pour ce faire, vous pouvez importer le fichier SQL gamesdb.sql.

Vous pourrez constater que la table games contient les champs suivants :

  • id int(11) NOT NULL : clé primaire (AUTO INCREMENT).
  • name varchar(255) NOT NULL : le nom du jeu.
  • description longtext : une description du jeu.
  • image varchar(255) DEFAULT NULL : le nom d'un fichier image.

L'import a aussi du vous créer quelques enregistrements dans la table games.

Nous allons maintenant voir comment utiliser PHP pour y accéder.

PDO

PHP propose divers moyens permettant aux scripts d'interagir avec divers SGBD.

Une des solutions les plus intéressantes est la classe PDO (PHP Data Objects).

Informations de connexion

Comme indiqué dans l'introduction, le serveur Web qui exécute les scripts PHP et le SGBD sont deux entités distinctes.

De ce fait, la connexion d'un script PHP (le serveur Web) à un SGBD pour manipuler une BD nécessite plusieurs informations.

Ces informations de connexion sont :
  • Le nom de la BD visée.
  • L'adresse IP de la machine qui héberge le SGBD qui gère cette BD.
  • Le n° de port sur lequel le SGBD écoute.
  • Le login que le script PHP doit utiliser pour avoir le droit d'effectuer des requêtes sur la BD.
  • Le mot de passe correspondant au login fourni.

Il est à noter que ces informations, y compris le login et mot de passe utilisés par PHP, peuvent tout à fait être inscrits "en clair" dans des variables du script (PHP) qui les utilise : en effet, les scripts PHP sont exécutés exclusivement au sein du serveur Web et tout le code PHP interprété par le serveur disparait du document qui est envoyé au client.

NB1 : bien entendu, il ne faut pas faire de générations type echo qui enverraient ces informations dans une réponse au client.

NB2 : ceci est totalement faux dans le cas de scripts Javascript qui sont eux exécuté sur le client !

Instanciation et Connexion

Les informations de connexion permettent d'instancier un objet PDO connecté au SGBD.

Le constructeur de PDO a pour paramètres :
  • $dsn : Data Source Name (DSN), une chaine concaténant le nom de la BD visée, l'IP et le port du SGBD.
    ex. 'mysql:dbname=gamesdb;host=127.0.0.1;port=8889;'
  • $username : une chaine contenant le login utilisé par le script.
  • $password : une chaine contenant le mot de passe correspondant au login.
  • $options : un tableau optionnel pour configurer la connexion.
-> Consultez la documentation <-

L'exemple ci-dessous illustre un script qui effectue une simple connexion à la base gamesdb en supposant que le SGBD s'exécute sur la machine locale et sur le port par défaut de MySQL.

connexion_simple.php
<?php

// Informations sur la BDD et le serveur qui la contient
$db_name = "gamesdb" ; // Nom de la base de données (pré-existante)
$db_host = "127.0.0.1" ; // Si le serveur MySQL est sur la machine locale
$db_port = "3306" ; // Port par défaut de MySQL

// Informations d'authentification de votre script PHP
$db_user = "root" ; // Utilisateur par défaut de MySQL (... à changer)
$db_pwd = "root" ;  // Mot de passe par défaut pour l'utilisateur root (.. à changer !!!)

// Connexion à la BDD
try{
    // Agrégation des informations de connexion dans une chaine DSN (Data Source Name)
    $dsn = 'mysql:dbname=' . $db_name . ';host='. $db_host. ';port=' . $db_port;

    // Connexion et récupération de l'objet connecté
    $pdo = new PDO($dsn, $db_user, $db_pwd);
}

// Récupération d'une éventuelle erreur
catch (\Exception $ex){
    // Arrêt de l'exécution du script PHP
    die("Erreur : " . $ex->getMessage()) ;
}

// Si pas d'erreur : poursuite de l'exécution
echo "Connexion OK<br>" ;

Vous aurez remarqué que l'instanciation de PDO a été mise dans un try...catch... En effet, si les informations de connexion sont erronées (ou si le SGBD ne tourne pas), l'instanciation génère une Exception PHP (cf. cours sur les exceptions PHP).

L'exemple ci-dessous présente une nouvelle version de la connexion PHP au SGBD en introduisant un traitement de l'erreur plus détaillé.

connexion_detaillee.php
<?php

// Informations sur la BDD et le serveur qui la contient
$db_name = "gamesdb" ; $db_host = "127.0.0.1" ; $db_port = "3306" ;

// Informations d'authentification de votre script PHP :
$db_user = "root" ; $db_pwd = "root" ;

// Connexion à la BDD
try{
    // Agrégation des informations de connexion dans une chaine DSN (Data Source Name)
    $dsn = 'mysql:dbname=' . $db_name . ';host='. $db_host. ';port=' . $db_port;

    // Connexion et récupération de l'objet connecté
    $pdo = new PDO($dsn, $db_user, $db_pwd);
}

// Récupération d'une éventuelle erreur
catch (\Exception $ex){ ?>
    <!-- Affichage des informations liées à l'erreur-->
    <div style="color: red">
        <b>!!! ERREUR DE CONNEXION !!!</b><br>
        Code : <?= $ex->getCode() ?><br>
        Message : <?= $ex->getMessage() ?>
    </div><?php
    // Arrêt de l'exécution du script PHP
    die("-> Exécution stoppée <-") ;
}

// Poursuite de l'exécution du script ?>
<div style="color: green">Connecté à <b><?= $dsn ?></b></div> <?php

NB : un réel traitement ne présenterait certainement pas des informations système à l'utilisateur... c'est juste pour l'exemple.

Requête Simple

Une fois l'objet PDO instancié et connecté à une BD, il est possible de l'utiliser pour effectuer tous types de requêtes SQL sur cette BD.

La technique de requête que nous préconisons se déroule en plusieurs étapes :
  1. Préparation de la requête.
  2. Exécution de la requête préparée.
  3. Récupération du résultat.

Préparation de la Requête : PDO::prepare

La méthode PDO::prepare permet de préparer une requête sur le SGBD (avant exécution).
Les paramètres sont :
  • string $query : la requête SQL à préparer
  • array $options : des options que nous n'utiliserons pas ici
Cette méthode renvoie :
  • soit un objet de type PDOStatement qui servira a exécuter la requête.
  • soit la valeur false quand le SGBD n'a pas pu préparer la requête.

NB : cette méthode est appelée sur l'instance de PDO récupérée lors de la connexion.

Exemple :

// Préparation d'une requête simple
$sql = "SELECT * FROM games" ;
$statement = $pdo->prepare($sql) ;

Exécution de la requête PDOStatement::execute

La méthode PDOStatement::execute exécute une requête préparée.

Dans le cas d'une requête "simple", il n'y a pas de paramètre.

Cette méthode renvoie :
  • true quand la requête a bien été exécutée.
  • false quand il y a eu un problème.

NB : cette méthode est appelée sur l'instance de PDOStatement récupérée lors de la préparation.

Exemple :

// Exécution de la requête
$statement->execute() or die(var_dump($statement->errorInfo())) ;

Récupération du résultat PDOStatement::fetchAll

La méthode PDOStatement::fetchAll permet des récupérer le résultat d'une requête exécutée.

NB1 : cette méthode est appelée sur l'instance de PDOStatement, après exécution.

Version sans paramètre

Par défaut, fetchAll ne prend pas de paramètre.
Dans ce cas, le résultat est retourné sous la forme d'un "simple" tableau PHP.

Version PDO::FETCH_OBJ

Il est possible de fournir le paramètre int $mode avec la valeur PDO::FETCH_OBJ.
Dans ce cas, le résultat est un tableau d'objets (génériques) PHP.

pdo_request_obj.php
<?php

// Informations sur la BDD et le serveur qui la contient
$db_name = "gamesdb" ; $db_host = "127.0.0.1" ; $db_port = "3306" ;
$db_user = "root" ; $db_pwd = "root" ;

// Connexion à la BDD
try{
    $dsn = 'mysql:dbname=' . $db_name . ';host='. $db_host. ';port=' . $db_port;
    $pdo = new PDO($dsn, $db_user, $db_pwd) ;
}catch(\Exception $ex){
    die("Erreur : " . $ex->getMessage()) ;
}

// Préparation d'une requête simple
$sql = "SELECT * FROM games" ;
$statement = $pdo->prepare($sql) ;

// Exécution de la requête
$statement->execute() or die(var_dump($statement->errorInfo())) ;

// Récupération de la réponse sous forme d'un tableau d'objets
$results = $statement->fetchAll(PDO::FETCH_OBJ) ; ?>

<h1>Liste des jeux</h1>
<ul>
<!--Affichage du champ 'name' des objets récupérés -->
<?php foreach ($results as $game): ?>
    <li><?= $game->name ?></li>
<?php endforeach;?>
</ul>

Version PDO::FETCH_CLASS :

Il est possible de fournir le paramètre int $mode avec la valeur PDO::FETCH_CLASS, accompagné d'un autre paramètre string $class indiquant une classe PHP dans laquelle les objets retournés seront castés.

Dans ce cas, le résultat est un tableau d'instances de la classe $class.

L'exemple ci-après montre comment récupérer les enregistrements de la BD games sous la forme d'un tableau d'objets instances d'une classe GameRenderer que nous allons définir. L'intérêt des instances de GameRenderer sera qu'elles possèdent une méthode (custom) nommée getHTML qui retourne une version HTML de l'objet, facilitant ainsi son affichage dans une page.

La première étape consiste donc à définir la classe GameRenderer (qu'on a décidé de mettre dans le namespace gdb pour "games database").

gdb/GameRenderer.php
<?php
namespace gdb ;

class GameRenderer
{
    public function getHTML(){?>
        <div class="game">
            <b><?= $this->name ?></b>
            <div><?= $this->description ?></div>
        </div>
    <?php }
}

La classe GameRenderer peut alors être utilisée dans le fetchAll avec le paramètre PDO::FETCH_CLASS : vous pourrez remarquer dans la boucle finale d'affichage des jeux que l'on peut appeler la méthode (custom) getHTML sur chaque instance du résultat de la requête.

NB : ne pas oublier l'Autoloader pour que PHP charge bien la classe GameRenderer.

pdo_request_class.php
<!--Définition de la classe CSS utilisée dans GameRenderer-->
<style>
    .game{
        margin-bottom: 10px ;
        border : 3px solid black ;
        background: lightgray;
        padding: 5px
    }
</style>

<?php
require "." . DIRECTORY_SEPARATOR .'class'.DIRECTORY_SEPARATOR.'Autoloader.php' ;
Autoloader::register();

try{ // Connexion à la BDD
    $db_name = "gamesdb" ; $db_host = "127.0.0.1" ; $db_port = "3306" ;
    $db_user = "root" ; $db_pwd = "root" ;
    $dsn = 'mysql:dbname=' . $db_name . ';host='. $db_host. ';port=' . $db_port;
    $pdo = new PDO($dsn, $db_user, $db_pwd) ;
}catch(\Exception $ex){
    die("Erreur : " . $ex->getMessage()) ;
}

// Préparation d'une requête simple
$statement = $pdo->prepare("SELECT * FROM games") ;
$statement->execute() or die(var_dump($statement->errorInfo())) ;

// Récupération de la réponse sous forme d'un tableau d'instances de GameRenderer
$results = $statement->fetchAll(PDO::FETCH_CLASS, "\gdb\GameRenderer") ; ?>

<h1>Liste des jeux</h1>
<!--Affichage des instances récupérées -->
<?php foreach ($results as $game): ?>
    <?= $game->getHTML() ?>
<?php endforeach;?>
(données issues de <a href="https://fr.wikipedia.org/">Wikipédia</a>)

Remarque : il existe d'autres moyens pour récupérer différemment les résultats d'une requête comme PDOStatement::fetch, PDOStatement::fetchColumn, PDOStatement::fetchObject, ...

Requête Paramétrée

PDO permet de préparer des requêtes SQL qui possèdent des paramètres et dont la valeur effective est fournie juste avant l'exécution.

NB : il est déconseillé de créer des requêtes en utilisant la concaténation de chaines.

Les paramètres formels d'une requête SQL sont des noms précédés de :.

Exemple d'une requête d'insertion avec 2 paramètres nommés :nom et :desc :

// Préparation d'une requête paramétrée
$query = "INSERT INTO games (name, description) VALUES (:nom, :desc)" ;
$statement = $pdo->prepare($query) ;

Il reste à lier les paramètres à des valeurs pour pouvoir exécuter la requête.

NB : la même requête préparée peut être exécutée plusieurs fois avec des valeurs différentes de paramètres SQL : il suffit de réaffecter ces paramètres, et de relancer l'exécution !

Liaison par Valeurs : PDOStatement::bindValue

La méthode PDOStatement::bindValue permet d'affecter des valeurs aux paramètres SQL.

Exemple :
req_bind_value.php
<?php
try{ // Connexion à la BDD
    $db_name = "gamesdb" ; $db_host = "127.0.0.1" ; $db_port = "3306" ;
    $db_user = "root" ; $db_pwd = "root" ;
    $dsn = 'mysql:dbname=' . $db_name . ';host='. $db_host. ';port=' . $db_port;
    $pdo = new PDO($dsn, $db_user, $db_pwd) ;
}catch(\Exception $ex){
    die("Erreur : " . $ex->getMessage()) ;
}

// Préparation d'une requête paramétrée
$query = "INSERT INTO games (name, description) VALUES (:nom, :desc)" ;
$statement = $pdo->prepare($query) ;

// Liaison des paramètres à des valeurs
$game_name = "Assassin's Creed Valhalla" ;
$statement->bindValue(':nom', $game_name) ;
$statement->bindValue(':desc', "Skol !") ;

// Exécution
$statement->execute() or die(var_dump($statement->errorInfo())) ;
echo "Le jeu $game_name a été ajouté.<br>" ;

// La même requête préparée peut être exécutée plusieurs fois
// avec les valeurs différentes
$statement->bindValue(':nom', "Resident Evil") ;
$statement->bindValue(':desc', "Miam !") ;
$statement->execute() or die(var_dump($statement->errorInfo())) ;
echo "... et puis un autre !<br>" ;

Tableau de paramètres

Il est possible de donner des valeurs aux paramètres SQL en utilisant un tableau associatif qui est directement envoyé en paramètre de la méthode execute.

Exemple :
req_params_tab.php
<?php
try{ // Connexion à la BDD
    $db_name = "gamesdb" ; $db_host = "127.0.0.1" ; $db_port = "3306" ;
    $db_user = "root" ; $db_pwd = "root" ;
    $dsn = 'mysql:dbname=' . $db_name . ';host='. $db_host. ';port=' . $db_port;
    $pdo = new PDO($dsn, $db_user, $db_pwd) ;
}catch(\Exception $ex){
    die("Erreur : " . $ex->getMessage()) ;
}

// Préparation d'une requête paramétrée
$query = "INSERT INTO games (name, description) VALUES (:nom, :desc)" ;
$statement = $pdo->prepare($query) ;

// Exécution avec les paramètres
$game_name = "RDR2" ;
$params = [
   'nom' => $game_name,
    'desc' => "... pan pan ..."
] ;
$statement->execute($params) or die(var_dump($statement->errorInfo())) ;
echo "Le jeu $game_name a été ajouté.<br>" ;

Liaison par Variables : PDOStatement::bindParam

PDOStatement::bindParam associe des références de variables aux paramètres SQL.

En conséquence, pour ré-exécuter la requête avec d'autres valeurs, il suffit de modifier le contenu des variables liées.

Exemple :
req_bind_variable.php
<?php
try{ // Connexion à la BDD
    $db_name = "gamesdb" ; $db_host = "127.0.0.1" ; $db_port = "3306" ;
    $db_user = "root" ; $db_pwd = "root" ;
    $dsn = 'mysql:dbname=' . $db_name . ';host='. $db_host. ';port=' . $db_port;
    $pdo = new PDO($dsn, $db_user, $db_pwd) ;
}catch(\Exception $ex){
    die("Erreur : " . $ex->getMessage()) ;
}

// Préparation d'une requête paramétrée
$query = "INSERT INTO games (name, description) VALUES (:nom, :desc)" ;
$statement = $pdo->prepare($query) ;

// Liaison des paramètres à des variables
$statement->bindParam(':nom', $game_name) ;
$statement->bindParam(':desc', $desc) ;

// Affectation des variables/paramètres et exécution
$game_name = "Donkey Kong" ;
$desc = "Ça donne la banane." ;
$statement->execute() or die(var_dump($statement->errorInfo())) ;
echo "Le jeu $game_name a été ajouté.<br>" ;

// La même requête préparée peut être exécutée plusieurs fois avec les valeurs
// différentes simplement en réaffectant les variables liées
$game_name = "Elden Ring" ;
$desc = "Un giga jeu !" ;
$statement->execute() or die(var_dump($statement->errorInfo())) ;
echo "Le jeu $game_name a été ajouté.<br>" ;

TP 05