Skip to content

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

Image

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:

<?php

// spazio dei nomi
namespace Application;

class ExceptionImpots extends \RuntimeException {

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

}

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:
(new TaxAdminData())→setFromQqChose(…)
  • 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:
    $this->x = "1000";

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:
$this→plafondQfDemiPart=’abcd’

è 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]:

Image

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

Image

  • 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;

Image

  • 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]:

Image

  • 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;

Image

  • 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:

Image

Ogni setter termina con un’operazione [return $this]. Ciò consente di inizializzare gli attributi nel modo seguente:

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

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:

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 quello del file di testo [$errorsFileName] sarà il seguente:

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

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:
oui,2,55555
oui,2,50000
  • (continua)
    • [$resultsFileName]: il nome del file di testo che conterrà i risultati. Ogni riga di testo avrà il seguente 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)
    • [$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]:

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}