8. Esercizio pratico – versione 3
Riprendiamo l’esercizio già studiato in precedenza (paragrafi 4.3 e 4.4) per risolverlo con un codice PHP che utilizza una classe.
8.1. Struttura ad albero degli script

8.2. L’eccezione [ExceptionImpots]
Nella versione 03, quando un costruttore o un metodo di classe incontra un errore, genera un’eccezione di tipo [ExceptionImpots] come segue:
Commenti
- riga 4: la classe [ExceptionImpots] si trova nello spazio dei nomi [Application];
- riga 6: la classe [ExceptionImpots] estende la classe predefinita in PHP [RuntimeException];
- riga 8: il costruttore richiede due parametri:
- $message: è il messaggio di errore associato all'eccezione;
- $code: è il codice di errore associato all'eccezione. Se non è presente, verrà utilizzato il codice 0;
8.3. La classe [TaxAdminData]
Nella versione 02, i dati dell’amministrazione fiscale sono stati raccolti:
- innanzitutto in un file jSON;
- poi da questo file jSON in una tabella associativa;
Nella versione 03, i dati dell’amministrazione fiscale si trovano ancora nel file [taxadmindata.json], ma con nomi di attributi diversi:
{
"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
}
Nella versione 02, questo file serviva per inizializzare un array associativo. Nella versione 03 il file inizializzerà la seguente classe [TaxAdminData]:
<?php
namespace Application;
class TaxAdminData {
// scaglioni fiscali
private $limites;
private $coeffR;
private $coeffN;
// costanti di calcolo dell'imposta
private $plafondQfDemiPart;
private $plafondRevenusCelibatairePourReduction;
private $plafondRevenusCouplePourReduction;
private $valeurReducDemiPart;
private $plafondDecoteCelibataire;
private $plafondDecoteCouple;
private $plafondImpotCouplePourDecote;
private $plafondImpotCelibatairePourDecote;
private $abattementDixPourcentMax;
private $abattementDixPourcentMin;
// inizializzazione
public function setFromJsonFile(string $taxAdminDataFilename): TaxAdminData {
// si recupera il contenuto del file dei dati fiscali
$fileContents = \file_get_contents($taxAdminDataFilename);
$erreur = FALSE;
// errore?
if (!$fileContents) {
// si registra l'errore
$erreur = TRUE;
$message = "Le fichier des données [$taxAdminDataFilename] n'existe pas";
}
if (!$erreur) {
// si recupera il codice jSON dal file di configurazione in un array associativo
$arrayTaxAdminData = \json_decode($fileContents, true);
// errore?
if ($arrayTaxAdminData === FALSE) {
// si registra l'errore
$erreur = TRUE;
$message = "Le fichier de données jSON [$taxAdminDataFilename] n'a pu être exploité correctement";
}
}
// errore?
if ($erreur) {
// viene generata un'eccezione
throw new ExceptionImpots($message);
}
// inizializzazione degli attributi della classe
foreach ($arrayTaxAdminData as $key => $value) {
$this->$key = $value;
}
// si verifica che tutte le chiavi siano state inizializzate
$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é");
}
}
// si verifica che i valori siano tutti reali
foreach ($this as $key => $value) {
// $value deve essere un numero reale >=0 o un array di numeri reali >=0
$result = $this->check($value);
// errore?
if ($result->erreur) {
// viene generata un'eccezione
throw new ExceptionImpots("La valeur de l'attribut [$key] est invalide");
} else {
// si registra il valore
$this->$key = $result->value;
}
}
// si restituisce l'oggetto
return $this;
}
private function check($value): \stdClass {
…
return $result;
}
// toString
public function __toString() {
// stringa JSON dell'oggetto
return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
}
// getter e setter
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;
}
…
}
Commenti
- righe 6-20: gli attributi che ospiteranno gli attributi con lo stesso nome dei file jSON e [taxadmindata.json]. Si tratta di un punto importante: gli attributi della classe [TaxAdminData] sono identici a quelli dei file jSON e [taxadmindata.json]. Questa particolarità facilita notevolmente la scrittura del codice;
- la classe [TaxAdminData] non ha un costruttore. In PHP non è possibile avere più costruttori. Definirne uno impedisce quindi di inizializzare l’oggetto in altro modo. Di seguito, le nostre classi non avranno un costruttore, ma diversi metodi di tipo [setFromQqChose] che consentiranno di inizializzarle in modi diversi. La creazione di un oggetto di tipo [TaxAdminData] avviene quindi con l’espressione:
- riga 23: il metodo [setFromJsonFile] inizializza gli attributi della classe con quelli omonimi presenti nel file [$jsonFilename];
- righe 24-42: il file jSON viene utilizzato per costruire l’array associativo [$arrayTaxAdminData]. Abbiamo già incontrato questo codice nello script [main.php] della versione 02;
- righe 44-47: se si verifica un errore durante l’elaborazione del file jSON, viene generata un’eccezione. Questa verrà segnalata allo script principale [main.php];
- righe 48-51: vengono inizializzati gli attributi della classe. Qui si sfrutta il fatto che l’array associativo [$arrayTaxAdminData] e la classe [TaxAdminData] abbiano attributi con gli stessi nomi dei valori provenienti dal file jSON;
- righe 53-57: si verifica che tutti gli attributi della classe [TaxAdminData] siano stati inizializzati;
- riga 53: l’espressione [get_object_vars($this)] restituisce un array associativo i cui attributi sono quelli dell’oggetto [$this], quindi gli attributi della classe [TaxAdminData]. A questo punto occorre tenere presente che l’operazione di inizializzazione delle righe 48-51 potrebbe aver aggiunto degli attributi all’oggetto [$this]. Pertanto, se si scrive:
allora l’attributo [x] viene aggiunto all’oggetto [$this] anche se tale attributo non è stato dichiarato nella classe [TaxAdminData]. Quel che è certo è che gli attributi delle righe 6-20 fanno effettivamente parte dell’oggetto [$this], ma potrebbero non essere stati inizializzati. È un errore facile da commettere: basta sbagliare il nome di un attributo nel file [taxadmindata.json];
- righe 54-57: si esaminano tutti gli attributi di [$this] e, se uno di essi non è stato inizializzato, viene generata un’eccezione;
- un attributo può essere inizializzato con un valore errato. In PHP non è possibile assegnare un tipo agli attributi. Pertanto, l’operazione:
è possibile, mentre l’attributo [$plafondQfDemiPart] dovrebbe essere di tipo reale;
- righe 59-71: si verifica che ciascuno degli attributi della classe abbia un valore numerico reale positivo o nullo. È la funzione [check] alla riga 76 che svolge questo compito. Il suo parametro [$value] è un singolo valore o un array di valori;
- riga 62: la funzione [check] restituisce un oggetto di tipo [\stdClass] con due attributi:
- [erreur]: a TRUE se si è verificato un errore, a FALSE in caso contrario;
- [value]: il valore numerico effettivo corrispondente al parametro [$value] passato come parametro, riga 62;
- riga 64: si verifica se la verifica ha avuto esito positivo o meno;
- riga 66: se un attributo non è un numero reale positivo o zero, si genera un'eccezione;
- riga 69: altrimenti si registra il suo valore numerico;
- riga 73: si restituisce l’oggetto [$this] come risultato;
La funzione [check] è la seguente:
private function check($value): \stdClass {
// $value è un array di elementi oppure un singolo elemento
// si crea un array
if (!\is_array($value)) {
$tableau = [$value];
} else {
$tableau = $value;
}
// si trasforma l'array di elementi di tipo sconosciuto in un array di numeri reali
$newTableau = [];
$result = new \stdClass();
// gli elementi dell'array devono essere numeri decimali positivi o pari a zero
$modèle = '/^\s*([+]?)\s*(\d+\.\d*|\.\d+|\d+)\s*$/';
for ($i = 0; $i < count($tableau); $i ++) {
if (preg_match($modèle, $tableau[$i])) {
// si inserisce il float in newTableau
$newTableau[] = (float) $tableau[$i];
} else {
// si rileva l'errore
$result->erreur = TRUE;
// si esce
return $result;
}
}
// si restituisce il risultato
$result->erreur = FALSE;
if (!\is_array($value)) {
// un unico valore
$result->value = $newTableau[0];
} else {
// un elenco di valori
$result->value = $newTableau;
}
return $result;
}
Commenti
- riga 1: il parametro [$value] è un array o un singolo elemento. Inoltre, non se ne conosce il tipo. Il valore proviene dal file [taxadmindata.json]. A seconda dei valori registrati in questo file, i valori letti possono essere interi, reali, stringhe o booleani. Ad esempio:
"plafondQfDemiPart": 1551,
"plafondQfDemiPart": 1551.78,
"plafondQfDemiPart": "1551",
"plafondQfDemiPart": "xx",
Nel caso 1, il valore è di tipo [entier], nel caso 2 di tipo [réel], nel caso 3 è di tipo [string], convertibile in numero, nel caso 4 è di tipo [string], non convertibile in numero;
- righe 4-8: si crea un array a partire dal parametro [$value] ricevuto come parametro alla riga 1;
- riga 10: l'array verrà popolato con numeri reali;
- riga 11: il risultato sarà un oggetto di tipo [\stdClass];
- riga 13: espressione relazionale di un numero reale positivo o nullo;
- righe 14-24: si verifica che tutti gli elementi dell’array [$tableau] siano numeri reali positivi o pari a zero e si popola l’array [$newTableau] con questi elementi convertiti nel tipo [float] (riga 17);
- righe 18-23: non appena si rileva un elemento che non è un numero reale positivo o nullo, si registra l’errore nel risultato e lo si restituisce;
- righe 25-34: caso in cui tutti gli elementi dell’array [$tableau] siano stati dichiarati corretti;
- riga 32: il valore restituito [$result→value] è un array di numeri reali [float] o un singolo numero reale;
La funzione [__toString] delle righe 82-85 restituisce la stringa jSON contenente gli attributi e i valori dell’oggetto [$this].
Righe 87-110: i getter e i setter della classe;
Nota: a volte può risultare un po’ fastidioso dover scrivere tutti i get/set di una classe, soprattutto quando ci sono molti attributi. NetBeans può generarli automaticamente, insieme al costruttore. Per farlo, è sufficiente inserire gli attributi [1]:

- in [2], cliccate con il tasto destro del mouse nel punto in cui desiderate inserire il codice, quindi selezionate l’opzione [Insert Code];

- in [4], specificare che si desidera generare il costruttore;
- in [5], spuntate tutti gli attributi: ciò significa che volete che il costruttore abbia un parametro per ciascuno degli attributi;
- in [6], adottate lo stile dei costruttori Java;
- in [7], specificare che si desidera esplicitamente la parola chiave [public] prima del costruttore;
- in [8], confermate;

- in [9], NetBeans ha generato il costruttore. Tuttavia, non è riuscito a specificare il tipo dei parametri perché non li conosce. Aggiungeteli voi stessi: [10];
Per generare i getter e i setter, ripetete i passaggi da 2 a 4 e, al passaggio 4, selezionate [Getter and Setter]:

- in [5], specificate che desiderate i getter e i setter per ciascuno degli attributi;
- in [6], specificare che si desiderano i getter e i setter nello stile utilizzato da Java: setAttribut, getAttribut;
- in [7], specificare che tali getter e setter siano pubblici;
- in [8], confermare;

- in [9], i getter e i setter generati da NetBeans;
Eliminate questi getter e setter e ripetete i passaggi da 2 a 7.
- in [8], selezionare l’opzione [Fluent Setter] che non era stata selezionata in precedenza;
Il risultato ottenuto è il seguente:

