Skip to content

8. Exercício prático – versão 3

Retomamos o exercício já estudado anteriormente (parágrafos 4.3 e 4.4) para resolvê-lo com um código PHP utilizando uma classe.

8.1. A estrutura dos scripts

Image

8.2. A exceção [ExceptionImpots]

Na versão 03, quando um construtor ou um método de classe encontrar um erro, ele lançará uma exceção do tipo [ExceptionImpots], conforme segue:

<?php

// espaço de nomes
namespace Application;

class ExceptionImpots extends \RuntimeException {

  public function __construct(string $message, int $code=0) {
    parent::__construct($message, $code);
  }

}

Comentários

  • linha 4: a classe [ExceptionImpots] está no espaço de nomes [Application];
  • linha 6: a classe [ExceptionImpots] estende a classe predefinida em PHP [RuntimeException];
  • linha 8: o construtor espera dois parâmetros:
    • $message: é a mensagem de erro associada à exceção;
    • $code: é o código de erro associado à exceção. Se não estiver presente, será utilizado o código 0;

8.3. A classe [TaxAdminData]

Na versão 02, os dados da administração fiscal foram reunidos:

  • primeiro em um arquivo jSON;
  • depois, desse arquivo jSON, em uma tabela associativa;

Na versão 03, os dados da administração fiscal continuam no arquivo [taxadmindata.json], mas com nomes de atributos diferentes:


{
    "limites": [
        9964,
        27519,
        73779,
        156244,
        0
    ],
    "coeffR": [
        0,
        0.14,
        0.3,
        0.41,
        0.45
    ],
    "coeffN": [
        0,
        1394.96,
        5798,
        13913.69,
        20163.45
    ],
    "plafondQfDemiPart": 1551,
    "plafondRevenusCelibatairePourReduction": 21037,
    "plafondRevenusCouplePourReduction": 42074,
    "valeurReducDemiPart": 3797,
    "plafondDecoteCelibataire": 1196,
    "plafondDecoteCouple": 1970,
    "plafondImpotCouplePourDecote": 2627,
    "plafondImpotCelibatairePourDecote": 1595,
    "abattementDixPourcentMax": 12502,
    "abattementDixPourcentMin": 437
}

Na versão 02, esse arquivo servia para inicializar uma tabela associativa. Na versão 03, o arquivo inicializará a seguinte classe [TaxAdminData]:


<?php

namespace Application;

class TaxAdminData {
  // faixas de imposto
  private $limites;
  private $coeffR;
  private $coeffN;
  // constantes de cálculo do imposto
  private $plafondQfDemiPart;
  private $plafondRevenusCelibatairePourReduction;
  private $plafondRevenusCouplePourReduction;
  private $valeurReducDemiPart;
  private $plafondDecoteCelibataire;
  private $plafondDecoteCouple;
  private $plafondImpotCouplePourDecote;
  private $plafondImpotCelibatairePourDecote;
  private $abattementDixPourcentMax;
  private $abattementDixPourcentMin;

  // inicialização
  public function setFromJsonFile(string $taxAdminDataFilename): TaxAdminData {
    // recuperação do conteúdo do arquivo de dados fiscais
    $fileContents = \file_get_contents($taxAdminDataFilename);
    $erreur = FALSE;
    // erro?
    if (!$fileContents) {
      // registra-se o erro
      $erreur = TRUE;
      $message = "Le fichier des données [$taxAdminDataFilename] n'existe pas";
    }
    if (!$erreur) {
      // recupera-se o código jSON do arquivo de configuração em uma tabela associativa
      $arrayTaxAdminData = \json_decode($fileContents, true);
      // erro?
      if ($arrayTaxAdminData === FALSE) {
        // registra-se o erro
        $erreur = TRUE;
        $message = "Le fichier de données jSON [$taxAdminDataFilename] n'a pu être exploité correctement";
      }
    }
    // erro?
    if ($erreur) {
      // lança-se uma exceção
      throw new ExceptionImpots($message);
    }
    // inicialização dos atributos da classe
    foreach ($arrayTaxAdminData as $key => $value) {
      $this->$key = $value;
    }
    // verifica-se se todas as chaves foram inicializadas
    $arrayOfAttributes = \get_object_vars($this);
    foreach ($arrayOfAttributes as $key => $value) {
      if (!isset($this->$key)) {
        throw new ExceptionImpots("L'attribut [$key] de [TaxAdminData] n'a pas été initialisé");
      }
    }
    // verifica-se se há apenas valores reais
    foreach ($this as $key => $value) {
      // $value deve ser um número real >=0 ou uma matriz de números reais >=0
      $result = $this->check($value);
      // erro?
      if ($result->erreur) {
        // lança-se uma exceção
        throw new ExceptionImpots("La valeur de l'attribut [$key] est invalide");
      } else {
        // registramos o valor
        $this->$key = $result->value;
      }
    }
    // retornamos o objeto
    return $this;
  }

