166

Accès à une base de données

Créez site11 en copiant site10.

  1. /cms
    1. ...
    2. site10
    3. site11

Dans ce chapitre, nous allons programmer l'accès à une base de données, la configuration du connecteur et l'initialisation de la connexion au démarrage du programme, puis un jeu de fonctions permettant d'enregistrer des variables dans la BD.

Pour tester le résultat en ligne, entrez http://www.frasq.org/cms/site11 dans la barre d'adresse de votre navigateur.

Créez un utilisateur appelé frasqdb2 dans la table user de la base de données mysql :

INSERT INTO `user`
(Host, `User`, Password, Select_priv, Insert_priv, Update_priv, Delete_priv, Create_priv, Drop_priv, Reload_priv, Shutdown_priv, Process_priv, File_priv, Grant_priv, References_priv, Index_priv, Alter_priv, Show_db_priv, Super_priv, Create_tmp_table_priv, Lock_tables_priv, Execute_priv, Repl_slave_priv, Repl_client_priv, Create_view_priv, Show_view_priv, Create_routine_priv, Alter_routine_priv, Create_user_priv, Event_priv, Trigger_priv, ssl_type, max_questions, max_updates, max_connections, max_user_connections)
VALUES ('localhost', 'frasqdb2', PASSWORD('Fch9Xw4k'), 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', '', '0', '0', '0', '0')

IMPORTANT : Choisissez un mot de passe aléatoire et notez-le. Pensez à sélectionner la fonction PASSWORD pour le champ password sinon la valeur ne sera pas chiffrée et le mot de passe transmis par PHP, qui sera chiffré, ne correspondra pas.

Ajoutez les droits d'accès de l'utilisateur frasqdb2 à la BD frasqdb2 dans la table db :

INSERT INTO `db`
(Host, Db, USER, Select_priv, Insert_priv, Update_priv, Delete_priv, Create_priv, Drop_priv, Grant_priv, References_priv, Index_priv, Alter_priv, Create_tmp_table_priv, Lock_tables_priv, Create_view_priv, Show_view_priv, Create_routine_priv, Alter_routine_priv, Execute_priv, Event_priv, Trigger_priv)
VALUES ('localhost', 'frasqdb2', 'frasqdb2', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'N', 'N', 'Y', 'Y', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N')

Quittez MySQL :

mysql> quit

Lisez l'article Les outils du développeur web pour apprendre à installer MySQL et plus généralement un environnement LAMP.

Un connecteur à une BD a 4 paramètres : le nom de l'utilisateur de la BD et son mot de passe, le nom ou l'adresse IP du serveur et le nom de la BD.

Ajoutez le fichier de configuration db.inc dans le dossier includes avec le contenu suivant :

  1. /cms/site11
    1. includes
      1. db.inc
  1. $db_url = 'mysql://username:password@localhost/databasename';
  2. $db_prefix = false;
  3. $db_debug = false;

$db_url définit le connecteur à la BD. Remplacez databasename et username par frasqdb2, password par le mot de passe que vous avez noté quand vous avez créé l'utilisateur de la BD.

$db_prefix permet de changer les noms de toutes les tables afin d'éviter des conflits avec d'autres composants de la BD. $db_debug à true trace tous les ordres SQL.

IMPORTANT : Assurez-vous que ce fichier est protégé d'un accès extérieur mais toujours lisible par Apache :

$ chmod 600 db.inc
$ sudo chown www-data db.inc

Initialisez la connexion à la BD au démarrage du programme dans la fonction bootstrap définie dans le fichier bootstrap.php du dossier library :

  1.     @include 'db.inc';

Charge la configuration de la connexion à la base de données.

  1.     if (isset($db_url) && $db_url == 'mysql://username:password@localhost/databasename') {
  2.         $db_url = false;
  3.     }
  4.  
  5.     if ($db_url) {
  6.         require_once 'pdo.php';
  7.         db_connect($db_url);
  8.     }

Met $db_url à false si $db_url est à la valeur qui rappelle les paramètres du connecteur. Si $db_url n'est pas false, charge le fichier des fonctions d'accès à la BD puis ouvre la connexion.

Ajoutez le fichier pdo.php dans le dossier library avec le contenu suivant :

  1. /cms/site11
    1. library
      1. pdo.php
  1. $db_conn=false;
  2. $db_scheme=false;
  3.  
  4. function db_connect($url, $persistent=true) {
  5.     global $db_conn, $db_scheme;
  6.  
  7.     $url = parse_url($url);
  8.  
  9.     $scheme = $url['scheme'];
  10.     $host = urldecode($url['host']);
  11.     if (isset($url['port'])) {
  12.         $host = $host . ':' . $url['port'];
  13.     }
  14.     $user = urldecode($url['user']);
  15.     $pass = isset($url['pass']) ? urldecode($url['pass']) : '';
  16.     $path = urldecode($url['path']);
  17.     if ($path[0] == '/') {
  18.         $path = substr($path, 1);
  19.     }
  20.  
  21.     $dsn = "$scheme:host=$host;dbname=$path";
  22.     $options = array(PDO::ATTR_PERSISTENT => $persistent ? true : false);
  23.  
  24.     try {
  25.         $db_conn = new PDO($dsn, $user, $pass, $options);
  26.         $db_conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  27.         $db_conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
  28.         $db_conn->exec("SET NAMES 'utf8'");
  29.  
  30.         if ($scheme == 'mysql') {
  31.             $db_conn->exec("SET SQL_MODE='ANSI_QUOTES'");
  32.         }
  33.  
  34.         $db_scheme=$scheme;
  35.     }
  36.     catch (PDOException $e) {
  37.         die($e->getMessage());
  38.     }
  39.  
  40.     return $db_conn;
  41. }

db_connect analyse $url pour en extraire les paramètres de connexion à la BD puis ouvre une connexion permanente avec le serveur et sélectionne la BD demandée. En cas de problème, db_connect remonte l'erreur MySQL et déclenche une erreur PHP. Avant de retourner le connecteur, db_connect force l'encodage des caractères en UTF-8. Notez que $db_conn est une variable globale.

  1. function db_query($sql) {
  2.     global $db_debug;
  3.     global $db_conn;
  4.  
  5.     if ($db_debug) {
  6.         dump($sql);
  7.     }
  8.  
  9.     try {
  10.         $r = $db_conn->query($sql);
  11.     }
  12.     catch (PDOException $e) {
  13.         die($e->getMessage());
  14.     }
  15.  
  16.     $rows = $r->fetchAll(PDO::FETCH_ASSOC);
  17.  
  18.     if (!$rows) {
  19.         return false;
  20.     }
  21.  
  22.     return $rows;
  23. }

db_query exécute la requête $sql en appelant la fonction privée _db_sql_query et retourne toutes les lignes de réponse dans un tableau ou false si la requête n'a rien renvoyé.

  1. function db_query($sql) {
  2.     global $db_debug;
  3.     global $db_conn;
  4.  
  5.     if ($db_debug) {
  6.         dump($sql);
  7.     }
  8.  
  9.     try {
  10.         $r = $db_conn->query($sql);
  11.     }
  12.     catch (PDOException $e) {
  13.         die($e->getMessage());
  14.     }
  15.  
  16.     $rows = $r->fetchAll(PDO::FETCH_ASSOC);
  17.  
  18.     if (!$rows) {
  19.         return false;
  20.     }
  21.  
  22.     if (get_magic_quotes_runtime()) {
  23.         foreach ($rows as $row) {
  24.             foreach ($row as $k => &$v) {
  25.                 $v = stripslashes($v);
  26.             }
  27.         }
  28.     }
  29.  
  30.     return $rows;
  31. }

Dans cette version utilisée avant PHP 7, db_query retire les \ (BACKSLASH) des valeurs retournées ajoutés automatiquement par PHP si le paramètre de configuration magic_quotes_gpc est à true.

  1. function db_insert($sql) {
  2.     return _db_sql_exec($sql);
  3. }
  4.  
  5. function db_update($sql) {
  6.     return _db_sql_exec($sql);
  7. }
  8.  
  9. function db_delete($sql) {
  10.     return _db_sql_exec($sql);
  11. }
  12.  
  13. function db_exec($sql) {
  14.     return _db_sql_exec($sql);
  15. }
  16.  
  17. function db_insert_id($id=null) {
  18.     global $db_conn;
  19.  
  20.     $r = $db_conn->lastInsertId($id);
  21.  
  22.     return $r;
  23. }

db_insert, db_update et db_delete retourne le résultat de l'exécution de la requête $sql par la fonction privée _db_sql_query. db_insert_id retourne la clé primaire créée par le dernier appel à db_insert.

  1. function db_sql_arg($s, $escape=true, $optional=false) {
  2.     global $db_conn;
  3.  
  4.     if ($s === NULL or $s === false or $s === '') {
  5.         return $optional ? 'NULL' : "''";
  6.     }
  7.  
  8.     return $escape ? $db_conn->quote($s) : "'$s'";
  9. }

db_sql_arg prépare un argument pour une requête SQL en ajoutant une ' (QUOTE) avant et après une valeur littérale. Mettez $escape à false seulement si $s ne peut pas contenir de caractères spéciaux pour SQL. Mettez $optional à true pour un champ dont la valeur est optionnelle.

IMPORTANT : PHP doit être connecté à une BD pour que la fonction mysql_real_escape_string fonctionne.

  1. function db_prefix_table($table) {
  2.     global $db_prefix;
  3.  
  4.     return $db_prefix ? $db_prefix . $table : $table;
  5. }

db_prefix_table ajoute $db_prefix au début du nom d'une table. $db_prefix est défini dans db.inc.

IMPORTANT : Utilisez systématiquement db_prefix_table et db_sql_arg pour fabriquer les noms des tables et les valeurs des arguments dans une requête SQL. EXEMPLE :

  1.     $sqlname=db_sql_arg($name, false);
  2.  
  3.     $tabregistry=db_prefix_table('registry');
  4.  
  5.     db_delete("DELETE FROM $tabregistry WHERE name=$sqlname LIMIT 1");

$sqlname contient la valeur de $name entourée par des guillemets simples, sans caractères d'échappement. Si $db_prefix vaut fq_, $tabregistry vaut fq_registry.

  1. function _db_sql_exec($sql) {
  2.     global $db_debug;
  3.     global $db_conn;
  4.  
  5.     if ($db_debug) {
  6.         dump($sql);
  7.     }
  8.  
  9.     try {
  10.         $r = $db_conn->exec($sql);
  11.     }
  12.     catch (PDOException $e) {
  13.         die($e->getMessage());
  14.     }
  15.  
  16.     return $r;
  17. }

_db_sql_exec trace la requête $sql si $db_debug est true puis l'exécute. En cas d'erreur, PHP est arrêté.

_db_sql_exec est une fonction privée appelée par toutes les fonctions qui exécutent une requête SQL. Elle permet de centraliser les appels à la méthode exec de la classe PDO. $db_debug est défini dans db.inc.

Modifiez la page d'accueil, en français et en anglais, pour afficher les numéros de version de PHP, de MySQL et du système du serveur web :

  1. /cms/site11
    1. views
      1. en
        1. home.phtml
      2. fr
        1. home.phtml
  1. <h3>Bienvenue</h3>
  2. <p>PHP <?php echo phpversion(); ?><br />
  3. MySQL <?php echo db_version(); ?><br />
  4. <?php echo php_uname('s'); ?>&nbsp;<?php echo php_uname('r'); ?></p>
  1. <h3>Welcome</h3>
  2. <p>PHP <?php echo phpversion(); ?><br />
  3. MySQL <?php echo db_version(); ?><br />
  4. <?php echo php_uname('s'); ?>&nbsp;<?php echo php_uname('r'); ?></p>

Entrez http://localhost/cms/site11 dans la barre d'adresse de votre navigateur pour vérifier que le site est connecté à la BD.

Mettez $db_url à false dans le fichier includes/db.inc pour ne plus connecter le programme à la BD. Rechargez la page d'accueil. Une erreur est générée. La fonction db_version n'est pas définie. Le fichier pdo.php n'a pas été chargé par la fonction bootstrap.

Pour illustrer les fonctions d'accès à une BD, nous allons programmer un registre permanent de sauvegarde de variables et de leurs valeurs :

$ mysql -u root -p

Entrez dans la BD du site que vous venez de créer :

mysql> USE frasqdb2;

Créez la table registry avec les champs name et value :

mysql> CREATE TABLE registry (
  name varchar(100) NOT NULL,
  value longtext NOT NULL,
  PRIMARY KEY (name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Ajoutez le fichier registry.php dans le dossier library avec le contenu suivant :

  1. /cms/site11
    1. library
      1. registry.php
  1. function registry_get($name, $default=false) {
  2.     $sqlname=db_sql_arg($name, false);
  3.  
  4.     $tabregistry=db_prefix_table('registry');
  5.  
  6.     $r = db_query("SELECT value FROM $tabregistry WHERE name=$sqlname LIMIT 1");
  7.  
  8.     return $r ? unserialize($r[0]['value']) : $default;
  9. }

registry_get retourne la valeur de la variable dont le nom est donné par $name. Les valeurs enregistrées dans la BD sont sérialisées. registry_get fabrique une requête SQL qui extrait la valeur de la variable demandée et la désérialise avant de la retourner. Notez l'emploi des fonctions db_sql_arg et db_prefix_table. Si la variable $name n'existe pas, registry_get retourne la valeur donnée par $default qui vaut false par défaut.

  1. function registry_set($name, $value) {
  2.     $sqlname=db_sql_arg($name, false);
  3.     $sqlvalue=db_sql_arg(serialize($value), true);
  4.  
  5.     $tabregistry=db_prefix_table('registry');
  6.  
  7.     db_insert("INSERT $tabregistry SET name=$sqlname, value=$sqlvalue ON DUPLICATE KEY UPDATE name=VALUES(name), value=VALUES(value)");
  8. }

registry_set enregistre la variable dont le nom est donné par $name avec la valeur donnée par $value. Les valeurs enregistrées dans la BD sont sérialisées. Si la variable est déjà enregistrée, sa valeur est remplacée.

  1. function registry_delete($name) {
  2.     $sqlname=db_sql_arg($name, false);
  3.  
  4.     $tabregistry=db_prefix_table('registry');
  5.  
  6.     db_delete("DELETE FROM $tabregistry WHERE name=$sqlname LIMIT 1");
  7. }

registry_delete supprime la variable dont le nom est donné par $name.

Commentaires

Votre commentaire :
[p] [b] [i] [u] [s] [quote] [pre] [br] [code] [url] [email] strip aide 2000

Entrez un maximum de 2000 caractères.
Améliorez la présentation de votre texte avec les balises de formatage suivantes :
[p]paragraphe[/p], [b]gras[/b], [i]italique[/i], [u]souligné[/u], [s]barré[/s], [quote]citation[/quote], [pre]tel quel[/pre], [br]à la ligne,
[url]http://www.izend.org[/url], [url=http://www.izend.org]site[/url], [email]izend@izend.org[/email], [email=izend@izend.org]izend[/email],
[code]commande[/code], [code=langage]code source en c, java, php, html, javascript, xml, css, sql, bash, dos, make, etc.[/code].