8. Exercice d'application – version 3
On reprend l'exercice déjà étudié précédemment (versions 1 et 2) pour le résoudre avec un code PHP utilisant des classes. Nous allons mettre en pratique ce que nous avons appris dans les trois chapitres précédents :
- une classe [readonly] (immuable) pour représenter les données de l'administration fiscale ;
- une interface, une classe abstraite qui l'implémente partiellement et une classe dérivée qui la complète ;
- un unique type d'exception, [ExceptionImpots], pour signaler toutes les erreurs de l'application ;
- l'attribut [#[\Override]] sur toutes les méthodes qui implémentent une méthode d'interface ou redéfinissent une méthode parent .
8.1. L'arborescence des scripts
La version 3 est dans le dossier [scripts-console/impots/version-03] :
Chaque classe ou interface est dans un fichier qui porte son nom. C'est une convention universellement suivie en PHP (elle est même imposée par le standard de chargement automatique PSR-4 que nous rencontrerons avec [Composer]).
8.2. L'exception [ExceptionImpots]
Dans la version 3, lorsqu'un constructeur ou une méthode de classe rencontrera une erreur, il ou elle lancera une exception de type [ExceptionImpots] :
Commentaires
- ligne 6 : la classe [ExceptionImpots] est dans l'espace de noms [Application]. Toutes les classes et interfaces de l'application de calcul de l'impôt seront dans cet espace de noms ;
- ligne 9 : la classe [ExceptionImpots] étend la classe prédéfinie de PHP [RuntimeException]. Comme nous l'avons vu au chapitre sur les exceptions, une exception sert d'abord par son type : c'est lui qui permet de l'intercepter avec une clause [catch (ExceptionImpots $ex)] ;
- lignes 11-12 : le cours PHP 7 définissait un constructeur [__construct(string $message, int $code = 0)] qui se contentait d'appeler celui de la classe parent. Il est inutile : la classe hérite du constructeur de [RuntimeException], qui accepte un message, un code d'erreur et une éventuelle exception précédente. Ce dernier paramètre nous servira à encapsuler une exception de bas niveau (une [JsonException] par exemple) dans une [ExceptionImpots] .
8.3. La classe [TaxAdminData]
Dans la version 2, les données de l'administration fiscale ont été rassemblées :
- d'abord dans un fichier JSON ;
- puis de ce fichier JSON dans un tableau associatif .
Nous avions noté le défaut de cette solution : rien ne garantit que le tableau associatif contient toutes les clés attendues ni que les valeurs associées sont des nombres. Dans la version 3, les données de l'administration fiscale sont toujours dans le fichier [taxadmindata.json] mais avec des noms d'attributs différents, écrits selon la convention camelCase des noms d'attributs de classe :
Dans la version 2, ce fichier servait à initialiser un tableau associatif. Dans la version 3, il va initialiser un objet de la classe [TaxAdminData] suivante :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | |
Commentaires
- ligne 9 : la classe est [readonly] : tous ses attributs sont en lecture seule. Une fois construit, un objet [TaxAdminData] ne peut plus être modifié : il peut être partagé sans risque entre toutes les parties de l'application. Elle est aussi [final] : elle ne peut être dérivée ;
- lignes 12-24 : deux constantes de classe privées rassemblent les noms des attributs attendus dans les données. Depuis PHP 8.3, une constante de classe peut être typée (ici [array]) ;
- lignes 27-43 : le constructeur utilise la promotion des paramètres : chaque paramètre devient un attribut public de même nom et de même type. Les attributs ont les noms des clés du fichier JSON. C'est un point important qui facilite beaucoup l'écriture du code, comme nous allons le voir. Les tranches d'impôt sont des tableaux ([array]), les constantes de calcul des réels ([float]). En PHP 7, les attributs ne pouvaient pas être typés (le cours PHP 7 le regrettait) : ils le peuvent depuis PHP 7.4 ;
- ligne 27 : le constructeur est privé : on ne peut pas écrire [new TaxAdminData(...)] en dehors de la classe. Un objet [TaxAdminData] ne peut être obtenu que par les méthodes statiques [fromJsonFile] et [fromArray], qui vérifient la validité des données. On est ainsi assuré que tout objet [TaxAdminData] contient des données valides. En PHP, une classe ne peut avoir qu'un constructeur. Les méthodes statiques qui construisent un objet, appelées parfois « constructeurs nommés », permettent d'avoir plusieurs façons de construire un objet. Le cours PHP 7 utilisait des méthodes d'instance [setFromJsonFile] appelées sur un objet vide : [(new TaxAdminData())->setFromJsonFile(...)]. C'est impossible avec un objet immuable ;
- lignes 47-67 : la méthode [fromJsonFile] construit un objet [TaxAdminData] à partir du fichier JSON dont on lui passe le nom. Son type de retour [self] désigne la classe elle-même ;
- lignes 50-53 : on lit le contenu du fichier. Si cela n'est pas possible, on lance une exception [ExceptionImpots]. Celle-ci remontera jusqu'au script principal ;
- lignes 56-60 : on décode le contenu JSON du fichier. Avec l'option [JSON_THROW_ON_ERROR] (PHP 7.3), la fonction [json_decode] lance une exception [JsonException] lorsque le JSON est invalide, au lieu de rendre silencieusement [null]. On remarquera l'argument nommé [flags:] qui évite de donner une valeur au troisième paramètre [depth] de [json_decode]. L'exception [JsonException] est encapsulée dans une [ExceptionImpots] : le code appelant n'a qu'un type d'exception à gérer mais peut retrouver la cause originelle avec [$ex->getPrevious()] ;
- lignes 62-64 : un fichier contenant par exemple le seul nombre 12 est du JSON valide : il faut vérifier qu'on a bien obtenu un tableau ;
- ligne 66 : la suite du travail est déléguée à la méthode [fromArray]. Cette méthode est publique : elle pourra servir dans une version ultérieure où les données de l'administration fiscale viendront d'une base de données ;
- lignes 73-81 : on vérifie que les clés du tableau [$data] sont exactement les noms attendus. [[...self::TABLEAUX, ...self::CONSTANTES]] crée un tableau contenant les éléments des deux tableaux. La fonction [array_diff] rend les éléments du premier tableau absents du second, et la fonction [array_first] (PHP 8.5) le premier élément d'un tableau ou [null] si le tableau est vide. On détecte ainsi une clé manquante puis une clé inconnue. C'est l'erreur la plus fréquente : une faute de frappe dans un nom d'attribut du fichier [taxadmindata.json]. Dans le cours PHP 7, la boucle [$this->$key = $value] créait silencieusement un attribut dynamique portant le nom erroné. Depuis PHP 8.2, la création d'attributs dynamiques est dépréciée, et elle est interdite dans une classe [readonly] ;
- lignes 84-90 : chacun des trois tableaux doit être une liste non vide (fonction [array_is_list], PHP 8.1) dont tous les éléments sont des réels positifs ou nuls. La fonction [array_map] applique la méthode [check] à chacun des éléments ;
- lignes 92-95 : les trois tableaux des tranches d'impôt doivent avoir la même taille ;
- lignes 96-99 : chacune des constantes de calcul doit être un réel positif ou nul ;
- ligne 101 : le tableau [$valeurs] a pour clés les noms des paramètres du constructeur. L'opérateur [...] appliqué à un tableau à clés chaînes transforme chaque élément en argument nommé (PHP 8.1) : cette ligne est équivalente à [new self(limites: $valeurs["limites"], coeffR: $valeurs["coeffR"], ...)]. C'est là que le choix de noms identiques pour les clés JSON et les attributs de la classe simplifie le code ;
- lignes 105-113 : la méthode [check] vérifie qu'une valeur [$value] de type inconnu ([mixed]) est un nombre positif ou nul. Selon ce qui est écrit dans le fichier JSON, la valeur lue peut être un entier (1551), un réel (1551.78), une chaîne convertible en nombre ("1551"), une chaîne non convertible ("xx"), un booléen... La fonction [is_numeric] rend [true] pour une chaîne représentant un nombre. Le cours PHP 7 faisait cette vérification avec une expression régulière et rendait un objet [stdClass] indiquant s'il y avait eu erreur. Ici, une erreur provoque une exception et la méthode rend simplement le nombre sous la forme d'un [float] ;
- ligne 116 : la méthode [__toString] rend la chaîne JSON des attributs de l'objet. La fonction [get_object_vars] rend un tableau associatif [nom de l'attribut => valeur] .
8.3.1. Pourquoi n'y a-t-il plus de getters et setters ?
Le cours PHP 7 déclarait les attributs privés et écrivait pour chacun d'eux un getter et un setter (26 méthodes), générés automatiquement par l'IDE NetBeans. Les setters étaient « fluents » : ils rendaient l'objet [$this], ce qui permettait d'enchaîner les appels [$data->setLimites($limites)->setCoeffR($coeffR)].
Avec PHP 8, ce code n'a plus de raison d'être :
- les getters servaient à lire des attributs privés. Les attributs [readonly] peuvent être publics sans danger puisqu'ils ne peuvent être modifiés : on écrit [$taxAdminData->plafondQfDemiPart] au lieu de [$taxAdminData->getPlafondQfDemiPart()] ;
- les setters servaient à initialiser l'objet et permettaient de vérifier les valeurs. Un objet immuable est initialisé une fois pour toutes par son constructeur, et la vérification est faite par les méthodes [fromJsonFile] et [fromArray] ;
- les attributs typés garantissent le type des valeurs. Le cours PHP 7 notait que l'instruction [$this->plafondQfDemiPart = 'abcd'] était possible : avec un attribut [float] et [declare(strict_types=1)], elle provoque une erreur [TypeError] .
Le script [test-taxadmindata.php] vérifie le fonctionnement de la classe [TaxAdminData] :
Commentaires
- ligne 14 : construction d'un objet [TaxAdminData] valide à partir du fichier [taxadmindata.json] ;
- ligne 15 : l'objet est inséré dans une chaîne : sa méthode [__toString] est appelée ;
- ligne 19 : tentative de modification d'un attribut en lecture seule ;
- ligne 25 : tentative d'utilisation du constructeur privé ;
- ligne 31 : le tableau associatif des données valides ;
- lignes 32-47 : un tableau de fonctions fléchées, chacune construisant un objet [TaxAdminData] à partir de données incorrectes. Les données incorrectes sont obtenues en modifiant une copie du tableau [$valides]. Avec l'opérateur [+] sur deux tableaux, ce sont les éléments du tableau de gauche qui l'emportent en cas de clé commune. La fonction [array_diff_key] rend le premier tableau privé des clés présentes dans le second ;
- lignes 48-54 : chaque fonction est exécutée et l'exception attendue est affichée ;
- ligne 56 : une chaîne numérique est acceptée et convertie en réel .
Résultats :
- ligne 2 : la chaîne JSON de l'objet. On remarquera que les réels qui n'ont pas de partie décimale (1551.0) sont écrits sans elle (1551) par la fonction [json_encode] ;
- lignes 4-5 : les deux erreurs [Error] provoquées par la modification d'un attribut [readonly] et par l'appel d'un constructeur privé. Ce sont des erreurs de programmation, pas des erreurs de données : elles ne sont pas transformées en [ExceptionImpots] ;
- lignes 6-12 : toutes les données incorrectes sont détectées, chacune avec un message et un code d'erreur différents ;
- ligne 13 : la chaîne "1551" a été convertie en réel .
8.4. L'interface [InterfaceImpots]
Nous définissons maintenant l'interface [InterfaceImpots] suivante [InterfaceImpots.php] :
Commentaires
- ligne 8 : l'interface permettant le calcul des impôts. Elle est elle aussi dans l'espace de noms [Application] ;
- ligne 12 : la méthode [getTaxAdminData] permettra d'acquérir les données de l'administration fiscale dans un objet de type [TaxAdminData] que nous venons de présenter. Comme ces données peuvent être dans un fichier, une base de données voire sur le réseau, la méthode [getTaxAdminData] peut échouer à obtenir les données. Dans ce cas, elle lancera une exception de type [ExceptionImpots]. C'est la méthode standard en programmation objet pour signaler une erreur rencontrée dans une méthode ou un constructeur ;
- ligne 15 : la méthode [calculerImpot] permettra de calculer l'impôt d'un contribuable ;
- ligne 22 : la méthode [executeBatchImpots] permettra de calculer l'impôt de plusieurs contribuables :
- [$taxPayersFileName] est le nom du fichier texte contenant les données des contribuables ;
- [$resultsFileName] est le nom du fichier texte contenant le montant de l'impôt pour ces contribuables ;
- [$errorsFileName] est le nom du fichier texte contenant les erreurs rencontrées lors de l'exploitation de ces fichiers .
Le contenu du fichier texte des contribuables [taxpayersdata.txt] est le suivant :
On notera que les lignes 5 et 8 contiennent des éléments erronés. Les onze autres lignes sont les onze contribuables du tableau des chiffres officiels.
8.5. La classe [Utilitaires]
Nous définissons par ailleurs une classe [Utilitaires] dans un fichier [Utilitaires.php] :
Commentaires
- ligne 9 : la classe [Utilitaires] est également placée dans l'espace de noms [Application]. Elle ne rassemble que des méthodes statiques : il n'y a aucune raison de l'instancier ni de la dériver. Le cours PHP 7 la déclarait abstraite pour empêcher son instanciation, en regrettant que PHP refuse de la déclarer à la fois [final] et [abstract]. C'est toujours le cas. L'usage est aujourd'hui de la déclarer [final] (non dérivable) et de lui donner un constructeur privé (non instanciable de l'extérieur) ;
- ligne 16 : la méthode [cutNewLineChar] enlève l'éventuelle marque de fin de ligne du texte qu'on lui a passé en paramètre. C'est une méthode statique, c'est-à-dire qu'elle sera appelée sous la forme [Utilitaires::cutNewLineChar($ligne)]. Son code est celui de la fonction de même nom de la version 2 .
8.6. La classe abstraite [AbstractBaseImpots]
L'interface [InterfaceImpots] sera implémentée par la classe abstraite [AbstractBaseImpots] suivante [AbstractBaseImpots.php] :
Commentaires
- ligne 9 : la classe [AbstractBaseImpots] implémente l'interface [InterfaceImpots] ;
- ligne 12 : les données de l'administration fiscale seront placées dans l'attribut privé [$taxAdminData]. Son type [?TaxAdminData] signifie « un objet [TaxAdminData] ou la valeur [null] ». Sa valeur initiale est [null] ;
- ligne 16 : la méthode [getTaxAdminData] de l'interface. On ne sait pas encore la définir : nous avons vu un cas où les données de l'administration fiscale ont été prises dans un fichier JSON (version 2). Nous verrons un autre cas où les données seront à chercher dans une base de données. Ce sera aux classes dérivées de définir le contenu de la méthode [getTaxAdminData]. La méthode est donc déclarée abstraite, ce qui oblige à déclarer la classe elle-même abstraite. Cette déclaration est facultative : une classe abstraite qui implémente une interface n'est pas obligée de redéclarer les méthodes qu'elle n'implémente pas. Elle rend cependant le code plus lisible. L'attribut [#[\Override]] (PHP 8.3) indique que la méthode est celle d'une interface ou d'une classe parent : si on se trompe dans son nom, PHP signale l'erreur ;
- lignes 21-73 : la fonction de calcul de l'impôt déjà rencontrée dans les versions 1 et 2. Ses paramètres ont changé : la version 2 recevait le tableau associatif [$taxAdminData] en premier paramètre, la version 3 utilise l'attribut [$this->taxAdminData]. La 1re différence entre ces deux solutions est une différence de visibilité des données fiscales :
- dans la version 2, le tableau associatif [$taxAdminData] n'avait pas une visibilité globale. Il était donc passé en paramètre à toutes les fonctions de calcul de l'impôt ;
- dans la version 3, l'attribut [$this->taxAdminData] est visible dans toutes les méthodes de la classe. Il n'est donc plus passé en paramètre ;
- une seconde différence vient du fait que la version 3 remplace des fonctions par des méthodes de classe. Chaque appel de méthode se fait désormais avec une expression [$this->méthode(...)] (ligne 31, ligne 61) ;
- ligne 29 : une troisième différence est que lorsque la méthode [calculerImpot] démarre son travail, elle ne sait pas si l'attribut [$taxAdminData] dont elle a besoin a été initialisé. En effet, la classe n'a pas de constructeur qui l'initialiserait. C'est donc à la méthode [calculerImpot] de le faire à l'aide de la méthode [getTaxAdminData]. L'opérateur [??=] (PHP 7.4) affecte la valeur de droite à la variable de gauche uniquement si celle-ci vaut [null]. La ligne est équivalente à [if ($this->taxAdminData === null) { $this->taxAdminData = $this->getTaxAdminData(); }] ;
- ligne 38 : les données de l'administration fiscale sont lues directement dans les attributs publics en lecture seule de l'objet [TaxAdminData] ;
- en-dehors de ces différences, la méthode de calcul de l'impôt reste ce qu'elle était dans les versions précédentes .
Les méthodes privées de calcul de l'impôt sont les suivantes :
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | |
Commentaires
- lignes 80-83 : l'expression [match] (PHP 8.0) remplace avantageusement le [if / else] de la version 2. Elle compare la valeur [$marié] aux valeurs de ses branches avec l'opérateur d'identité [===] et rend la valeur de la branche trouvée. Si aucune branche ne convient, elle lance une erreur [UnhandledMatchError] : un statut marital autre que "oui" ou "non" ne passera pas inaperçu ;
- lignes 100-102 : les tableaux des tranches d'imposition sont copiés dans des variables locales. En PHP, l'affectation d'un tableau crée une copie : la modification de [$limites] à la ligne 104 ne touche pas l'attribut [readonly] de l'objet [TaxAdminData] (ce qui serait d'ailleurs une erreur) ;
- lignes 122-123 : les fonctions [min] et [max] remplacent les deux [if] de la version 2 : l'abattement ne peut dépasser [abattementDixPourcentMax] ni être inférieur à [abattementDixPourcentMin] ;
- le reste du code est celui de la version 2, où l'expression [$taxAdminData["PLAFOND_DECOTE_COUPLE"]] est devenue [$this->taxAdminData->plafondDecoteCouple]. Une faute de frappe dans un nom d'attribut est maintenant signalée par l'éditeur [VS Code] (extension [PHP Intelephense]) dès l'écriture du code, ce qui n'était pas le cas d'une faute de frappe dans une clé de tableau .
La méthode [executeBatchImpots] est la suivante :
Commentaires du code
- ligne 185 : la méthode reçoit trois paramètres :
- [$taxPayersFileName] : le nom du fichier texte contenant les données des contribuables. Chaque ligne de texte contient les données d'un contribuable sous la forme : statut marital (oui / non), nombre d'enfants, salaire annuel, par exemple [oui,2,55555] ;
- [$resultsFileName] : le nom du fichier texte qui contiendra les résultats. Chaque ligne de texte sera la chaîne JSON des résultats d'un contribuable, par exemple [{"marié":"oui","enfants":2,"salaire":50000,"impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14}] ;
- [$errorsFileName] : le nom du fichier texte des erreurs, par exemple [la ligne [5] du fichier [taxpayersdata.txt] est erronée] ;
- ligne 188 : les variables des trois fichiers sont initialisées à [false] (fichier non ouvert). Cela permettra de savoir, dans la clause [finally], quels fichiers ont été ouverts ;
- ligne 190 : parce qu'un certain nombre d'opérations peuvent lancer une exception, une structure [try / finally] entoure tout le code de la méthode ;
- lignes 192-204 : les trois fichiers sont ouverts. Une exception est lancée dès qu'une ouverture échoue, avec un code d'erreur différent pour chaque cas ;
- ligne 210 : les lignes du fichier [$data] sont lues une par une à raison de 100 caractères au plus (les lignes font toutes moins de 100 caractères) ;
- ligne 214 : on utilise la méthode statique [Utilitaires::cutNewLineChar] pour enlever l'éventuelle marque de fin de ligne ;
- lignes 216-218 : les lignes vides (par exemple en fin de fichier) sont ignorées ;
- ligne 220 : on découpe la ligne sur la virgule et on supprime les espaces qui entourent chaque champ. [trim(...)] est la syntaxe des « callables de première classe » (PHP 8.1) : elle désigne la fonction [trim] elle-même, sans l'appeler ;
- ligne 221 : la ligne doit avoir exactement trois champs. Le cours PHP 7 écrivait directement [list($marié, $enfants, $salaire) = explode(",", $ligne)] : avec une ligne n'ayant que deux champs, PHP 8 émet un avertissement « Undefined array key 2 » ;
- lignes 227-229 : la validité des trois éléments est vérifiée : le statut marital doit être oui ou non, le nombre d'enfants et le salaire doivent être des entiers positifs ou nuls (une suite de chiffres, expression régulière [/^\d+$/]). Le troisième paramètre [true] de [in_array] demande une comparaison stricte [===] ;
- lignes 232-236 : on ne lance pas une exception si la ligne est erronée mais on écrit un message d'erreur dans le fichier texte [$errors] et on passe à la ligne suivante ;
- ligne 238 : si la ligne lue est valide, le calcul de l'impôt est fait. On obtient un résultat sous forme de tableau associatif [impôt, surcôte, décôte, réduction, taux] ;
- ligne 240 : au résultat obtenu, on ajoute en tête les clés [marié, enfants, salaire] ;
- ligne 241 : le résultat est inscrit dans le fichier texte [$results] sous la forme de sa chaîne JSON. L'option [JSON_THROW_ON_ERROR] fait que la fonction [json_encode] lance une exception [JsonException] plutôt que de rendre [false] si l'encodage échoue ;
- lignes 244-246 : à la fin de l'exploitation du fichier [$data], on regarde le nombre de lignes erronées rencontrées. S'il y en a au moins une, on lance une exception ;
- lignes 247-254 : quelle que soit la façon dont se termine l'exécution de la méthode (normalement ou par une exception), la clause [finally] est exécutée : les fichiers qui ont été ouverts sont fermés. Le cours PHP 7 utilisait une structure [try / catch / finally] dont la clause [catch] se contentait de relancer l'exception. Elle est inutile : une structure [try / finally] sans [catch] laisse remonter l'exception après avoir exécuté la clause [finally]. Par ailleurs, le cours PHP 7 fermait les trois fichiers sans vérifier qu'ils avaient été ouverts : en PHP 8, [fclose(false)] provoque une erreur [TypeError] .
8.7. La classe [ImpotsWithTaxAdminDataInJsonFile]
La classe abstraite [AbstractBaseImpots] n'implémente pas la méthode [getTaxAdminData] de l'interface [InterfaceImpots]. Il nous faut donc la définir dans une classe dérivée. Nous le faisons dans la classe dérivée [ImpotsWithTaxAdminDataInJsonFile] suivante :
Commentaires
- ligne 9 : la classe [ImpotsWithTaxAdminDataInJsonFile] étend la classe abstraite [AbstractBaseImpots]. Elle doit définir la méthode [getTaxAdminData] que sa classe parent n'a pas définie ;
- ligne 12 : l'attribut [$taxAdminData] contiendra les données de l'administration fiscale. Il est en lecture seule : il est initialisé une fois pour toutes par le constructeur. On remarquera que la classe parent a elle aussi un attribut privé [$taxAdminData] : les deux attributs sont distincts, un attribut privé n'étant visible que dans la classe qui le déclare ;
- lignes 15-20 : le constructeur reçoit comme unique paramètre le nom du fichier JSON contenant les données fiscales ;
- ligne 19 : un objet de type [TaxAdminData] est créé à partir du fichier JSON. Cette opération peut lancer une exception de type [ExceptionImpots]. Celle-ci remontera jusqu'au script principal [main.php] ;
- lignes 23-27 : on donne un corps à la méthode [getTaxAdminData] que la classe parent n'avait pas définie. Ici, il suffit de rendre l'attribut [$this->taxAdminData] initialisé par le constructeur .
8.8. Le script [main.php]
Ces classes et interface sont exploitées par le script [main.php] suivant :
Commentaires
- ligne 4 : on impose le respect strict des types des paramètres des fonctions ;
- ligne 7 : le script [main.php] est lui aussi placé dans l'espace de noms [Application] ;
- lignes 10-15 : on charge les fichiers des classes et interfaces utilisées par le script. On notera qu'ici nous n'avons pas utilisé d'instruction [use] pour déclarer le nom complet des classes utilisées par le script. C'est en effet inutile parce que le script et les classes sont dans le même espace de noms [Application]. On notera également l'ordre de chargement : une classe parent ou une interface doit être chargée avant la classe qui la dérive ou l'implémente ;
- lignes 18-21 : les noms des fichiers utilisés dans le script. Ces noms sont relatifs : les fichiers sont cherchés dans le dossier courant, celui à partir duquel on exécute le script ;
- ligne 25 : un objet [ImpotsWithTaxAdminDataInJsonFile] est créé. Cela peut provoquer une exception si le fichier JSON est absent ou incorrect ;
- ligne 27 : on exécute la méthode [executeBatchImpots] qui va faire le calcul des impôts pour tous les contribuables du fichier [TAXPAYERSDATA_FILENAME]. Les résultats seront mis dans le fichier [RESULTS_FILENAME] et les erreurs éventuelles dans le fichier [ERRORS_FILENAME] ;
- lignes 28-31 : en cas d'erreur, on affiche le code et le message de l'erreur. L'expression [{$ex->getCode()}] permet d'insérer dans une chaîne le résultat d'un appel de méthode .
8.9. Résultats
On exécute le script dans un terminal ouvert dans le dossier [version-03] :
Avec le fichier des contribuables [taxpayersdata.txt] présenté plus haut, on obtient le fichier des erreurs [errors.txt] suivant :
et le fichier des résultats [resultats.txt] suivant :
Les impôts calculés pour les onze lignes correctes (2814, 1384, 0, 19884, 16782, 9200, 4230, 22986, 0, 64210, 42842) sont ceux des versions 1 et 2.
8.10. Conclusion
La version 3 a les mêmes fonctionnalités que la version 2 mais elle est mieux armée face aux erreurs : données fiscales incomplètes ou incorrectes, lignes erronées dans le fichier des contribuables, fichiers absents. Chaque erreur est signalée par une exception [ExceptionImpots] qui porte un message et un code précis.
La classe [AbstractBaseImpots] mélange cependant deux types de code : le calcul de l'impôt d'une part, la lecture et l'écriture de fichiers d'autre part. Nous allons voir dans les chapitres suivants comment séparer ces deux préoccupations dans des couches différentes.