Skip to content

8. Ejercicio IMPOTS con MySQL

Ya hemos escrito tres versiones de este ejercicio. La última, version, utilizaba una clase de cálculo de impuestos. Esta clase tomaba de un archivo de texto los datos necesarios para dicho cálculo. A partir de ahora, los tomará 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 hay que 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

// se transfiere el archivo de texto con los datos necesarios para el cálculo de los impuestos
// a una tabla 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);
// ¿Se ha producido algún error?
if ($erreur) {
  print "$erreur\n";
  exit;
}//if
// se transfieren estas tablas a una tabla 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 mysql se encuentra en la máquina $HOTE
  // el usuario está identificado por $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)";
  // relleno
  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 ha terminado - nos desconectamos
  déconnecte($connexion);
  // retorno 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 junto con 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)

Este version utiliza una clase Impuestos que busca en una base de datos los valores necesarios para el cálculo del impuesto. Introducimos aquí un nuevo concepto, el de consulta preparada. La ejecución de una orden SQL por parte de una SGBD se realiza en dos fases:

  1. se prepara la consulta: el SGBD preparará un plan de ejecución optimizado para la consulta. Se trata de ejecutarla de la forma más eficaz posible.
  2. Se ejecuta la consulta.

Si una misma consulta se ejecuta N veces, los dos pasos anteriores se realizan N veces. Sin embargo, es posible preparar la consulta una 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 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 la selección 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 aporta otras ventajas además de una mayor eficiencia. En particular, ofrece mayor seguridad. Por lo tanto, debería utilizarse sistemáticamente.


<?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
// perteneciente 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 colocado 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);
// ¿ha habido algún error?
$erreur=$I->getErreur();
if ($erreur) {
  print "$I->erreur\n";
  exit;
}//if
// 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 introduce 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
    // perteneciente a la base $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
    // 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;
    }
    // lectura de 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 matriz 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 $dsn
  // devuelve el id de la conexión junto con 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 la selección