Skip to content

8. Ejercicio IMPOTS con MySQL

Ya hemos escrito tres versiones de este ejercicio. La última versión utilizaba una clase para calcular el impuesto. Esa clase obtenía de un archivo de texto los datos necesarios para realizar el cálculo. A partir de ahora, los obtendrá de una base de datos. Para ello, escribimos un primer código que transferirá los datos del archivo de texto a una base de datos.

El archivo de texto impots.txt es el siguiente:

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 datos que debemos crear es la siguiente:

La base se llama [dbimpots] y tiene una única tabla: [impots]. La utiliza el usuario [root] sin contraseña.

8.1. Transferencia de un archivo de texto a una tabla MySQL (txt2mysql)


<?php

// transfiere el archivo de texto con los datos necesarios para el cálculo de impuestos
// a una tabla de MySQL
// los datos
$IMPOTS = "impots.txt";
$BASE = "dbimpots";
$TABLE = "impots";
$USER = "root";
$PWD = "";
$HOTE = "localhost";

// los datos necesarios para el cálculo del impuesto se han colocado en el archivo $IMPOTS
// a razón de una línea por tabla en el formato
// val1:val2:val3,...
list($erreur, $limites, $coeffR, $coeffN) = getTables($IMPOTS);
// ¿Ha ocurrido algún error?
if ($erreur) {
  print "$erreur\n";
  exit;
}//si
// se transfieren estas tablas a una tabla de 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) {
  // copia las 3 tablas numéricas $limites, $coeffR, $coeffN
  // en la tabla $TABLE de la base de datos MySQL $BASE
  // la base de datos MySQL se encuentra en la máquina $HOTE
  // el usuario se identifica con $USER y $PWD
  // conexión
  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";
  // eliminación de la tabla
  $requête = "drop table $TABLE";
  exécuteRequête($connexion, $requête);
  // creación de la tabla
  $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)";
  // llenado
  for ($i = 0; $i < count($limites); $i++) {
    // consulta de inserción
    $requête = "insert into $TABLE (limites,coeffR,coeffN) values ($limites[$i],$coeffR[$i],$coeffN[$i])";
    // ejecución de la consulta
    list($erreur, $res) = exécuteRequête($connexion, $requête);
    // retorno en caso de error
    if ($erreur)
      return "$requête : erreur ($erreur)";
  }//for
  // se acabó: nos desconectamos
  déconnecte($connexion);
  // regreso sin error
  return "";
}
// --------------------------------------------------------------------------
function getTables($IMPOTS) {
  // $IMPOTS: el nombre del archivo que contiene los datos de las tablas $limites, $coeffR, $coeffN
  ...
  // fin
  return array("", $limites, $coeffR, $coeffN);
}
// --------------------------------------------------------------------------
function cutNewLinechar($ligne) {
  // se elimina el marcador de fin de línea de $ligne si existe
  ...
  // fin
  return($ligne);
}
// ---------------------------------------------------------------------------------
function connecte($dsn, $login, $pwd) {
  // conecta ($login,$pwd) a la base $dsn
  // devuelve el ID de la conexión y un mensaje de error
  ...
}

//se conecta
// ---------------------------------------------------------------------------------
function déconnecte($connexion) {
  // cierra la conexión identificada por $connexion
  ...
}

// ---------------------------------------------------------------------------------
function exécuteRequête($connexion, $sql) {
  //ejecuta la consulta $sql en la conexión $connexion
  // devuelve una matriz de 2 elementos ($erreur, $résultat)
 ...
}

Resultados en pantalla:

Transfert opéré

8.2. El programa de cálculo de impuestos ( impots_04)

Esta versión utiliza una clase Impôts que busca en una base de datos los valores necesarios para el cálculo del impuesto. Aquí introducimos un nuevo concepto: el de consulta preparada. La ejecución de una orden SQL mediante una SGBD se realiza en dos etapas:

  1. se prepara la consulta: el SGBD preparará un plan de ejecución optimizado para la consulta. El objetivo es ejecutarla de la manera más eficiente posible.
  1. Se ejecuta la consulta.

Si una misma consulta se ejecuta N veces, los dos pasos anteriores se repiten N veces. Sin embargo, es posible preparar la consulta una sola vez y ejecutarla N veces. Para ello, hay que utilizar consultas preparadas. Si $requête es la orden SQL que se va a ejecutar y $connexion es el objeto PDO que representa la conexión:

  • $statement=$connexion->prepare($requête) prepara una consulta y devuelve la consulta «preparada»
  • $statement->execute() ejecuta la consulta preparada.

Si la consulta preparada es una orden select, entonces

  • $statement->fetchAll() devuelve todas las filas de la tabla resultante de select en forma de una matriz T, donde T[i,j] es el valor de la columna j de la fila i de la tabla
  • $statement->fetch() devuelve la fila actual de la tabla en forma de una matriz T, donde T[j] es el valor de la columna j de la fila

La consulta preparada ofrece otras ventajas además de una mayor eficiencia. En particular, brinda mayor seguridad. Por lo tanto, se debe utilizar de manera sistemática.


<?php

// prueba -----------------------------------------------------
// definición de las constantes
$DATA = "data.txt";
$RESULTATS = "resultats.txt";
$TABLE = "impots";
$BASE = "dbimpots";
$USER = "root";
$PWD = "";
$HOTE="localhost";

// los datos necesarios para el cálculo del impuesto se han colocado en la tabla mysqL $TABLE
// que pertenece a la base de datos $BASE. La tabla tiene la siguiente estructura
// límites decimal(10,2), coeffR decimal(6,2), coeffN decimal(10,2)
// los parámetros de los contribuyentes (estado civil, número de hijos, salario anual)
// se han incluido en el archivo de texto $DATA a razón de una línea por contribuyente
// los resultados (estado civil, número de hijos, salario anual, impuesto a pagar) se colocan en
// el archivo de texto $RESULTATS, a razón de un resultado por línea

// se crea un objeto Impuestos
$I = new Impôts($HOTE, $USER, $PWD, $BASE, $TABLE);
// ¿Hubo algún error?
$erreur=$I->getErreur();
if ($erreur) {
  print "$I->erreur\n";
  exit;
}//si
// se crea un objeto de utilidades
$u = new Utilitaires();

// apertura del archivo de datos de los contribuyentes
$data = fopen($DATA, "r");
if (!$data) {
  print "Impossible d'ouvrir en lecture le fichier des données [$DATA]\n";
  exit;
}

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

// se procesa la línea actual del archivo de datos de los contribuyentes
while ($ligne = fgets($data, 100)) {
  // se elimina el posible marcador de fin de línea
  $ligne = $u->cutNewLineChar($ligne);
  // se recuperan los 3 campos «casado», «hijos» y «salario» que forman $ligne
  list($marié, $enfants, $salaire) = explode(",", $ligne);
  // se calcula el impuesto
  $impôt = $I->calculer($marié, $enfants, $salaire);
  // se registra el resultado
  fputs($résultats, "$marié:$enfants:$salaire:$impôt\n");
  // dato siguiente
}
// se cierran los archivos
fclose($data);
fclose($résultats);

// fin
exit;

// ---------------------------------------------------------------------------------
// definición de una clase «Impuestos»
class Impôts {

  // atributos: las 3 tablas de datos
  private $limites;
  private $coeffR;
  private $coeffN;
  private $erreur;
  private $nbLimites;

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

  function __construct($HOTE, $USER, $PWD, $BASE, $TABLE) {
    // inicializa los atributos $limites, $coeffR, $coeffN
    // los datos necesarios para el cálculo del impuesto se han colocado en la tabla MySQL $TABLE
    //, que pertenece a la base de datos $BASE. La tabla tiene la siguiente estructura
    // límites decimal(10,2), coeffR decimal(6,2), coeffN decimal(10,2)
    // la conexión a la base de datos MySQL de la máquina $HOTE se realiza con la identidad ($USER,$PWD)
    // inicializa el campo $erreur con un posible error
    // se deja vacío si no hay error
    // 
    // conexión a la base de datos 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;
    }
    // lee la tabla $TABLE
    $requête = "select limites,coeffR,coeffN from $TABLE";
    // ejecuta la consulta $requête en la conexión $connexion
    try {
      $statement = $connexion->prepare($requête);
      $statement->execute();
      // procesamiento del resultado de la consulta
      while ($colonnes = $statement->fetch()) {
        $this->limites[] = $colonnes[0];
        $this->coeffR[] = $colonnes[1];
        $this->coeffN[] = $colonnes[2];
      }
      // sin errores
      $this->erreur = "";
      // número de elementos de la tabla de límites
      $this->nbLimites = count($this->limites);
    } catch (PDOException $e) {
      $this->erreur = $e->getMessage();
    }
     // desconexión
    déconnecte($connexion);
  }
  // --------------------------------------------------------------------------
  function calculer($marié, $enfants, $salaire) {
    // $marié: sí, no
    // $enfants: número de hijos
    // $salaire: salario anual
    
  // ¿El objeto se encuentra en buen estado?
    if ($this->erreur)
      return -1;

    // número de participaciones
    ...
  }

}

// ---------------------------------------------------------------------------------
// una clase de funciones de utilidad
class Utilitaires {

  function cutNewLinechar($ligne) {
    // se elimina el marcador de fin de línea de $ligne si existe
    ...
  }

}
// ---------------------------------------------------------------------------------
function connecte($dsn, $login, $pwd) {
  // conecta ($login, $pwd) a la base de datos $dsn
  // devuelve el ID de la conexión y un mensaje de error
  ...
}

//se conecta
// ---------------------------------------------------------------------------------
function déconnecte($connexion) {
  // cierra la conexión identificada por $connexion
  ...
}

Resultados: los mismos que con las versiones anteriores.

Comentarios

Las novedades se encuentran en las líneas 98-109:

  • línea 99: la orden SQL select, que permitirá recuperar los datos necesarios para el cálculo del impuesto.
  • línea 102: preparación de la orden SQL select
  • línea 103: ejecución de la orden preparada
  • líneas 105-109: procesamiento línea por línea de la tabla de resultados de select