  private function check($value): \stdClass {

    return $result;
  }

    // toString
  public function __toString() {
    // cadeia JSON do objeto
    return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
  }

  // getters e setters
  public function getLimites() {
    return $this->limites;
  }

  public function getCoeffR() {
    return $this->coeffR;
  }


  }

  public function setLimites($limites) {
    $this->limites = $limites;
    return $this;
  }

  public function setCoeffR($coeffR) {
    $this->coeffR = $coeffR;
    return $this;
  }



}

Comentários

  • linhas 6-20: os atributos que receberão os atributos com o mesmo nome dos arquivos jSON e [taxadmindata.json]. Este é um ponto importante: os atributos da classe [TaxAdminData] são idênticos aos dos arquivos jSON e [taxadmindata.json]. Essa particularidade facilita muito a escrita do código;
  • a classe [TaxAdminData] não possui construtor. Em PHP, não é possível ter vários construtores. Definir um deles impede, portanto, a inicialização do objeto de outra forma. Daqui em diante, nossas classes não terão construtor, mas sim vários métodos do tipo [setFromQqChose] que permitirão inicializá-las de diferentes maneiras. A construção de um objeto do tipo [TaxAdminData] é feita, então, com a expressão:
(new TaxAdminData())→setFromQqChose(…)
  • linha 23: o método [setFromJsonFile] inicializa os atributos da classe com aqueles de mesmo nome no arquivo [$jsonFilename];
  • linhas 24-42: o arquivo jSON é utilizado para construir a tabela associativa [$arrayTaxAdminData]. Já encontramos esse código no script [main.php] da versão 02;
  • linhas 44-47: se ocorrer um erro na processamento do arquivo jSON, é lançada uma exceção. Essa exceção será propagada até o script principal [main.php];
  • linhas 48-51: os atributos da classe são inicializados. Aproveita-se aqui o fato de que a tabela associativa [$arrayTaxAdminData] e a classe [TaxAdminData] possuem atributos com os mesmos nomes que os valores provenientes do arquivo jSON;
  • linhas 53-57: verifica-se se todos os atributos da classe [TaxAdminData] foram inicializados;
  • linha 53: a expressão [get_object_vars($this)] retorna um array associativo cujos atributos são os do objeto [$this], ou seja, os atributos da classe [TaxAdminData]. Aqui, é preciso entender que a operação de inicialização das linhas 48-51 pode ter adicionado atributos ao objeto [$this]. Assim, se escrevermos:
    $this->x = "1000";

então o atributo [x] é adicionado ao objeto [$this], mesmo que esse atributo não tenha sido declarado na classe [TaxAdminData]. O que é certo é que os atributos das linhas 6 a 20 fazem parte do objeto [$this], mas podem não ter sido inicializados. É um erro fácil de cometer; basta errar o nome de um atributo no arquivo [taxadmindata.json];

  • linhas 54-57: são verificados todos os atributos de [$this] e, se algum deles não tiver sido inicializado, é lançada uma exceção;
  • um atributo pode ser inicializado com um valor incorreto. No PHP, não é possível atribuir um tipo aos atributos. Assim, a operação:
$this→plafondQfDemiPart=’abcd’

é possível, embora o atributo [$plafondQfDemiPart] devesse ser real;

  • linhas 59-71: verifica-se se cada um dos atributos da classe possui um valor numérico real positivo ou nulo. É a função [check], na linha 76, que realiza essa tarefa. Seu parâmetro [$value] é um único valor ou uma matriz de valores;
  • linha 62: a função [check] retorna um objeto do tipo [\stdClass] com dois atributos:
    • [erreur]: igual a TRUE se houver erro; caso contrário, igual a FALSE;
    • [value]: o valor numérico real correspondente ao parâmetro [$value] passado como parâmetro, linha 62;
  • linha 64: verifica-se se a verificação foi bem-sucedida ou não;
  • linha 66: se um atributo não for um número real positivo ou zero, lança-se uma exceção;
  • linha 69: caso contrário, registra-se seu valor numérico;
  • linha 73: retorna-se o objeto [$this] como resultado;

A função [check] é a seguinte:


private function check($value): \stdClass {
    // $value é uma matriz de elementos ou um único elemento
    // cria-se um array
    if (!\is_array($value)) {
      $tableau = [$value];
    } else {
      $tableau = $value;
    }
    // transforma-se a matriz de elementos de tipo desconhecido em uma matriz de números reais
    $newTableau = [];
    $result = new \stdClass();
    // os elementos da matriz devem ser números decimais positivos ou nulos
    $modèle = '/^\s*([+]?)\s*(\d+\.\d*|\.\d+|\d+)\s*$/';
    for ($i = 0; $i < count($tableau); $i ++) {
      if (preg_match($modèle, $tableau[$i])) {
        // coloca-se o float em newTableau
        $newTableau[] = (float) $tableau[$i];
      } else {
        // registra-se o erro
        $result->erreur = TRUE;
        // sai do programa
        return $result;
      }
    }
    // retornamos o resultado
    $result->erreur = FALSE;
    if (!\is_array($value)) {
      // um único valor
      $result->value = $newTableau[0];
    } else {
      // uma lista de valores
      $result->value = $newTableau;
    }
    return $result;
  }

Comentários

  • linha 1: o parâmetro [$value] é uma matriz ou um único elemento. Além disso, não se sabe seu tipo. O valor provém do arquivo [taxadmindata.json]. De acordo com os valores registrados nesse arquivo, os valores lidos podem ser inteiros, reais, cadeias de caracteres ou booleanos. Por exemplo:

"plafondQfDemiPart": 1551,
"plafondQfDemiPart": 1551.78,
"plafondQfDemiPart": "1551",
"plafondQfDemiPart": "xx",

No caso 1, o valor é do tipo [entier]; no caso 2, do tipo [réel]; no caso 3, do tipo [string], que pode ser convertido em número; no caso 4, do tipo [string], que não pode ser convertido em número;

  • linhas 4-8: cria-se uma tabela a partir do parâmetro [$value] recebido na linha 1;
  • linha 10: o array será preenchido com números reais;
  • linha 11: o resultado será um objeto do tipo [\stdClass];
  • linha 13: expressão relacional de um número real positivo ou zero;
  • linhas 14-24: verifica-se se todos os elementos da matriz [$tableau] são números reais positivos ou nulos e preenche-se a matriz [$newTableau] com esses elementos convertidos para o tipo [float] (linha 17);
  • linhas 18-23: assim que for detectado um elemento que não seja um número real positivo ou zero, registra-se o erro no resultado e este é retornado;
  • linhas 25-34: caso em que todos os elementos da matriz [$tableau] foram declarados corretos;
  • linha 32: o valor retornado [$result→value] é uma matriz de números reais [float] ou um único número real;

A função [__toString] das linhas 82-85 retorna a cadeia jSON dos atributos e valores do objeto [$this].

Linhas 87-110: os getters e setters da classe;

Observação: às vezes pode ser um pouco trabalhoso ter que escrever todos os get/set de uma classe, especialmente quando há muitos atributos. O NetBeans pode gerá-los automaticamente, assim como o construtor. Para isso, basta selecionar os atributos [1]:

Image

  • em [2], clique com o botão direito do mouse onde deseja inserir o código e selecione a opção [Insert Code];

Image

  • em [4], indique que deseja gerar o construtor;
  • em [5], marque todos os atributos: isso significa que você deseja que o construtor tenha um parâmetro para cada um dos atributos;
  • em [6], adote o estilo dos construtores Java;
  • em [7], indique que deseja explicitamente a palavra-chave [public] antes do construtor;
  • em [8], confirme;

Image

  • em [9], o NetBeans gerou o construtor. No entanto, ele não conseguiu definir o tipo dos parâmetros porque não os conhece. Adicione-os você mesmo: [10];

Para gerar os getters e setters, repita as etapas 2 a 4 e, na etapa 4, selecione [Getter and Setter]:

Image

  • em [5], indique que deseja os getters e setters para cada um dos atributos;
  • em [6], indique que deseja os getters e setters no estilo usado pelo Java: setAttribut, getAttribut;
  • em [7], indique que deseja que esses getters e setters sejam públicos;
  • em [8], confirme;

Image

  • em [9], os getters e setters gerados pelo NetBeans;

Exclua esses getters e setters e repita as etapas 2 a 7.

  • em [8], marque a opção [Fluent Setter] que não havíamos marcado anteriormente;

O resultado obtido é o seguinte:

Image

Cada setter termina com uma operação [return $this]. Isso permite inicializar os atributos da seguinte maneira:

$data→setLimites($limites)→setCoeffR($coeffR)→setCoeffN($coeffN) ;

De fato, o valor de [$data→setLimites($limites)] (linha 32 do código) é [$this]; portanto, aqui é [$data]. Portanto, é possível chamar o método [setCoeffR($coeffR)] desse objeto e assim por diante, já que, por sua vez, esse método também retorna [$this] (linha 37 do código). Essa forma de escrever os métodos de uma classe, que faz com que os métodos que não deveriam retornar nada retornem o objeto [$this], é chamada de escrita fluente. Ela facilita o uso desses métodos.

8.4. A interface [InterfaceImpots]

Definimos agora a seguinte interface [InterfaceImpots] [InterfaceImpots.php]:


<?php

// espaço de nomes
namespace Application;

interface InterfaceImpots {

  // recuperar os dados das faixas de imposto que permitem o cálculo do imposto
  // pode lançar a exceção ExceptionImpots
  public function getTaxAdminData(): TaxAdminData;

  // a interface sabe calcular um imposto
  public function calculerImpot(string $marié, int $enfants, int $salaire): array;

  // a interface sabe processar dados em arquivos de texto
  // $usersFilename: arquivo de dados do usuário contendo estado civil, número de filhos e salário anual
  // $resultsFilename: arquivo de resultados contendo estado civil, número de filhos, salário anual e valor do imposto
  // $errorsFilename: arquivo de erros encontrados
  // pode gerar a exceção ExceptionImpots
  public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void;
}

Comentários

  • linha 4: a interface está localizada no espaço de nomes [Application];
  • linha 6: a interface que permite o cálculo dos impostos;
  • linha 10: o método [getTaxAdminData] permitirá obter os dados da administração fiscal em um objeto do tipo [TaxAdminData] que acabamos de apresentar. Como esses dados podem estar em um arquivo, em um banco de dados ou até mesmo na rede, o método [getTaxAdminData] pode não conseguir obter os dados. Nesse caso, ele lançará uma exceção do tipo [ExceptionImpots]. Esse é o método padrão na programação orientada a objetos para sinalizar um erro encontrado em um método ou construtor;
  • linha 13: o método [calculerImpot] permitirá calcular o imposto de um usuário;
  • linha 20: o método [executeBatchImpots] permitirá calcular o imposto de vários contribuintes:
    • [$usersFileName] é o nome do arquivo de texto que contém os dados dos contribuintes;
    • [$resultsFileName] é o nome do arquivo de texto que contém o valor do imposto para esses contribuintes;
    • [$errorsFileName] é o nome do arquivo de texto que contém os erros encontrados durante a processamento desses arquivos;

O conteúdo do arquivo de texto [$usersFileName] poderia ser o seguinte:


oui,2,55555
oui,2,50000
oui,3,50000
non,2,100000
non,3x,100000
oui,3,100000
oui,5,100000x
non,0,100000
oui,2,30000
non,0,200000
oui,3,200000

Observe-se que as linhas 5 e 7 contêm elementos incorretos.

O conteúdo do arquivo de texto [$resultsFileName] será, então, o seguinte:

1
2
3
4
5
6
7
8
9
{"marié":"oui","enfants":2,"salaire":55555,"impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14}
{"marié":"oui","enfants":2,"salaire":50000,"impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14}
{"marié":"oui","enfants":3,"salaire":50000,"impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14}
{"marié":"non","enfants":2,"salaire":100000,"impôt":19884,"surcôte":4480,"décôte":0,"réduction":0,"taux":0.41}
{"marié":"oui","enfants":3,"salaire":100000,"impôt":9200,"surcôte":2180,"décôte":0,"réduction":0,"taux":0.3}
{"marié":"non","enfants":0,"salaire":100000,"impôt":22986,"surcôte":0,"décôte":0,"réduction":0,"taux":0.41}
{"marié":"oui","enfants":2,"salaire":30000,"impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0}
{"marié":"non","enfants":0,"salaire":200000,"impôt":64210,"surcôte":7498,"décôte":0,"réduction":0,"taux":0.45}
{"marié":"oui","enfants":3,"salaire":200000,"impôt":42842,"surcôte":17283,"décôte":0,"réduction":0,"taux":0.41}

e o do arquivo de texto [$errorsFileName] será o seguinte:

la ligne 5 du fichier taxpayersdata.txt est erronée
la ligne 7 du fichier taxpayersdata.txt est erronée

8.5. A classe [Utilitaires]

Além disso, definimos uma classe [Utilitaires] em um arquivo [Utilitaires.php]:


<?php

// espaço de nomes
namespace Application;

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

  public static function cutNewLinechar(string $ligne): string {
    // a marca de fim de linha de $ligne é removida, caso exista
    $longueur = strlen($ligne);  // comprimento da linha
    while (substr($ligne, $longueur - 1, 1) == "\n" or substr($ligne, $longueur - 1, 1) == "\r") {
      $ligne = substr($ligne, 0, $longueur - 1);
      $longueur--;
    }
    // fim — a linha é restaurada
    return($ligne);
  }
}

Comentários

  • linha 4: a classe [Utilitaires] também está localizada no espaço de nomes [Exemples];
  • linha 9: o método [cutNewLinechar] remove o eventual caractere de fim de linha do texto que lhe foi passado como parâmetro. Ele retorna a nova linha assim formada. Observe-se que se trata de um método estático, ou seja, ele será chamado na forma [Utilitaires::cutNewLineChar];

8.6. A classe abstrata [AbstractBaseImpots]

A interface [InterfaceImpots] será implementada pela seguinte classe abstrata [AbstractBaseImpots]: [AbstractBaseImpots.php]:


<?php

// espaço de nomes
namespace Application;

// definição de uma classe abstrata AbstractBaseImpots
abstract class AbstractBaseImpots implements InterfaceImpots {
  // dados da administração fiscal
  private $taxAdminData = NULL;

  // dados necessários para o cálculo do imposto
  abstract function getTaxAdminData(): TaxAdminData;

// cálculo do imposto
// --------------------------------------------------------------------------
  public function calculerImpot(string $marié, int $enfants, int $salaire): array {
    // $marié: sim, não
    // $enfants: número de filhos
    // $salaire: salário anual
    // $this->taxAdminData: dados da administração fiscal
    //
    // verifica-se se os dados da administração fiscal estão corretos
    if ($this->taxAdminData === NULL) {
      $this->taxAdminData = $this->getTaxAdminData();
    }
    // cálculo do imposto com filhos
    $result1 = $this->calculerImpot2($marié, $enfants, $salaire);
    $impot1 = $result1["impôt"];
    // cálculo do imposto sem os filhos
    if ($enfants != 0) {
      $result2 = $this->calculerImpot2($marié, 0, $salaire);
      $impot2 = $result2["impôt"];
      // aplicação do limite máximo do quociente familiar
      $plafonDemiPart = $this->taxAdminData->getPlafondQfDemiPart();
      if ($enfants < 3) {
        // $PLAFOND_QF_DEMI_PART euros para os dois primeiros filhos
        $impot2 = $impot2 - $enfants * $plafonDemiPart;
      } else {
        // $PLAFOND_QF_DEMI_PART euros para os dois primeiros filhos, o dobro para os seguintes
        $impot2 = $impot2 - 2 * $plafonDemiPart - ($enfants - 2) * 2 * $plafonDemiPart;
      }
    } else {
      $impot2 = $impot1;
      $result2 = $result1;
    }
    // considera-se o imposto mais alto
    if ($impot1 > $impot2) {
      $impot = $impot1;
      $taux = $result1["taux"];
      $surcôte = $result1["surcôte"];
    } else {
      $surcôte = $impot2 - $impot1 + $result2["surcôte"];
      $impot = $impot2;
      $taux = $result2["taux"];
    }
    // cálculo de uma eventual dedução
    $décôte = $this->getDecôte($marié, $salaire, $impot);
    $impot -= $décôte;
    // cálculo de uma eventual redução de impostos
    $réduction = $this->getRéduction($marié, $salaire, $enfants, $impot);
    $impot -= $réduction;
    // resultado
    return ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux];
  }

// --------------------------------------------------------------------------
  private function calculerImpot2(string $marié, int $enfants, float $salaire): array {

    // resultado
    return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
  }

  // revenuImposable=salárioAnual-abatimento
  // a dedução tem um valor mínimo e um valor máximo
  private function getRevenuImposable(float $salaire): float {

    // resultado
    return floor($revenuImposable);
  }

// calcula uma eventual redução
  private function getDecôte(string $marié, float $salaire, float $impots): float {

    // resultado
    return ceil($décôte);
  }

// calcula uma eventual redução
  private function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {

    // resultado
    return ceil($réduction);
  }

  public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void {

  }

}

Comentários

  • linha 4: a classe [AbstractBaseImpots] estará no espaço de nomes [Application], assim como os demais elementos do aplicativo em desenvolvimento;
  • linha 7: a classe [AbstractBaseImpots] implementa a interface [InterfaceImpots];
  • linha 9: os dados da administração fiscal serão colocados no atributo [$taxAdminData];
  • linha 12: implementação do método [getTaxAdminData] da interface. Ainda não sabemos como definir esse método: vimos um exemplo em que os dados da administração fiscal foram obtidos de um arquivo jSON no parágrafo anterior. Veremos outro caso em que os dados deverão ser buscados em um banco de dados. Caberá às classes derivadas definir o conteúdo do método [getTaxAdminData]. Os dois casos anteriores darão origem a duas classes derivadas. O método [getTaxAdminData] é, portanto, declarado abstrato, o que automaticamente torna a própria classe abstrata (linha 7);
  • linhas 15-64: a função de cálculo do imposto já apresentada nos parágrafos link e link;
  • a versão 02 colocava os dados da administração fiscal em uma tabela associativa [$taxAdminData]. A versão 03 os coloca no atributo [$this→taxAdminData]. A primeira diferença entre essas duas soluções é uma diferença na visibilidade dos dados fiscais:
    • na versão 02, a tabela associativa [$taxAdminData] não tinha visibilidade global. Portanto, era passada como parâmetro para todas as funções de cálculo do imposto;
    • na versão 03, o atributo [$this→taxAdminData] possui visibilidade global para todos os métodos da classe. Portanto, ele não é passado como parâmetro para todas as funções de cálculo do imposto;
  • uma segunda diferença decorre do fato de que a versão 03 substitui funções por métodos de classe. Cada chamada de método é feita agora com uma expressão [$this→getMéthode(…)] (linhas 27, 31, 57, 60);
  • uma terceira diferença é que, quando o método [calculerImpot] inicia seu trabalho, ele não sabe se o atributo [private $taxAdminData] de que precisa foi inicializado. De fato, o construtor da classe não o inicializa. Cabe, portanto, ao método [calculerImpot] fazer isso por meio do método [getTaxAdminData] da linha 12. É isso que é feito nas linhas 23 a 25;
  • além dessas diferenças, os métodos de cálculo do imposto permanecem os mesmos das versões anteriores;

A função [executeBatchImpots] é a seguinte:


public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void {
    // podem ocorrer vários erros ao se trabalhar com arquivos
    try {
      // erros ao abrir o arquivo
      $errors = fopen($errorsFileName, "w");
      if (!$errors) {
        throw new ExceptionImpots("Impossible de créer le fichier des erreurs [$errorsFileName]", 10);
      }
      // abertura do arquivo de resultados
      $results = fopen($resultsFileName, "w");
      if (!$results) {
        throw new ExceptionImpots("Impossible de créer le fichier des résultats [$resultsFileName]", 11);
      }
      // leitura dos dados do usuário
      // cada linha tem o formato: estado civil, número de filhos, salário anual
      $data = fopen($usersFileName, "r");
      if (!$data) {
        throw new ExceptionImpots("Impossible d'ouvrir en lecture les déclarations des contribuables [$usersFileName]", 12);
      }
      // processa-se a linha atual do arquivo de dados do usuário
      // que tem o formato: estado civil, número de filhos, salário anual
      $num = 1;         // nº da linha atual
      $nbErreurs = 0;   // número de erros encontrados
      while ($ligne = fgets($data, 100)) {
        // depuração
        //  print "linha n° " . ($i + 1) . " : " . $ligne;
        // remove-se o eventual caractere de fim de linha
        $ligne = Utilitaires::cutNewLineChar($ligne);
        // recuperam-se os três campos casado:filhos:salário que formam $ligne
        list($marié, $enfants, $salaire) = explode(",", $ligne);
        // verifica-se se estão corretos
        // o estado civil deve ser “sim” ou “não”
        $marié = trim(strtolower($marié));
        $erreur = ($marié !== "oui" and $marié !== "non");
        if (!$erreur) {
          // o número de filhos deve ser um número inteiro
          $enfants = trim($enfants);
          if (!preg_match("/^\s*\d+\s*$/", $enfants)) {
            $erreur = TRUE;
          } else {
            $enfants = (int) $enfants;
          }
        }
        if (!$erreur) {
          // o salário é um número inteiro, sem centavos de euro
          $salaire = trim($salaire);
          if (!preg_match("/^\s*\d+\s*$/", $salaire)) {
            $erreur = TRUE;
          } else {
            $salaire = (int) $salaire;
          }
        }
        // erro?
        if ($erreur) {
          fputs($errors, "la ligne [$num] du fichier [$usersFileName] est erronée\n");
          $nbErreurs++;
        } else {
          // calcula-se o imposto
          $result = $this->calculerImpot($marié, (int) $enfants, (int) $salaire);
          // o resultado é registrado no arquivo de resultados
          $result = ["marié" => $marié, "enfants" => $enfants, "salaire" => $salaire] + $result;
          fputs($results, \json_encode($result, JSON_UNESCAPED_UNICODE) . "\n");
        }
        // próxima linha
        $num++;
      }
      // erros?
      if ($nbErreurs > 0) {
        throw new ExceptionImpots("Il y a eu des erreurs", 15);
      }
    } catch (ExceptionImpots $ex) {
      // relança-se a exceção
      throw $ex;
    } finally {
      // fecha-se todos os arquivos
      fclose($data);
      fclose($results);
      fclose($errors);
    }
  }

Comentários sobre o código

  • linha 1: a função recebe três parâmetros:
    • [$usersFileName]: o nome do arquivo de texto que contém os dados dos contribuintes. Cada linha do texto contém os dados de um contribuinte no seguinte formato: estado civil (sim/não), número de filhos, salário anual:
oui,2,55555
oui,2,50000
  • (continuação)
    • [$resultsFileName]: o nome do arquivo de texto que conterá os resultados. Cada linha de texto terá o seguinte formato:
{"marié":"oui","enfants":2,"salaire":50000,"impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14}
{"marié":"oui","enfants":3,"salaire":50000,"impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14}
  • (continuação)
    • [$errorsFileName]: o nome do arquivo de texto com os erros:

la ligne [5] du fichier [taxpayersdata.txt] est erronée
la ligne [7] du fichier [taxpayersdata.txt] est erronée
  • linha 3: como algumas operações podem gerar uma exceção, um bloco try/catch/finally envolve todo o código do método;
  • linhas 3-19: os três arquivos são abertos. Uma exceção é lançada assim que a abertura de um arquivo falha;
  • linha 24: as linhas do arquivo [$data] são lidas uma a uma, em blocos de no máximo 100 caracteres (todas as linhas têm menos de 100 caracteres);
  • linha 28: utiliza-se o método estático [Utilitaires::cutNewLineChar] para remover o eventual caractere de fim de linha;
  • linha 30: recuperam-se os três elementos da linha lida;
  • linhas 33-52: verifica-se a validade dos três elementos. Aqui, não se lança uma exceção caso haja erro, mas registra-se a mensagem do erro no arquivo de texto [$errors] (linha 55);
  • linha 59: se a linha lida for válida, o cálculo do imposto é realizado. Obtém-se um resultado na forma de uma tabela associativa ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux];
  • linha 61: ao resultado obtido, são adicionadas as chaves [marié, enfants, salaire];
  • linha 61: o resultado é gravado no arquivo de texto [$results] na forma da sequência jSON do resultado obtido;
  • linhas 68-70: ao final da análise do arquivo [$data], verifica-se o número de linhas com erros encontradas. Se houver pelo menos uma, lança-se uma exceção;
  • linhas 71-74: intercepta-se a exceção que o código possa ter lançado e ela é relançada imediatamente (linha 73). O objetivo desse recurso é poder ter uma cláusula [finally] nas linhas 74-79: independentemente de como a execução do código do método termine, os três arquivos que possam ter sido abertos por esse código são fechados. Fechar um arquivo que não foi aberto não causa erro;

8.7. A classe [ImpotsWithTaxAdminDataInJsonFile]

A classe abstrata [AbstractBaseImpots] não implementa o método [getTaxAdminData] da interface [InterfaceImpots]. Portanto, precisamos defini-lo em uma classe derivada. Fazemos isso na seguinte classe derivada [ImpotsWithTaxAdminDataInJsonFile]:


<?php

// espaço de nomes
namespace Application;

// definição de uma classe ImpotsWithDataInArrays
class ImpotsWithTaxAdminDataInJsonFile extends AbstractBaseImpots {
  // um atributo do tipo Data
  private $taxAdminData;

  // o construtor
  public function __construct(string $jsonFileName) {
    // inicializa-se $this->taxAdminData a partir do arquivo jSON
    $this->taxAdminData = (new TaxAdminData())->setFromJsonFile($jsonFileName);
  }

  // retorna os dados que permitem o cálculo do imposto
  public function getTaxAdminData(): TaxAdminData {
    // retorna o atributo [$this->taxAdminData]
    return $this->taxAdminData;
  }

}

Comentários

  • linha 7: a classe [ImpotsWithTaxAdminDataInJsonFile] estende a classe abstrata [AbstractBaseImpots]. Ela deverá definir o método [getTaxAdminData], que sua classe pai não definiu;
  • linha 9: o atributo [$taxAdminData] conterá os dados da administração fiscal;
  • linhas 12-15: o construtor recebe como único parâmetro o nome do arquivo jSON, que contém os dados fiscais;
  • linha 14: um objeto do tipo [TaxAdminData] é criado e, em seguida, inicializado. Essa operação pode lançar uma exceção do tipo [ExceptionImpots]. Essa exceção será propagada até o script principal [main.php];
  • linhas 18-20: define-se o corpo do método [getTaxAdminData], que a classe pai não havia definido. Aqui, basta fazer com que o atributo [$this->taxAdminData] seja inicializado pelo construtor;

8.8. O script [main.php]

Essas classes e interface são utilizadas pelo seguinte script [main.php]:


<?php

// respeito estrito aos tipos declarados dos parâmetros das funções
declare(strict_types = 1);

// espaço de nomes
namespace Application;

// inclusão de interfaces e classes
require_once __DIR__ . '/InterfaceImpots.php';
require_once __DIR__ . "/TaxAdminData.php";
require_once __DIR__ . '/ExceptionImpots.php';
require_once __DIR__ . '/Utilitaires.php';
require_once __DIR__ . '/AbstractBaseImpots.php';
require_once __DIR__ . "/ImpotsWithTaxAdminDataInJsonFile.php";

// teste -----------------------------------------------------
// definição de constantes
const TAXPAYERSDATA_FILENAME = "taxpayersdata.txt";
const RESULTS_FILENAME = "resultats.txt";
const ERRORS_FILENAME = "errors.txt";
const TAXADMINDATA_FILENAME = "taxadmindata.json";

try {
  // cria-se um objeto ImpotsWithTaxAdminDataInJsonFile
  $impots = new ImpotsWithTaxAdminDataInJsonFile(TAXADMINDATA_FILENAME);
  // executa-se o lote de impostos
  $impots->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
  // exibe-se o erro
  print $ex->getMessage() . "\n";
}
// fim
print "Terminé\n";
exit();


Comentários

  • linha 4: impõe-se o respeito estrito aos tipos dos parâmetros das funções;
  • linha 7: o script [main.php] também é colocado no espaço de nomes [Application];
  • linhas 10-15: indica-se ao interpretador PHP onde estão localizadas as classes e interfaces utilizadas pelo script. Observe que, neste caso, não utilizamos a instrução use para declarar o nome completo das classes utilizadas pelo script. Isso é desnecessário, pois o script e as classes estão no mesmo espaço de nomes [Application];
  • linhas 18-22: os nomes dos arquivos de texto utilizados no script;
  • linhas 24-29: é criado um objeto [ImpotsWithTaxAdminDataInJsonFile] e qualquer exceção é tratada;
  • linha 28: é executado o método [executeBatchImpots], que fará o cálculo dos impostos para todos os contribuintes do arquivo [TAXPAYERSDATA_FILENAME]. Os resultados serão gravados no arquivo [RESULTS_FILENAME] e os eventuais erros, no arquivo [ERRORS_FILENAME];
  • linhas 29-32: em caso de erro irrecuperável, exibe-se a mensagem de erro;

Resultados

Com o arquivo de contribuintes [taxpayersdata.txt] a seguir:


oui,2,55555
oui,2,50000
oui,3,50000
non,2,100000
non,3x,100000
oui,3,100000
oui,5,100000x
non,0,100000
oui,2,30000
non,0,200000
oui,3,200000

obtém-se o seguinte arquivo de erros [errors.txt]:


la ligne [5] du fichier [taxpayersdata.txt] est erronée
la ligne [7] du fichier [taxpayersdata.txt] est erronée

e o arquivo de resultados [resultats.txt] a seguir:

1
2
3
4
5
6
7
8
9
{"marié":"oui","enfants":2,"salaire":55555,"impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14}
{"marié":"oui","enfants":2,"salaire":50000,"impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14}
{"marié":"oui","enfants":3,"salaire":50000,"impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14}
{"marié":"non","enfants":2,"salaire":100000,"impôt":19884,"surcôte":4480,"décôte":0,"réduction":0,"taux":0.41}
{"marié":"oui","enfants":3,"salaire":100000,"impôt":9200,"surcôte":2180,"décôte":0,"réduction":0,"taux":0.3}
{"marié":"non","enfants":0,"salaire":100000,"impôt":22986,"surcôte":0,"décôte":0,"réduction":0,"taux":0.41}
{"marié":"oui","enfants":2,"salaire":30000,"impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0}
{"marié":"non","enfants":0,"salaire":200000,"impôt":64210,"surcôte":7498,"décôte":0,"réduction":0,"taux":0.45}
{"marié":"oui","enfants":3,"salaire":200000,"impôt":42842,"surcôte":17283,"décôte":0,"réduction":0,"taux":0.41}