Skip to content

8. Exercício IMPOTS com MySQL

Já escrevemos três versões deste exercício. A última versão utilizava uma classe de cálculo de impostos. Essa classe obtinha, a partir de um ficheiro de texto, os dados necessários para esse cálculo. Passará agora a obtê-los a partir de uma base de dados. Para tal, escrevemos um primeiro código que irá transferir os dados do ficheiro de texto para uma base de dados.

O ficheiro de texto impots.txt é o seguinte:

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

A base de dados a criar é a seguinte:

A base de dados chama-se [dbimpots] e possui uma única tabela, [impots]. É utilizada pelo utilizador [root] sem palavra-passe.

8.1. Transferência de um ficheiro de texto para uma tabela MySQL (txt2mysql)


<?php

// transfere o ficheiro de texto com os dados necessários para o cálculo dos impostos
// para uma tabela MySQL
// os dados
$IMPOTS = "impots.txt";
$BASE = "dbimpots";
$TABLE = "impots";
$USER = "root";
$PWD = "";
$HOTE = "localhost";

// os dados necessários para o cálculo do imposto foram colocados no ficheiro $IMPOTS
// à razão de uma linha por tabela, na forma
// val1:val2:val3,...
list($erreur, $limites, $coeffR, $coeffN) = getTables($IMPOTS);
// Houve algum erro?
if ($erreur) {
  print "$erreur\n";
  exit;
}//if
// transferimos estas tabelas para uma tabela MySQL
$erreur = copyToMysql($limites, $coeffR, $coeffN, $HOTE, $USER, $PWD, $BASE, $TABLE);
if ($erreur)
  print "$erreur\n";
else
  print "Transfert opéré\n";
// fim
exit;

// --------------------------------------------------------------------------
function copyToMysql($limites, $coeffR, $coeffN, $HOTE, $USER, $PWD, $BASE, $TABLE) {
  // copia as 3 tabelas numéricas $limites, $coeffR, $coeffN
  // para a tabela $TABLE da base de dados MySQL $BASE
  // a base de dados MySQL encontra-se no servidor $HOTE
  // o utilizador é identificado por $USER e $PWD
  // ligação
  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";
  // eliminação da tabela
  $requête = "drop table $TABLE";
  exécuteRequête($connexion, $requête);
  // criação da tabela
  $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)";
  // preenchimento
  for ($i = 0; $i < count($limites); $i++) {
    // consulta de inserção
    $requête = "insert into $TABLE (limites,coeffR,coeffN) values ($limites[$i],$coeffR[$i],$coeffN[$i])";
    // execução da consulta
    list($erreur, $res) = exécuteRequête($connexion, $requête);
    // retorno em caso de erro
    if ($erreur)
      return "$requête : erreur ($erreur)";
  }//for
  // concluído - a sessão é encerrada
  déconnecte($connexion);
  // retorno sem erro
  return "";
}
// --------------------------------------------------------------------------
function getTables($IMPOTS) {
  // $IMPOTS: o nome do ficheiro que contém os dados das tabelas $limites, $coeffR, $coeffN
  ...
  // fim
  return array("", $limites, $coeffR, $coeffN);
}
// --------------------------------------------------------------------------
function cutNewLinechar($ligne) {
  // elimina-se o marcador de fim de linha de $ligne, caso exista
  ...
  // fim
  return($ligne);
}
// ---------------------------------------------------------------------------------
function connecte($dsn, $login, $pwd) {
  // liga ($login,$pwd) à base de dados $dsn
  // retorna o ID da ligação, bem como uma mensagem de erro
  ...
}

//estabelece a ligação
// ---------------------------------------------------------------------------------
function déconnecte($connexion) {
  // encerra a ligação identificada por $connexion
  ...
}

// ---------------------------------------------------------------------------------
function exécuteRequête($connexion, $sql) {
  // executa a consulta $sql na ligação $connexion
  // retorna um array com 2 elementos ($erreur,$résultat)
 ...
}

Resultados no ecrã:

Transfert opéré

8.2. O programa de cálculo do imposto ( impots_04)

Esta versão utiliza uma classe Impôts que recupera de uma base de dados os valores necessários para o cálculo do imposto. Introduzimos aqui um novo conceito: o da consulta preparada. A execução de uma ordem SQL por uma ordem SGBD é realizada em duas etapas:

  1. a consulta é preparada: o SGBD prepara um plano de execução otimizado para a consulta. O objetivo é executá-la da forma mais eficiente possível.
  1. a consulta é executada.

Se uma mesma consulta for executada N vezes, as duas etapas anteriores são realizadas N vezes. No entanto, é possível preparar a consulta uma vez e executá-la N vezes. Para tal, é necessário utilizar consultas preparadas. Se $requête for a ordem SQL a executar e $connexion for o objeto PDO que representa a ligação:

  • $statement=$connexion->prepare($requête) prepara uma consulta e devolve a consulta «preparada»
  • $statement->execute() executa a consulta preparada.

Se a consulta preparada for uma ordem select, então

  • $statement->fetchAll() devolve todas as linhas da tabela resultante do select sob a forma de um tabulácio T, em que T[i,j] é o valor da coluna j da linha i da tabela
  • $statement->fetch() devolve a linha atual da tabela na forma de um tabuleiro T, em que T[j] é o valor da coluna j da linha

A consulta preparada oferece outras vantagens para além de uma maior eficiência. Proporciona, nomeadamente, maior segurança. Por isso, deve ser utilizada sistematicamente.


<?php

// teste -----------------------------------------------------
// definição das constantes
$DATA = "data.txt";
$RESULTATS = "resultats.txt";
$TABLE = "impots";
$BASE = "dbimpots";
$USER = "root";
$PWD = "";
$HOTE="localhost";

// os dados necessários para o cálculo do imposto foram colocados na tabela mysqL $TABLE
// pertencente à base de dados $BASE. A tabela tem a seguinte estrutura
// limites decimal(10,2), coeffR decimal(6,2), coeffN decimal(10,2)
// parâmetros dos contribuintes (estado civil, número de filhos, salário anual)
// foram inseridos no ficheiro de texto $DATA, à razão de uma linha por contribuinte
// os resultados (estado civil, número de filhos, salário anual, imposto a pagar) são inseridos no
// no ficheiro de texto $RESULTATS, à razão de um resultado por linha

// é criado um objeto «Impostos»
$I = new Impôts($HOTE, $USER, $PWD, $BASE, $TABLE);
// ocorreu algum erro?
$erreur=$I->getErreur();
if ($erreur) {
  print "$I->erreur\n";
  exit;
}//if
// é criado um objeto «utilitários»
$u = new Utilitaires();

// abertura do ficheiro com os dados dos contribuintes
$data = fopen($DATA, "r");
if (!$data) {
  print "Impossible d'ouvrir en lecture le fichier des données [$DATA]\n";
  exit;
}

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

// processa-se a linha atual do ficheiro de dados dos contribuintes
while ($ligne = fgets($data, 100)) {
  // remove-se a eventual marca de fim de linha
  $ligne = $u->cutNewLineChar($ligne);
  // recuperam-se os 3 campos «casado», «filhos» e «salário» que formam $ligne
  list($marié, $enfants, $salaire) = explode(",", $ligne);
  // calcula-se o imposto
  $impôt = $I->calculer($marié, $enfants, $salaire);
  // regista-se o resultado
  fputs($résultats, "$marié:$enfants:$salaire:$impôt\n");
  // dado seguinte
}
// fecha-se os ficheiros
fclose($data);
fclose($résultats);

// fim
exit;

// ---------------------------------------------------------------------------------
// definição de uma classe «Impostos»
class Impôts {

  // atributos: as 3 tabelas de dados
  private $limites;
  private $coeffR;
  private $coeffN;
  private $erreur;
  private $nbLimites;

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

  function __construct($HOTE, $USER, $PWD, $BASE, $TABLE) {
    // inicializa os atributos $limites, $coeffR, $coeffN
    // os dados necessários para o cálculo do imposto foram inseridos na tabela MySQL $TABLE
    // pertencente à base de dados $BASE. A tabela tem a seguinte estrutura
    // limites decimal(10,2), coeffR decimal(6,2), coeffN decimal(10,2)
    // a ligação à base de dados MySQL da máquina $HOTE é efetuada com a identidade ($USER,$PWD)
    // inicializa o campo $erreur com um eventual erro
    // fica vazio se não houver erro
    // 
    // ligação à base de dados 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;
    }
    // leitura da tabela $TABLE
    $requête = "select limites,coeffR,coeffN from $TABLE";
    // executa a consulta $requête na ligação $connexion
    try {
      $statement = $connexion->prepare($requête);
      $statement->execute();
      // análise do resultado da consulta
      while ($colonnes = $statement->fetch()) {
        $this->limites[] = $colonnes[0];
        $this->coeffR[] = $colonnes[1];
        $this->coeffN[] = $colonnes[2];
      }
      // sem erros
      $this->erreur = "";
      // número de elementos da tabela de limites
      $this->nbLimites = count($this->limites);
    } catch (PDOException $e) {
      $this->erreur = $e->getMessage();
    }
     // desligamento
    déconnecte($connexion);
  }
  // --------------------------------------------------------------------------
  function calculer($marié, $enfants, $salaire) {
    // $marié: sim, não
    // $enfants: número de filhos
    // $salaire: salário anual
    
  // o objeto está em bom estado?
    if ($this->erreur)
      return -1;

    // número de quotas
    ...
  }

}

// ---------------------------------------------------------------------------------
// uma classe de funções utilitárias
class Utilitaires {

  function cutNewLinechar($ligne) {
    // elimina-se o marcador de fim de linha de $ligne, caso exista
    ...
  }

}
// ---------------------------------------------------------------------------------
function connecte($dsn, $login, $pwd) {
  // liga ($login,$pwd) à base de dados $dsn
  // retorna o ID da ligação, bem como uma mensagem de erro
  ...
}

//estabelece a ligação
// ---------------------------------------------------------------------------------
function déconnecte($connexion) {
  // encerra a ligação identificada por $connexion
  ...
}

Resultados: os mesmos que nas versões anteriores.

Comentários

As novidades encontram-se nas linhas 98-109:

  • linha 99: a ordem SQL select, que permitirá recuperar os dados necessários para o cálculo do imposto.
  • linha 102: preparação da ordem SQL select
  • linha 103: execução da ordem preparada
  • linhas 105-109: análise linha a linha da tabela de resultados do select