8. Exercício prático – Versão 3
Vamos revisitar o exercício que abordámos anteriormente (secções 4.3 e 4.4) para o resolver utilizando código PHP que emprega uma classe.
8.1. A estrutura do diretório do script

8.2. A exceção [ExceptionImpots]
Na versão 03, quando um construtor ou método de classe encontra um erro, lança a seguinte exceção [ExceptionImpots]:
Comentários
- linha 4: a classe [ExceptionImpots] está no namespace [Application];
- linha 6: a classe [ExceptionImpots] estende a classe PHP predefinida [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 de administração fiscal foram recolhidos:
- primeiro num ficheiro JSON;
- depois, a partir desse ficheiro JSON, para uma matriz associativa;
Na versão 03, os dados de administração fiscal continuam no ficheiro [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, este ficheiro era utilizado para inicializar um tabela associativa. Na versão 03, o ficheiro irá inicializar a seguinte classe [TaxAdminData]:
<?php
namespace Application;
class TaxAdminData {
// tax brackets
private $limites;
private $coeffR;
private $coeffN;
// tax calculation constants
private $plafondQfDemiPart;
private $plafondRevenusCelibatairePourReduction;
private $plafondRevenusCouplePourReduction;
private $valeurReducDemiPart;
private $plafondDecoteCelibataire;
private $plafondDecoteCouple;
private $plafondImpotCouplePourDecote;
private $plafondImpotCelibatairePourDecote;
private $abattementDixPourcentMax;
private $abattementDixPourcentMin;
// initialization
public function setFromJsonFile(string $taxAdminDataFilename): TaxAdminData {
// retrieve the contents of the tax data file
$fileContents = \file_get_contents($taxAdminDataFilename);
$erreur = FALSE;
// mistake?
if (!$fileContents) {
// we note the error
$erreur = TRUE;
$message = "Le fichier des données [$taxAdminDataFilename] n'existe pas";
}
if (!$erreur) {
// retrieve the jSON code from the configuration file in an associative array
$arrayTaxAdminData = \json_decode($fileContents, true);
// mistake?
if ($arrayTaxAdminData === FALSE) {
// we note the error
$erreur = TRUE;
$message = "Le fichier de données jSON [$taxAdminDataFilename] n'a pu être exploité correctement";
}
}
// mistake?
if ($erreur) {
// throw an exception
throw new ExceptionImpots($message);
}
// initialization of class attributes
foreach ($arrayTaxAdminData as $key => $value) {
$this->$key = $value;
}
// check that all keys have been initialized
$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é");
}
}
// we check that we only have real values
foreach ($this as $key => $value) {
// $value must be a real number >=0 or an array of reals >=0
$result = $this->check($value);
// mistake?
if ($result->erreur) {
// throw an exception
throw new ExceptionImpots("La valeur de l'attribut [$key] est invalide");
} else {
// we note the value
$this->$key = $result->value;
}
}
// we return the object
return $this;
}
private function check($value): \stdClass {
…
return $result;
}
// toString
public function __toString() {
// object's Json string
return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
}
// getters and 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: as propriedades que irão conter as propriedades com o mesmo nome do ficheiro JSON [taxadmindata.json]. Este é um ponto importante: as propriedades da classe [TaxAdminData] são idênticas às do ficheiro JSON [taxadmindata.json]. Esta característica facilita muito a escrita do código;
- A classe [TaxAdminData] não tem construtor. Em PHP, não é possível ter múltiplos construtores. Definir um impede, portanto, que o objeto seja inicializado de qualquer outra forma. Daqui em diante, as nossas classes não terão um construtor, mas terão, em vez disso, vários métodos do tipo [setFromSomething] que permitirão que sejam inicializadas de diferentes formas. Um objeto do tipo [TaxAdminData] é então construído utilizando a expressão:
- linha 23: o método [setFromJsonFile] inicializa os atributos da classe com os de mesmo nome no ficheiro [$jsonFilename];
- linhas 24–42: O ficheiro JSON é analisado para construir a matriz associativa [$arrayTaxAdminData]. Já vimos este código no script [main.php] da versão 02;
- linhas 44–47: se ocorrer um erro durante o processamento do ficheiro JSON, é lançada uma exceção. Esta exceção propagar-se-á até ao script principal [main.php];
- linhas 48–51: Os atributos da classe são inicializados. Aqui, aproveitamos o facto de a matriz associativa [$arrayTaxAdminData] e a classe [TaxAdminData] terem atributos com os mesmos nomes que os valores do ficheiro JSON;
- linhas 53–57: Verificamos 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, é importante compreender que o processo de inicialização nas linhas 48–51 pode ter adicionado atributos ao objeto [$this]. Assim, se escrevermos:
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 nas linhas 6–20 fazem, de facto, parte do objeto [$this], mas podem não ter sido inicializados. Este é um erro fácil de cometer; basta um erro ortográfico no nome de um atributo no ficheiro [taxadmindata.json];
- linhas 54–57: percorremos todos os atributos de [$this] e, se algum deles não tiver sido inicializado, lançamos uma exceção;
- um atributo pode ser inicializado com um valor incorreto. Em PHP, não é possível especificar um tipo para os atributos. Assim, a operação:
é possível, embora o atributo [$plafondQfDemiPart] deva ser um número real;
- linhas 59–71: verificamos se cada um dos atributos da classe tem um número real positivo ou o valor zero. A função [check] na linha 76 executa esta tarefa. O seu parâmetro [$value] é um valor único ou uma matriz de valores;
- linha 62: a função [check] devolve um objeto do tipo [\stdClass] com dois atributos:
- [error]: TRUE se ocorreu um erro, FALSE caso contrário;
- [value]: o valor numérico real correspondente ao parâmetro [$value] passado na linha 62;
- linha 64: verificamos 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çada uma exceção;
- linha 69: caso contrário, registamos o seu valor numérico;
- linha 73: devolvemos o objeto [$this] como resultado;
A função [check] é a seguinte:
private function check($value): \stdClass {
// $value est soit un tableau d'éléments soit un unique élément
// on crée un tableau
if (!\is_array($value)) {
$tableau = [$value];
} else {
$tableau = $value;
}
// on transforme le tableau d'éléments de type non connu en tableau de réels
$newTableau = [];
$result = new \stdClass();
// les éléments du tableau doivent être des nombres décimaux positifs ou nuls
$modèle = '/^\s*([+]?)\s*(\d+\.\d*|\.\d+|\d+)\s*$/';
for ($i = 0; $i < count($tableau); $i ++) {
if (preg_match($modèle, $tableau[$i])) {
// on met le float dans newTableau
$newTableau[] = (float) $tableau[$i];
} else {
// on note l'erreur
$result->erreur = TRUE;
// on quitte
return $result;
}
}
// on rend le résultat
$result->erreur = FALSE;
if (!\is_array($value)) {
// une seule valeur
$result->value = $newTableau[0];
} else {
// une liste de valeurs
$result->value = $newTableau;
}
return $result;
}
Comentários
- Linha 1: O parâmetro [$value] é uma matriz ou um único elemento. Além disso, o seu tipo é desconhecido. O valor provém do ficheiro [taxadmindata.json]. Dependendo dos valores introduzidos neste ficheiro, os valores lidos podem ser inteiros, números reais, cadeias de caracteres ou valores booleanos. Por exemplo:
"plafondQfDemiPart": 1551,
"plafondQfDemiPart": 1551.78,
"plafondQfDemiPart": "1551",
"plafondQfDemiPart": "xx",
No caso 1, o valor é do tipo [inteiro], no caso 2 do tipo [ponto flutuante], no caso 3 do tipo [cadeia de caracteres] que pode ser convertida num número e, no caso 4, do tipo [cadeia de caracteres] que não pode ser convertida num número;
- linhas 4–8: criamos uma matriz a partir do parâmetro [$value] recebido na linha 1;
- linha 10: a matriz que iremos preencher com números reais;
- linha 11: o resultado será um objeto do tipo [\stdClass];
- linha 13: expressão relacional para um número real positivo ou zero;
- linhas 14–24: verificamos se todos os elementos da matriz [$tableau] são números reais positivos ou zero e preenchemos a matriz [$newTableau] com esses elementos convertidos para o tipo [float] (linha 17);
- linhas 18–23: assim que for detetado um elemento que não seja um número real positivo ou zero, o erro é registado no resultado e este é devolvido;
- linhas 25–34: caso em que todos os elementos da matriz [$tableau] foram declarados válidos;
- Linha 32: O valor devolvido [$result→value] é uma matriz de floats ou um único float;
A função [__toString] nas linhas 82–85 devolve a cadeia JSON dos atributos e valores do objeto [$this].
Linhas 87–110: os getters e setters da classe;
Nota: Por vezes, pode ser um pouco tedioso ter de escrever todos os métodos get/set para uma classe, especialmente quando existem muitas propriedades. O NetBeans pode gerar automaticamente estes métodos, bem como o construtor. Para tal, basta selecionar as propriedades [1]:

- em [2], clique com o botão direito do rato no local onde pretende inserir o código e, em seguida, selecione a opção [Inserir Código];

- em [4], indique que pretende gerar o construtor;
- em [5], marque todos os atributos: isto significa que pretende que o construtor tenha um parâmetro para cada atributo;
- em [6], selecione o estilo de construtor Java;
- em [7], especifique que pretende explicitamente a palavra-chave [public] antes do construtor;
- Em [8], clique em OK;

- Em [9], o NetBeans gerou o construtor. No entanto, não foi possível especificar os tipos de parâmetros porque não os conhece. Adicione-os você mesmo [10];
Para gerar os getters e setters, repita os passos 2–4 e, no passo 4, selecione [Getter e Setter]:

- em [5], especifique que pretende getters e setters para cada um dos atributos;
- Em [6], especifique que pretende os getters e setters no estilo utilizado pelo Java: setAttribute, getAttribute;
- Em [7], especifique que estes getters e setters devem ser públicos;
- em [8], clique em OK;

- Em [9], os getters e setters gerados pelo NetBeans;
Elimine estes getters e setters e repita os passos 2 a 7.
- Em [8], marque a opção [Fluent Setter] que não marcámos anteriormente;
O resultado é o seguinte:

Cada setter termina com uma operação [return $this]. Isto permite que os atributos sejam inicializados da seguinte forma:
Na verdade, o valor de [$data→setLimites($limites)] (linha 32 do código) é [$this], ou seja, aqui [$data]. Podemos, portanto, chamar o método [setCoeffR($coeffR)] deste objeto e assim por diante, uma vez que, por sua vez, este método também retorna [$this] (linha 37 do código). Este estilo de escrita de métodos de classe, em que métodos que não deveriam retornar nada retornam, em vez disso, o objeto [$this], é chamado de escrita fluente. Isso torna estes métodos mais fáceis de usar.
8.4. A interface [InterfaceImpots]
Definimos agora a seguinte interface [InterfaceImpots] [InterfaceImpots.php]:
<?php
// namespace
namespace Application;
interface InterfaceImpots {
// retrieve tax bracket data for tax calculations
// can throw the ExceptionImpots exception
public function getTaxAdminData(): TaxAdminData;
// the interface knows how to calculate a tax
public function calculerImpot(string $marié, int $enfants, int $salaire): array;
// the interface can process data in text files
// $usersFilename: user data file in the form of marital status, number of children, annual salary
// $resultsFilename: results file with marital status, number of children, annual salary, tax amount
// $errorsFilename: file of errors encountered
// can throw the ExceptionImpots exception
public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void;
}
Comentários
- linha 4: a interface é colocada no namespace [Application];
- linha 6: a interface para o cálculo de impostos;
- linha 10: o método [getTaxAdminData] irá recuperar dados da administração fiscal para um objeto do tipo [TaxAdminData], que acabámos de introduzir. Uma vez que estes dados podem estar num ficheiro, numa base de dados ou mesmo na rede, o método [getTaxAdminData] pode falhar na recuperação dos dados. Nesse caso, lançará uma exceção [ExceptionImpots]. Este é o método padrão na programação orientada a objetos para sinalizar um erro encontrado num método ou construtor;
- linha 13: o método [calculateTax] irá calcular o imposto de um utilizador;
- linha 20: o método [executeBatchImpots] calculará o imposto para vários contribuintes:
- [$usersFileName] é o nome do ficheiro de texto que contém os dados dos contribuintes;
- [$resultsFileName] é o nome do ficheiro de texto que contém os montantes de imposto para estes contribuintes;
- [$errorsFileName] é o nome do ficheiro de texto que contém os erros encontrados durante o processamento destes ficheiros;
O conteúdo do ficheiro de texto [$usersFileName] pode ter o seguinte aspeto:
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
Note que as linhas 5 e 7 contêm entradas incorretas.
O conteúdo do ficheiro de texto [$resultsFileName] será então o seguinte:
e o que se encontra no ficheiro de texto [$errorsFileName] é o seguinte:
8.5. A classe [Utilities]
Também definimos uma classe [Utilities] num ficheiro chamado [Utilities.php]:
<?php
// namespace
namespace Application;
// a class of utility functions
abstract class Utilitaires {
public static function cutNewLinechar(string $ligne): string {
// delete the end-of-line mark from $ligne if it exists
$longueur = strlen($ligne); // line length
while (substr($ligne, $longueur - 1, 1) == "\n" or substr($ligne, $longueur - 1, 1) == "\r") {
$ligne = substr($ligne, 0, $longueur - 1);
$longueur--;
}
// end - return the line
return($ligne);
}
}
Comentários
- linha 4: a classe [Utilities] também é colocada no namespace [Examples];
- linha 9: o método [cutNewLineChar] remove qualquer caractere de fim de linha do texto que lhe é passado como parâmetro. Ele devolve a nova linha assim formada. Note-se que este é um método estático, o que significa que será chamado na forma [Utilities::cutNewLineChar];
8.6. A classe abstrata [AbstractBaseImpots]
A interface [InterfaceImpots] será implementada pela seguinte classe abstrata [AbstractBaseImpots] [AbstractBaseImpots.php]:
<?php
// namespace
namespace Application;
// definition of an abstract class AbstractBaseImpots
abstract class AbstractBaseImpots implements InterfaceImpots {
// tax administration data
private $taxAdminData = NULL;
// data required for tax calculation
abstract function getTaxAdminData(): TaxAdminData;
// tAX CALCULATION
// --------------------------------------------------------------------------
public function calculerImpot(string $marié, int $enfants, int $salaire): array {
// $marié : yes, no
// $enfants : number of children
// $salaire: annual salary
// $this->taxAdminData: tax administration data
//
// we check that we have the correct data from the tax authorities
if ($this->taxAdminData === NULL) {
$this->taxAdminData = $this->getTaxAdminData();
}
// tax calculation with children
$result1 = $this->calculerImpot2($marié, $enfants, $salaire);
$impot1 = $result1["impôt"];
// tax calculation without children
if ($enfants != 0) {
$result2 = $this->calculerImpot2($marié, 0, $salaire);
$impot2 = $result2["impôt"];
// application of the family allowance ceiling
$plafonDemiPart = $this->taxAdminData->getPlafondQfDemiPart();
if ($enfants < 3) {
// $PLAFOND_QF_DEMI_PART euros for the first 2 children
$impot2 = $impot2 - $enfants * $plafonDemiPart;
} else {
// $PLAFOND_QF_DEMI_PART euros for the first 2 children, double for subsequent children
$impot2 = $impot2 - 2 * $plafonDemiPart - ($enfants - 2) * 2 * $plafonDemiPart;
}
} else {
$impot2 = $impot1;
$result2 = $result1;
}
// we take the highest tax
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"];
}
// calculation of any discount
$décôte = $this->getDecôte($marié, $salaire, $impot);
$impot -= $décôte;
// calculation of any tax reduction
$réduction = $this->getRéduction($marié, $salaire, $enfants, $impot);
$impot -= $réduction;
// result
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 {
…
// result
return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
}
// revenuImposable=annualwage-discount
// the allowance has a minimum and a maximum
private function getRevenuImposable(float $salaire): float {
…
// result
return floor($revenuImposable);
}
// calculates any discount
private function getDecôte(string $marié, float $salaire, float $impots): float {
…
// result
return ceil($décôte);
}
// calculates any reduction
private function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {
…
// result
return ceil($réduction);
}
public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void {
…
}
}
Comentários
- linha 4: a classe [AbstractBaseImpots] estará no namespace [Application], tal como os outros elementos da aplicação que está a ser escrita;
- 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 este método: vimos um exemplo em que os dados da administração fiscal foram obtidos de um ficheiro JSON na secção anterior. Veremos outro caso em que os dados serão recuperados de uma base 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 torna automaticamente a própria classe abstrata (linha 7);
- linhas 15–64: a função de cálculo de impostos já encontrada nas secções link e link;
- A versão 02 armazenava os dados de administração fiscal numa matriz associativa [$taxAdminData]. A versão 03 armazena-os no atributo [$this→taxAdminData]. A primeira diferença entre estas duas abordagens é a visibilidade dos dados fiscais:
- na versão 02, a matriz associativa [$taxAdminData] não tinha visibilidade global. Por isso, era passada como parâmetro a todas as funções de cálculo de impostos;
- na versão 03, o atributo [$this→taxAdminData] tem visibilidade global para todos os métodos da classe. Por conseguinte, não é passado como parâmetro para todas as funções de cálculo de impostos;
- Uma segunda diferença decorre do facto de a versão 03 substituir funções por métodos de classe. Cada chamada de método é agora feita utilizando a expressão [$this→getMethod(…)] (linhas 27, 31, 57, 60);
- uma terceira diferença é que, quando o método [calculateTax] inicia o seu trabalho, não sabe se o atributo [private $taxAdminData] de que necessita foi inicializado. Isto deve-se ao facto de o construtor da classe não o inicializar. Cabe, portanto, ao método [calculateTax] fazê-lo utilizando o método [getTaxAdminData] na linha 12. É isso que é feito nas linhas 23–25;
- para além destas diferenças, os métodos de cálculo de impostos permanecem os mesmos das versões anteriores;
A função [executeBatchImpots] é a seguinte:
public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void {
// pas mal d'erreurs peuvent se produire dès qu'on gère des fichiers
try {
// ouverture fichier des erreurs
$errors = fopen($errorsFileName, "w");
if (!$errors) {
throw new ExceptionImpots("Impossible de créer le fichier des erreurs [$errorsFileName]", 10);
}
// ouverture fichier des résultats
$results = fopen($resultsFileName, "w");
if (!$results) {
throw new ExceptionImpots("Impossible de créer le fichier des résultats [$resultsFileName]", 11);
}
// lecture des données utilisateur
// chaque ligne a la forme statut marital, nombre d'enfants, salaire annuel
$data = fopen($usersFileName, "r");
if (!$data) {
throw new ExceptionImpots("Impossible d'ouvrir en lecture les déclarations des contribuables [$usersFileName]", 12);
}
// on exploite la ligne courante du fichier des données utilisateur
// qui a la forme statut marital, nombre d'enfants, salaire annuel
$num = 1; // n° ligne courante
$nbErreurs = 0; // nbre d'erreurs rencontrées
while ($ligne = fgets($data, 100)) {
// debug
// print "ligne n° " . ($i + 1) . " : " . $ligne;
// on enlève l'éventuelle marque de fin de ligne
$ligne = Utilitaires::cutNewLineChar($ligne);
// on récupère les 3 champs marié:enfants:salaire qui forment $ligne
list($marié, $enfants, $salaire) = explode(",", $ligne);
// on les vérifie
// le statut marital doit être oui ou non
$marié = trim(strtolower($marié));
$erreur = ($marié !== "oui" and $marié !== "non");
if (!$erreur) {
// le nombre d'enfants doit être un entier
$enfants = trim($enfants);
if (!preg_match("/^\s*\d+\s*$/", $enfants)) {
$erreur = TRUE;
} else {
$enfants = (int) $enfants;
}
}
if (!$erreur) {
// le salaire est un entier sans les centimes d'euros
$salaire = trim($salaire);
if (!preg_match("/^\s*\d+\s*$/", $salaire)) {
$erreur = TRUE;
} else {
$salaire = (int) $salaire;
}
}
// erreur ?
if ($erreur) {
fputs($errors, "la ligne [$num] du fichier [$usersFileName] est erronée\n");
$nbErreurs++;
} else {
// on calcule l'impôt
$result = $this->calculerImpot($marié, (int) $enfants, (int) $salaire);
// on inscrit le résultat dans le fichier des résultats
$result = ["marié" => $marié, "enfants" => $enfants, "salaire" => $salaire] + $result;
fputs($results, \json_encode($result, JSON_UNESCAPED_UNICODE) . "\n");
}
// ligne suivante
$num++;
}
// des erreurs ?
if ($nbErreurs > 0) {
throw new ExceptionImpots("Il y a eu des erreurs", 15);
}
} catch (ExceptionImpots $ex) {
// on relance l'exception
throw $ex;
} finally {
// on ferme tous les fichiers
fclose($data);
fclose($results);
fclose($errors);
}
}
Comentários de código
- linha 1: a função recebe três parâmetros:
- [$usersFileName]: o nome do ficheiro de texto que contém os dados dos contribuintes. Cada linha de texto contém os dados de um contribuinte no seguinte formato: estado civil (sim/não), número de filhos, salário anual:
- (continuação)
- [$resultsFileName]: o nome do ficheiro de texto que irá conter os resultados. Cada linha de texto terá o seguinte formato:
- (continuação)
- [$errorsFileName]: o nome do ficheiro de texto que contém os erros:
la ligne [5] du fichier [taxpayersdata.txt] est erronée
la ligne [7] du fichier [taxpayersdata.txt] est erronée
- linha 3: uma vez que várias operações podem lançar uma exceção, um bloco try / catch / finally envolve todo o código do método;
- linhas 3–19: os três ficheiros são abertos. É lançada uma exceção assim que uma operação de abertura falha;
- linha 24: as linhas do ficheiro [$data] são lidas uma a uma em blocos de até 100 caracteres (todas as linhas têm menos de 100 caracteres);
- linha 28: o método estático [Utilities::cutNewLineChar] é utilizado para remover quaisquer caracteres de fim de linha;
- linha 30: os três elementos da linha lida são recuperados;
- linhas 33–52: verifica-se a validade dos três elementos. Aqui, não é lançada uma exceção se ocorrer um erro, mas a mensagem de erro é gravada no ficheiro de texto [$errors] (linha 55);
- linha 59: se a linha lida for válida, o imposto é calculado. O resultado é obtido como uma matriz associativa ["tax" => floor($tax), "surcharge" => $surcharge, "discount" => $discount, "reduction" => $reduction, "rate" => $rate];
- linha 61: as chaves [casado, filhos, salário] são adicionadas ao resultado;
- linha 61: o resultado é gravado no ficheiro de texto [$results] na forma de uma cadeia JSON do resultado obtido;
- linhas 68–70: no final do processamento do ficheiro [$data], verificamos o número de linhas com erros encontradas. Se houver pelo menos uma, lançamos uma exceção;
- linhas 71–74: Capturamos a exceção que o código possa ter lançado e relançamo-la imediatamente (linha 73). O objetivo desta técnica é incluir um bloco [finally] nas linhas 74–79: independentemente de como a execução do código do método termine, os três ficheiros que possam ter sido abertos por este código são fechados. Fechar um ficheiro que não tenha sido aberto não causa um erro;
8.7. A classe [ImpotsWithTaxAdminDataInJsonFile]
A classe abstrata [AbstractBaseImpots] não implementa o método [getTaxAdminData] da interface [InterfaceImpots]. Por isso, temos de o definir numa classe derivada. Fazemos isso na seguinte classe derivada [ImpotsWithTaxAdminDataInJsonFile]:
<?php
// namespace
namespace Application;
// definition of a ImpotsWithDataInArrays class
class ImpotsWithTaxAdminDataInJsonFile extends AbstractBaseImpots {
// a Data attribute
private $taxAdminData;
// the manufacturer
public function __construct(string $jsonFileName) {
// initialize $this->taxAdminData from file jSON
$this->taxAdminData = (new TaxAdminData())->setFromJsonFile($jsonFileName);
}
// returns data for tax calculation
public function getTaxAdminData(): TaxAdminData {
// we make the attribute [$this->taxAdminData]
return $this->taxAdminData;
}
}
Comentários
- linha 7: a classe [ImpotsWithTaxAdminDataInJsonFile] estende a classe abstrata [AbstractBaseImpots]. Terá de definir o método [getTaxAdminData] que a 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 ficheiro JSON que contém os dados fiscais;
- linha 14: é criado e, em seguida, inicializado um objeto do tipo [TaxAdminData]. Esta operação pode lançar uma exceção do tipo [ExceptionImpots]. Esta exceção propagar-se-á até ao script principal [main.php];
- linhas 18–20: fornecemos um corpo para o método [getTaxAdminData], que a classe pai não tinha definido. Aqui, simplesmente devolvemos o atributo [$this->taxAdminData] inicializado pelo construtor;
8.8. O script [main.php]
Estas classes e interfaces são utilizadas pelo seguinte script [main.php]:
<?php
// strict adherence to declared types of function parameters
declare(strict_types = 1);
// namespace
namespace Application;
// interface and class inclusion
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";
// test -----------------------------------------------------
// definition of constants
const TAXPAYERSDATA_FILENAME = "taxpayersdata.txt";
const RESULTS_FILENAME = "resultats.txt";
const ERRORS_FILENAME = "errors.txt";
const TAXADMINDATA_FILENAME = "taxadmindata.json";
try {
// create a ImpotsWithTaxAdminDataInJsonFile object
$impots = new ImpotsWithTaxAdminDataInJsonFile(TAXADMINDATA_FILENAME);
// we execute the tax batch
$impots->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
// error is displayed
print $ex->getMessage() . "\n";
}
// end
print "Terminé\n";
exit();
Comentários
- linha 4: É imposta a adesão estrita aos tipos de parâmetros das funções;
- linha 7: o script [main.php] também é colocado no namespace [Application];
- linhas 10–15: Indicamos ao interpretador PHP onde se encontram as classes e interfaces utilizadas pelo script. Note-se que aqui não utilizámos uma instrução `use` para declarar os nomes completos das classes utilizadas pelo script. Isto é desnecessário porque o script e as classes estão no mesmo namespace [Application];
- linhas 18–22: os nomes dos ficheiros de texto utilizados no script;
- linhas 24–29: é criado um objeto [ImpotsWithTaxAdminDataInJsonFile] e quaisquer exceções são tratadas;
- linha 28: o método [executeBatchImpots] é executado, o qual calcula os impostos para todos os contribuintes no ficheiro [TAXPAYERSDATA_FILENAME]. Os resultados serão guardados no ficheiro [RESULTS_FILENAME] e quaisquer erros no ficheiro [ERRORS_FILENAME];
- linhas 29–32: em caso de erro fatal, a mensagem de erro é exibida;
Resultados
Com o seguinte ficheiro de contribuintes [taxpayersdata.txt]:
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
Obtemos o seguinte ficheiro 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 seguinte ficheiro de resultados [resultats.txt]: