Skip to content

8. Exercice IMPOTS avec MySQL

Nous avons déjà écrit trois versions de cet exercice. La dernière version utilisait une classe de calcul de l'impôt. Cette classe prenait dans un fichier texte, les données permettant ce calcul. Elle va désormais les prendre dans une base de données. Pour cela, nous écrivons un premier code qui va transférer les données du fichier texte dans une base de données.

Le fichier texte impots.txt est le suivant:

12620:13190:15640:24740:31810:39970:48360:55790:92970:127860:151250:172040:195000:0
0:0.05:0.1:0.15:0.2:0.25:0.3:0.35:0.4:0.45:0.5:0.55:0.6:0.65
0:631:1290.5:272.5:3309.5:4900:6898.5:9316.5:12106:16754.5:23147.5:30710:39312:49062

La base de données à construire est la suivante :

La base s'appelle [dbimpots] et a une unique table [impots]. Elle est exploitée par l'utilisateur [root] sans mot de passe.

8.1. Transfert d'un fichier texte dans une table MySQL (txt2mysql)


<?php

// transfère le fichier texte des données nécessaires au calcul des impôts
// dans une table mysql
// les données
$IMPOTS = "impots.txt";
$BASE = "dbimpots";
$TABLE = "impots";
$USER = "root";
$PWD = "";
$HOTE = "localhost";

// les données nécessaires au calcul de l'impôt ont été placées dans le fichier $IMPOTS
// à raison d'une ligne par tableau sous la forme
// val1:val2:val3,...
list($erreur, $limites, $coeffR, $coeffN) = getTables($IMPOTS);
// y-a-t-il eu une erreur ?
if ($erreur) {
  print "$erreur\n";
  exit;
}//if
// on transfère ces tableaux dans une table mysql
$erreur = copyToMysql($limites, $coeffR, $coeffN, $HOTE, $USER, $PWD, $BASE, $TABLE);
if ($erreur)
  print "$erreur\n";
else
  print "Transfert opéré\n";
// fin
exit;

// --------------------------------------------------------------------------
function copyToMysql($limites, $coeffR, $coeffN, $HOTE, $USER, $PWD, $BASE, $TABLE) {
  // copie les 3 tableaux numériques $limites, $coeffR, $coeffN
  // dans la table $TABLE de la base mysql $BASE
  // la base mysql est sur la machine $HOTE
  // l'utilisateur est identifié par $USER et $PWD
  // connexion
  list($erreur, $connexion) = connecte("mysql:host=$HOTE;dbname=$BASE", $USER, $PWD);
  if ($erreur)
    return "Erreur lors de la connexion à MySql sous l'identité ($HOTE,$USER,$PWD) : $erreur\n";
  // suppression de la table
  $requête = "drop table $TABLE";
  exécuteRequête($connexion, $requête);
  // création de la table
  $requête = "create table $TABLE (limites decimal(10,2), coeffR decimal(6,2), coeffN decimal(10,2))";
  list($erreur, $res) = exécuteRequête($connexion, $requête);
  if ($erreur)
    return "$requête : erreur ($erreur)";
  // remplissage
  for ($i = 0; $i < count($limites); $i++) {
    // requête d'insertion
    $requête = "insert into $TABLE (limites,coeffR,coeffN) values ($limites[$i],$coeffR[$i],$coeffN[$i])";
    // exécution de la requête
    list($erreur, $res) = exécuteRequête($connexion, $requête);
    // retour si erreur
    if ($erreur)
      return "$requête : erreur ($erreur)";
  }//for
  // c'est fini - on se déconnecte
  déconnecte($connexion);
  // retour sans erreur
  return "";
}
// --------------------------------------------------------------------------
function getTables($IMPOTS) {
  // $IMPOTS : le nom du fichier contenant les données des tables $limites, $coeffR, $coeffN
  ...
  // fin
  return array("", $limites, $coeffR, $coeffN);
}
// --------------------------------------------------------------------------
function cutNewLinechar($ligne) {
  // on supprime la marque de fin de ligne de $ligne si elle existe
  ...
  // fin
  return($ligne);
}
// ---------------------------------------------------------------------------------
function connecte($dsn, $login, $pwd) {
  // connecte ($login,$pwd) à la base $dsn
  // rend l'id de la connexion ainsi qu'un msg d'erreur
  ...
}

//connecte
// ---------------------------------------------------------------------------------
function déconnecte($connexion) {
  // ferme la connexion identifiée par $connexion
  ...
}

// ---------------------------------------------------------------------------------
function exécuteRequête($connexion, $sql) {
  // exécute la requête $sql sur la connexion $connexion
  // rend un tableau de 2 éléments ($erreur,$résultat)
 ...
}

Les résultats écran :

Transfert opéré

8.2. Le programme de calcul de l'impôt (impots_04)

Cette version utilise une classe Impôts qui va chercher dans une base de données les valeurs nécessaires au calcul de l'impôt. Nous introduisons ici une nouvelle notion, celle de requête préparée. L'exécution d'un ordre SQL par un SGBD est faite en deux temps :

  1. la requête est préparée : le SGBD va préparer un plan d'exécution optimisé pour la requête. Il s'agit de l'exécuter le plus efficacement possible.
  1. la requête est exécutée.

Si une même requête est exécutée N fois, les deux étapes précédentes sont faites N fois. Il est cependant possible de préparer la requête 1 fois et de l'exécuter N fois. Pour cela il faut utiliser des requêtes préparées. Si $requête est l'ordre SQL à exécuter et $connexion l'objet PDO représentant la connexion :

  • $statement=$connexion->prepare($requête) prépare une requête et rend la requête "préparée"
  • $statement->execute() exécute la requête préparée.

Si la requête préparée est un ordre select alors

  • $statement->fetchAll() ramène toutes les lignes de la table résultat du select sous la forme d'un tableau T où T[i,j] est la valeur de la colonne j de la ligne i de la table
  • $statement->fetch() ramène la ligne courante de la table sous la forme d'un tbaleau T où T[j] est la valeur de la colonne j de la ligne

La requête préparée apporte d'autres avantages que celui d'une meilleure efficacité. Elle amène notamment plus de sécurité. Aussi devrait-on l'utiliser systématiquement.


<?php

// test -----------------------------------------------------
// définition des constantes
$DATA = "data.txt";
$RESULTATS = "resultats.txt";
$TABLE = "impots";
$BASE = "dbimpots";
$USER = "root";
$PWD = "";
$HOTE="localhost";

// les données nécessaires au calcul de l'impôt ont été placées dans la table mysqL $TABLE
// appartenant à la base $BASE. La table a la structure suivante
// limites decimal(10,2), coeffR decimal(6,2), coeffN decimal(10,2)
// les paramètres des personnes imposables (statut marital, nombre d'enfants, salaire annuel)
// ont été placés dans le fichier texte $DATA à raison d'une ligne par contribuable
// les résultats (statut marital, nombre d'enfants, salaire annuel, impôt à payer) sont placés dans
// le fichier texte $RESULTATS à raison d'un résultat par ligne

// on crée un objet Impôts
$I = new Impôts($HOTE, $USER, $PWD, $BASE, $TABLE);
// y-a-t-il eu une erreur ?
$erreur=$I->getErreur();
if ($erreur) {
  print "$I->erreur\n";
  exit;
}//if
// on crée un objet utilitaires
$u = new Utilitaires();

// ouverture fichier des données des contribuables
$data = fopen($DATA, "r");
if (!$data) {
  print "Impossible d'ouvrir en lecture le fichier des données [$DATA]\n";
  exit;
}

// ouverture fichier des résultats
$résultats = fopen($RESULTATS, "w");
if (!$résultats) {
  print "Impossible de créer le fichier des résultats [$RESULTATS]\n";
  exit;
}

// on exploite la ligne courante du fichier des données contribuables
while ($ligne = fgets($data, 100)) {
  // on enlève l'éventuelle marque de fin de ligne
  $ligne = $u->cutNewLineChar($ligne);
  // on récupère les 3 champs marié,enfants,salaire qui forment $ligne
  list($marié, $enfants, $salaire) = explode(",", $ligne);
  // on calcule l'impôt
  $impôt = $I->calculer($marié, $enfants, $salaire);
  // on inscrit le résultat
  fputs($résultats, "$marié:$enfants:$salaire:$impôt\n");
  // donnée suivante
}
// on ferme les fichiers
fclose($data);
fclose($résultats);

// fin
exit;

// ---------------------------------------------------------------------------------
// définition d'une classe Impôts
class Impôts {

  // attributs : les 3 tableaux de données
  private $limites;
  private $coeffR;
  private $coeffN;
  private $erreur;
  private $nbLimites;

  // getter
  public function getErreur(){
    return $this->erreur;
  }
  // constructeur

  function __construct($HOTE, $USER, $PWD, $BASE, $TABLE) {
    // initialise les attributs $limites, $coeffR, $coeffN
    // les données nécessaires au calcul de l'impôt ont été placées dans la table mysql $TABLE
    // appartenant à la base $BASE. La table a la structure suivante
    // limites decimal(10,2), coeffR decimal(6,2), coeffN decimal(10,2)
    // la connexion à la base mysql de la machine $HOTE se fait sous l'identité ($USER,$PWD)
    // initialise le champ $erreur avec une éventuelle erreur
    // vide si pas d'erreur
    // 
    // connexion à la base mysql
    $DSN = "mysql:host=$HOTE;dbname=$BASE";
    list($erreur, $connexion) = connecte($DSN, $USER, $PWD);
    if ($erreur) {
      $this->erreur = "Erreur lors de la connexion à MySql sous l'identité ($HOTE,$USER,$PWD) : $erreur\n";
      return;
    }
    // lecture de la table $TABLE
    $requête = "select limites,coeffR,coeffN from $TABLE";
    // exécute la requête $requête sur la connexion $connexion
    try {
      $statement = $connexion->prepare($requête);
      $statement->execute();
      // exploitation du résultat de la requête
      while ($colonnes = $statement->fetch()) {
        $this->limites[] = $colonnes[0];
        $this->coeffR[] = $colonnes[1];
        $this->coeffN[] = $colonnes[2];
      }
      // pas d'erreur
      $this->erreur = "";
      // nombre d'élements du tableau limites
      $this->nbLimites = count($this->limites);
    } catch (PDOException $e) {
      $this->erreur = $e->getMessage();
    }
    // déconnexion
    déconnecte($connexion);
  }
  // --------------------------------------------------------------------------
  function calculer($marié, $enfants, $salaire) {
    // $marié : oui, non
    // $enfants : nombre d'enfants
    // $salaire : salaire annuel
    
  // l'objet est-il dans un état correct ?
    if ($this->erreur)
      return -1;

    // nombre de parts
    ...
  }

}

// ---------------------------------------------------------------------------------
// une classe de fonctions utilitaires
class Utilitaires {

  function cutNewLinechar($ligne) {
    // on supprime la marque de fin de ligne de $ligne si elle existe
    ...
  }

}
// ---------------------------------------------------------------------------------
function connecte($dsn, $login, $pwd) {
  // connecte ($login,$pwd) à la base $dsn
  // rend l'id de la connexion ainsi qu'un msg d'erreur
  ...
}

//connecte
// ---------------------------------------------------------------------------------
function déconnecte($connexion) {
  // ferme la connexion identifiée par $connexion
  ...
}

Résultats : les mêmes qu'avec les versions précédentes.

Commentaires

Les nouveautés sont lignes 98-109 :

  • ligne 99 : l'ordre SQL select qui va permettre de récupérer les données nécessaires au calcul de l'impôt.
  • ligne 102 : préparation de l'ordre SQL select
  • ligne 103 : exécution de l'ordre préparé
  • lignes 105-109 : exploitation ligne par ligne de la table résultat du select