Ogni setter termina con un’operazione [return $this]. Ciò consente di inizializzare gli attributi nel modo seguente:
Infatti, il valore di [$data→setLimites($limites)] (riga 32 del codice) è [$this], quindi in questo caso [$data]. È quindi possibile chiamare il metodo [setCoeffR($coeffR)] di questo oggetto e così via, poiché a sua volta anche questo metodo restituisce [$this] (riga 37 del codice). Questo modo di scrivere i metodi di una classe, per cui i metodi che non dovrebbero restituire nulla restituiscono l’oggetto [$this], è chiamato scrittura fluida. Essa facilita l’utilizzo di questi metodi.
8.4. L’interfaccia [InterfaceImpots]
Definiamo ora la seguente interfaccia [InterfaceImpots] [InterfaceImpots.php]:
<?php
// spazio dei nomi
namespace Application;
interface InterfaceImpots {
// recupera i dati relativi alle fasce di imposta che consentono il calcolo dell'imposta
// può generare l'eccezione ExceptionImpots
public function getTaxAdminData(): TaxAdminData;
// l'interfaccia è in grado di calcolare un'imposta
public function calculerImpot(string $marié, int $enfants, int $salaire): array;
// l'interfaccia è in grado di elaborare i dati contenuti nei file di testo
// $usersFilename: file dei dati utente contenente stato civile, numero di figli e stipendio annuo
// $resultsFilename: file dei risultati contenente stato civile, numero di figli, stipendio annuo e importo dell'imposta
// $errorsFilename: file degli errori riscontrati
// può generare l'eccezione ExceptionImpots
public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void;
}
Commenti
- riga 4: l’interfaccia è collocata nello spazio dei nomi [Application];
- riga 6: l’interfaccia che consente il calcolo delle imposte;
- riga 10: il metodo [getTaxAdminData] consentirà di acquisire i dati dall’amministrazione fiscale in un oggetto di tipo [TaxAdminData] che abbiamo appena presentato. Poiché questi dati possono trovarsi in un file, in un database o addirittura in rete, il metodo [getTaxAdminData] potrebbe non riuscire a recuperarli. In tal caso, genererà un'eccezione di tipo [ExceptionImpots]. Si tratta del metodo standard nella programmazione orientata agli oggetti per segnalare un errore verificatosi in un metodo o in un costruttore;
- riga 13: il metodo [calculerImpot] consentirà di calcolare l’imposta di un utente;
- riga 20: il metodo [executeBatchImpots] consentirà di calcolare l’imposta di più contribuenti;
- [$usersFileName] è il nome del file di testo contenente i dati dei contribuenti;
- [$resultsFileName] è il nome del file di testo contenente l’importo dell’imposta per questi contribuenti;
- [$errorsFileName] è il nome del file di testo contenente gli errori riscontrati durante l’elaborazione di questi file;
Il contenuto del file di testo [$usersFileName] potrebbe essere il seguente:
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
Si noti che le righe 5 e 7 contengono elementi errati.
Il contenuto del file di testo [$resultsFileName] sarà quindi il seguente:
e quello del file di testo [$errorsFileName] sarà il seguente:
8.5. La classe [Utilitaires]
Definiamo inoltre una classe [Utilitaires] in un file [Utilitaires.php]:
<?php
// spazio dei nomi
namespace Application;
// una classe di funzioni di utilità
abstract class Utilitaires {
public static function cutNewLinechar(string $ligne): string {
// si rimuove il carattere di fine riga da $ligne, se presente
$longueur = strlen($ligne); // lunghezza della riga
while (substr($ligne, $longueur - 1, 1) == "\n" or substr($ligne, $longueur - 1, 1) == "\r") {
$ligne = substr($ligne, 0, $longueur - 1);
$longueur--;
}
// fine - si restituisce la riga
return($ligne);
}
}
Commenti
- riga 4: anche la classe [Utilitaires] è collocata nello spazio dei nomi [Exemples];
- riga 9: il metodo [cutNewLinechar] rimuove l'eventuale carattere di fine riga dal testo che gli è stato passato come parametro. Restituisce la nuova riga così formata. Si noti che si tratta di un metodo statico, ovvero verrà chiamato nella forma [Utilitaires::cutNewLineChar];
8.6. La classe astratta [AbstractBaseImpots]
L’interfaccia [InterfaceImpots] sarà implementata dalla seguente classe astratta [AbstractBaseImpots]: [AbstractBaseImpots.php]:
<?php
// spazio dei nomi
namespace Application;
// definizione di una classe astratta AbstractBaseImpots
abstract class AbstractBaseImpots implements InterfaceImpots {
// dati dell'amministrazione fiscale
private $taxAdminData = NULL;
// dati necessari per il calcolo dell'imposta
abstract function getTaxAdminData(): TaxAdminData;
// calcolo dell'imposta
// --------------------------------------------------------------------------
public function calculerImpot(string $marié, int $enfants, int $salaire): array {
// $marié: sì, no
// $enfants: numero di figli
// $salaire: stipendio annuo
// $this->taxAdminData: dati dell'amministrazione fiscale
//
// si verifica che siano presenti i dati dell'amministrazione fiscale
if ($this->taxAdminData === NULL) {
$this->taxAdminData = $this->getTaxAdminData();
}
// calcolo dell'imposta con figli
$result1 = $this->calculerImpot2($marié, $enfants, $salaire);
$impot1 = $result1["impôt"];
// calcolo dell'imposta senza figli
if ($enfants != 0) {
$result2 = $this->calculerImpot2($marié, 0, $salaire);
$impot2 = $result2["impôt"];
// applicazione del limite massimo del quoziente familiare
$plafonDemiPart = $this->taxAdminData->getPlafondQfDemiPart();
if ($enfants < 3) {
// $PLAFOND_QF_DEMI_PART euro per i primi 2 figli
$impot2 = $impot2 - $enfants * $plafonDemiPart;
} else {
// $PLAFOND_QF_DEMI_PART euro per i primi 2 figli, il doppio per i successivi
$impot2 = $impot2 - 2 * $plafonDemiPart - ($enfants - 2) * 2 * $plafonDemiPart;
}
} else {
$impot2 = $impot1;
$result2 = $result1;
}
// si applica l’imposta più elevata
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"];
}
// calcolo di un'eventuale riduzione
$décôte = $this->getDecôte($marié, $salaire, $impot);
$impot -= $décôte;
// calcolo di un'eventuale riduzione delle imposte
$réduction = $this->getRéduction($marié, $salaire, $enfants, $impot);
$impot -= $réduction;
// risultato
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 {
…
// risultato
return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
}
// revenuImposable=stipendioAnnuale-detrazione
// la detrazione ha un valore minimo e uno massimo
private function getRevenuImposable(float $salaire): float {
…
// risultato
return floor($revenuImposable);
}
// calcola un'eventuale riduzione
private function getDecôte(string $marié, float $salaire, float $impots): float {
…
// risultato
return ceil($décôte);
}
// calcola un'eventuale riduzione
private function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {
…
// risultato
return ceil($réduction);
}
public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void {
…
}
}
Commenti
- riga 4: la classe [AbstractBaseImpots] si troverà nello spazio dei nomi [Application], come gli altri elementi dell’applicazione in fase di scrittura;
- riga 7: la classe [AbstractBaseImpots] implementa l'interfaccia [InterfaceImpots];
- riga 9: i dati dell’amministrazione fiscale saranno inseriti nell’attributo [$taxAdminData];
- riga 12: implementazione del metodo [getTaxAdminData] dell’interfaccia. Non sappiamo ancora come definire questo metodo: nel paragrafo precedente abbiamo visto un esempio in cui i dati dell’amministrazione fiscale sono stati prelevati da un file jSON. Vedremo un altro caso in cui i dati dovranno essere recuperati da un database. Spetterà alle classi derivate definire il contenuto del metodo [getTaxAdminData]. I due casi precedenti daranno origine a due classi derivate. Il metodo [getTaxAdminData] è quindi dichiarato astratto, il che rende automaticamente astratta la classe stessa (riga 7);
- righe 15-64: la funzione di calcolo dell’imposta già vista nei paragrafi link e link;
- la versione 02 inseriva i dati dell’amministrazione fiscale in una tabella associativa [$taxAdminData]. La versione 03 li inserisce nell’attributo [$this→taxAdminData]. La prima differenza tra queste due soluzioni è una differenza nella visibilità dei dati fiscali:
- nella versione 02, la tabella associativa [$taxAdminData] non aveva visibilità globale. Veniva quindi passata come parametro a tutte le funzioni di calcolo dell’imposta;
- nella versione 03, l’attributo [$this→taxAdminData] ha visibilità globale per tutti i metodi della classe. Non viene quindi passato come parametro a tutte le funzioni di calcolo dell’imposta;
- una seconda differenza deriva dal fatto che la versione 03 sostituisce le funzioni con metodi di classe. Ogni chiamata al metodo avviene ora tramite un’espressione [$this→getMéthode(…)] (righe 27, 31, 57, 60);
- una terza differenza è che quando il metodo [calculerImpot] avvia la propria esecuzione, non sa se l’attributo [private $taxAdminData] di cui ha bisogno sia stato inizializzato. Infatti, il costruttore della classe non lo inizializza. Spetta quindi al metodo [calculerImpot] farlo utilizzando il metodo [getTaxAdminData] alla riga 12. È ciò che avviene alle righe 23-25;
- a parte queste differenze, i metodi di calcolo dell’imposta rimangono gli stessi delle versioni precedenti;
La funzione [executeBatchImpots] è la seguente:
public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void {
// possono verificarsi diversi errori quando si gestiscono i file
try {
// errori di apertura del file
$errors = fopen($errorsFileName, "w");
if (!$errors) {
throw new ExceptionImpots("Impossible de créer le fichier des erreurs [$errorsFileName]", 10);
}
// apertura del file dei risultati
$results = fopen($resultsFileName, "w");
if (!$results) {
throw new ExceptionImpots("Impossible de créer le fichier des résultats [$resultsFileName]", 11);
}
// lettura dei dati utente
// ogni riga ha il formato: stato civile, numero di figli, stipendio annuo
$data = fopen($usersFileName, "r");
if (!$data) {
throw new ExceptionImpots("Impossible d'ouvrir en lecture les déclarations des contribuables [$usersFileName]", 12);
}
// si elabora la riga corrente del file dei dati utente
// che ha il formato: stato civile, numero di figli, stipendio annuo
$num = 1; // numero della riga corrente
$nbErreurs = 0; // numero di errori riscontrati
while ($ligne = fgets($data, 100)) {
// debug
// print "riga n. " . ($i + 1) . " : " . $ligne;
// si rimuove l'eventuale carattere di fine riga
$ligne = Utilitaires::cutNewLineChar($ligne);
// si recuperano i 3 campi coniuge:figli:stipendio che formano $ligne
list($marié, $enfants, $salaire) = explode(",", $ligne);
// li si verifica
// lo stato civile deve essere «sì» o «no»
$marié = trim(strtolower($marié));
$erreur = ($marié !== "oui" and $marié !== "non");
if (!$erreur) {
// il numero di figli deve essere un numero intero
$enfants = trim($enfants);
if (!preg_match("/^\s*\d+\s*$/", $enfants)) {
$erreur = TRUE;
} else {
$enfants = (int) $enfants;
}
}
if (!$erreur) {
// lo stipendio è un numero intero senza i centesimi di euro
$salaire = trim($salaire);
if (!preg_match("/^\s*\d+\s*$/", $salaire)) {
$erreur = TRUE;
} else {
$salaire = (int) $salaire;
}
}
// errore?
if ($erreur) {
fputs($errors, "la ligne [$num] du fichier [$usersFileName] est erronée\n");
$nbErreurs++;
} else {
// si calcola l'imposta
$result = $this->calculerImpot($marié, (int) $enfants, (int) $salaire);
// si inserisce il risultato nel file dei risultati
$result = ["marié" => $marié, "enfants" => $enfants, "salaire" => $salaire] + $result;
fputs($results, \json_encode($result, JSON_UNESCAPED_UNICODE) . "\n");
}
// riga successiva
$num++;
}
// errori?
if ($nbErreurs > 0) {
throw new ExceptionImpots("Il y a eu des erreurs", 15);
}
} catch (ExceptionImpots $ex) {
// si rilancia l'eccezione
throw $ex;
} finally {
// si chiudono tutti i file
fclose($data);
fclose($results);
fclose($errors);
}
}
Commenti al codice
- riga 1: la funzione riceve tre parametri:
- [$usersFileName]: il nome del file di testo contenente i dati dei contribuenti. Ogni riga di testo contiene i dati di un contribuente nel formato: stato civile (sì / no), numero di figli, stipendio annuale:
- (continua)
- [$resultsFileName]: il nome del file di testo che conterrà i risultati. Ogni riga di testo avrà il seguente formato:
- (continua)
- [$errorsFileName]: il nome del file di testo contenente gli errori:
la ligne [5] du fichier [taxpayersdata.txt] est erronée
la ligne [7] du fichier [taxpayersdata.txt] est erronée
- riga 3: poiché alcune operazioni possono generare un'eccezione, l'intero codice del metodo è racchiuso in un blocco try / catch / finally;
- righe 3-19: i tre file vengono aperti. Viene generata un'eccezione non appena l'apertura fallisce;
- riga 24: le righe del file [$data] vengono lette una alla volta, con un limite massimo di 100 caratteri (tutte le righe contengono meno di 100 caratteri);
- riga 28: si utilizza il metodo statico [Utilitaires::cutNewLineChar] per rimuovere l'eventuale carattere di fine riga;
- riga 30: si recuperano i tre elementi della riga letta;
- righe 33-52: viene verificata la validità dei tre elementi. In questo caso, se si verifica un errore non viene generata un’eccezione, ma il messaggio di errore viene scritto nel file di testo [$errors] (riga 55);
- riga 59: se la riga letta è valida, viene effettuato il calcolo dell’imposta. Si ottiene un risultato sotto forma di tabella associativa ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux];
- riga 61: al risultato ottenuto si aggiungono le chiavi [marié, enfants, salaire];
- riga 61: il risultato viene scritto nel file di testo [$results] sotto forma della stringa jSON del risultato ottenuto;
- righe 68-70: al termine dell’elaborazione del file [$data], si verifica il numero di righe errate riscontrate. Se ce n’è almeno una, viene generata un’eccezione;
- righe 71-74: si intercetta l’eccezione che il codice potrebbe aver generato e la si rilancia immediatamente (riga 73). Lo scopo di questo espediente è quello di poter inserire una clausola [finally] alle righe 74-79: indipendentemente da come si concluda l’esecuzione del codice del metodo, i tre file che potrebbero essere stati aperti da tale codice vengono chiusi. La chiusura di un file che non è stato aperto non provoca alcun errore;
8.7. La classe [ImpotsWithTaxAdminDataInJsonFile]
La classe astratta [AbstractBaseImpots] non implementa il metodo [getTaxAdminData] dell’interfaccia [InterfaceImpots]. Dobbiamo quindi definirlo in una classe derivata. Lo facciamo nella seguente classe derivata [ImpotsWithTaxAdminDataInJsonFile]:
<?php
// spazio dei nomi
namespace Application;
// definizione di una classe ImpotsWithDataInArrays
class ImpotsWithTaxAdminDataInJsonFile extends AbstractBaseImpots {
// un attributo di tipo Data
private $taxAdminData;
// il costruttore
public function __construct(string $jsonFileName) {
// si inizializza $this->taxAdminData a partire dal file jSON
$this->taxAdminData = (new TaxAdminData())->setFromJsonFile($jsonFileName);
}
// restituisce i dati necessari per il calcolo dell'imposta
public function getTaxAdminData(): TaxAdminData {
// viene restituito l'attributo [$this->taxAdminData]
return $this->taxAdminData;
}
}
Commenti
- riga 7: la classe [ImpotsWithTaxAdminDataInJsonFile] estende la classe astratta [AbstractBaseImpots]. Dovrà definire il metodo [getTaxAdminData] che la sua classe padre non ha definito;
- riga 9: l'attributo [$taxAdminData] conterrà i dati dell'amministrazione fiscale;
- righe 12-15: il costruttore riceve come unico parametro il nome del file jSON contenente i dati fiscali;
- riga 14: viene creato e quindi inizializzato un oggetto di tipo [TaxAdminData]. Questa operazione può generare un'eccezione di tipo [ExceptionImpots], che verrà segnalata allo script principale [main.php];
- righe 18-20: si assegna un corpo al metodo [getTaxAdminData] che la classe padre non aveva definito. In questo caso, è sufficiente rendere l’attributo [$this->taxAdminData] inizializzato dal costruttore;
8.8. Lo script [main.php]
Queste classi e l’interfaccia vengono utilizzate dal seguente script [main.php]:
<?php
// rigoroso rispetto dei tipi dichiarati dei parametri delle funzioni
declare(strict_types = 1);
// spazio dei nomi
namespace Application;
// inclusione di interfacce e classi
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 -----------------------------------------------------
// definizione delle costanti
const TAXPAYERSDATA_FILENAME = "taxpayersdata.txt";
const RESULTS_FILENAME = "resultats.txt";
const ERRORS_FILENAME = "errors.txt";
const TAXADMINDATA_FILENAME = "taxadmindata.json";
try {
// si crea un oggetto ImpotsWithTaxAdminDataInJsonFile
$impots = new ImpotsWithTaxAdminDataInJsonFile(TAXADMINDATA_FILENAME);
// si esegue il batch delle imposte
$impots->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
// viene visualizzato l'errore
print $ex->getMessage() . "\n";
}
// fine
print "Terminé\n";
exit();
Commenti
- riga 4: si impone il rigoroso rispetto dei tipi dei parametri delle funzioni;
- riga 7: anche lo script [main.php] è collocato nello spazio dei nomi [Application];
- righe 10-15: si indica all’interprete PHP dove si trovano le classi e le interfacce utilizzate dallo script. Si noti che in questo caso non è stata utilizzata l’istruzione use per dichiarare il nome completo delle classi utilizzate dallo script. Ciò è infatti superfluo poiché lo script e le classi si trovano nello stesso spazio dei nomi [Application];
- righe 18-22: i nomi dei file di testo utilizzati nello script;
- righe 24-29: viene creato un oggetto [ImpotsWithTaxAdminDataInJsonFile] e viene gestita l’eventuale eccezione;
- riga 28: viene eseguito il metodo [executeBatchImpots] che calcolerà le imposte per tutti i contribuenti presenti nel file [TAXPAYERSDATA_FILENAME]. I risultati verranno inseriti nel file [RESULTS_FILENAME] e gli eventuali errori nel file [ERRORS_FILENAME];
- righe 29-32: in caso di errore irreversibile, viene visualizzato il messaggio di errore;
Risultati
Con il seguente file dei contribuenti [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
si ottiene il seguente file degli errori [errors.txt]:
la ligne [5] du fichier [taxpayersdata.txt] est erronée
la ligne [7] du fichier [taxpayersdata.txt] est erronée
e il seguente file dei risultati [resultats.txt]: