IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Introduction au langage PHP 7 par l'exemple


précédentsommairesuivant

XIII. Exercice d’application – version 5

Image non disponible

Nous avons déjà écrit plusieurs versions de cet exercice. La dernière version utilisait une architecture en couches :

Image non disponible

La couche [dao] implémente une interface [InterfaceDao]. Nous avons construit une classe implémentant de cette interface :

  • [DaoImpotsWithTaxAdminDataInJsonFile] qui allait chercher les données fiscales dans un fichier jSON ;

Nous allons implémenter l’interface [InterfaceDao] par une nouvelle classe [DaoImpotsWithTaxAdminDataInDatabase] qui ira chercher les données de l’administration fiscale dans une base de données MySQL.

XIII-A. Création de la base de données [dbimpots-2019]

En suivant l’exemple du paragraphe lienCréation d’une base de données, nous construisons une base de données MySQL nommée [dbimpots-2019] dont le propriétaire sera [admimpots] avec le mot de passe [mdpimpots] :

Image non disponible
  • en [1-4] ci-dessus, nous voyons la base [dbimpots-2019] qui pour l’instant n’a pas de tables ;
Image non disponible
  • en [1-5] ci-dessus, nous voyons que l’utilisateur [admimpots] a tous les droits sur la base [dbimpots-2019]. Ce que nous ne voyons pas ici c’est que cet utilisateur a le mot de passe [admimpots] ;

Nous créons maintenant la table [tbtranches]qui contiendra les tranches d’imposition :

Image non disponible
  • en [1-7], nous créons une table nommée [tbtranches] ayant 4 colonnes ;
Image non disponible
  • en [3-6] nous définissons une colonne nommée [id] (3), de type entier [int] (4), qui sera clé primaire [6] de la table et sera autoincrémentée [5] par le SGBD. Cela signifie que MySQL va gérer lui-même les valeurs de la clé primaire au moment des insertions. Il affectera la valeur 1 à la clé primaire de la 1ière insertion, puis 2 à la suivante, etc … ;
  • en [7], l’assistant nous propose d’autres options de configuration de la clé primaire. Ici on se contente de valider [7] les valeurs par défaut ;
Image non disponible
  • en [8-16], on définit les trois autres colonnes de la table :
    • [limites] (8) de type nombre décimal (9) à 10 chiffres dont 2 décimales (10) contiendra les éléments de la colonne 17 des tranches d’impôts ;
    • [coeffR] (11) de type nombre décimal (12) à 6 chiffres dont 2 décimales (13) contiendra les éléments de la colonne 18 des tranches d’impôts ;
    • [coeffN] (14) de type nombre décimal (15) à 10 chiffres dont 2 décimales (16) contiendra les éléments de la colonne 19 des tranches d’impôts ;

Après avoir validé cette structure, nous obtenons le résultat suivant :

Image non disponible
  • en [5], l’icône de la clé indique que la colonne [id] est clé primaire. On voit également que cette clé primaire a des valeurs entières (6) et qu’elle est gérée (autoincrémentée) par MySQL ;

De la même façon que nous avons créé la table [tbtranches] nous construisons la table [tbconstantes] qui contiendra les constantes du calcul de l’impôt :

Image non disponible

Il est possible d’exporter la structure de la base de données dans un fichier texte sous forme d’une suite d’ordres SQL :

Image non disponible

L’option [5] n’exporte ici que la structure de la base de données et pas son contenu. Dans notre cas, la base n’a pas encore de contenu.

Image non disponible
Image non disponible
Image non disponible

L’option [11] produit le fichier SQL [dbimpots-2019.sql] suivant :

 
Sélectionnez
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.
-- phpMyAdmin SQL Dump
-- version 4.8.5
-- https://www.phpmyadmin.net/
--
-- Host: localhost:3306
-- Generation Time: Jun 30, 2019 at 01:10 PM
-- Server version: 5.7.24
-- PHP Version: 7.2.11

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+00:00";


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;

--
-- Database: `dbimpots-2019`
--
CREATE DATABASE IF NOT EXISTS `dbimpots-2019` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
USE `dbimpots-2019`;

-- --------------------------------------------------------

--
-- Table structure for table `tbconstantes`
--

DROP TABLE IF EXISTS `tbconstantes`;
CREATE TABLE `tbconstantes` (
  `id` int(11) NOT NULL,
  `plafondQfDemiPart` decimal(10,2) NOT NULL,
  `plafondRevenusCelibatairePourReduction` decimal(10,2) NOT NULL,
  `plafondRevenusCouplePourReduction` decimal(10,2) NOT NULL,
  `valeurReducDemiPart` decimal(10,2) NOT NULL,
  `plafondDecoteCelibataire` decimal(10,2) NOT NULL,
  `plafondDecoteCouple` decimal(10,2) NOT NULL,
  `plafondImpotCelibatairePourDecote` decimal(10,2) NOT NULL,
  `plafondImpotCouplePourDecote` decimal(10,2) NOT NULL,
  `abattementDixPourcentMax` decimal(10,2) NOT NULL,
  `abattementDixPourcentMin` decimal(10,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- --------------------------------------------------------

--
-- Table structure for table `tbtranches`
--

DROP TABLE IF EXISTS `tbtranches`;
CREATE TABLE `tbtranches` (
  `id` int(11) NOT NULL,
  `limites` decimal(10,2) NOT NULL,
  `coeffR` decimal(10,2) NOT NULL,
  `coeffN` decimal(10,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Indexes for dumped tables
--

--
-- Indexes for table `tbconstantes`
--
ALTER TABLE `tbconstantes`
  ADD PRIMARY KEY (`id`);

--
-- Indexes for table `tbtranches`
--
ALTER TABLE `tbtranches`
  ADD PRIMARY KEY (`id`);

--
-- AUTO_INCREMENT for dumped tables
--

--
-- AUTO_INCREMENT for table `tbconstantes`
--
ALTER TABLE `tbconstantes`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT for table `tbtranches`
--
ALTER TABLE `tbtranches`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
COMMIT;

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

Vous pouvez utiliser ce fichier SQL pour régénérer la base [dbimpots-2019] si elle a été détruite ou altérée. Il n’y a pas lieu ici de supprimer la base avant de la régénérer puisque le script SQL prend soin de le faire lui-même :

Image non disponible
Image non disponible

XIII-B. Organisation du code

Pour mieux montrer le rôle des différents scripts PHP que nous écrivons, nous allons organiser notre code en dossiers :

Image non disponible
  • en [1], vue d’ensemble de la version 05 ;
  • en [2], les entités de l’application, entités échangées entre couches ;
  • en [3], les utilitaires de l’application ;
  • en [4], les données utilisées ou produites par l’application. Nous prenons ici la décision de n’utiliser que des fichiers jSON pour les fichiers texte. Ceux-ci présentent plusieurs avantages :
    • ils sont reconnus par beaucoup d’outils ;
    • ces outils ont une coloration syntaxique. Par ailleurs, la notation jSON a des règles. Lorsque celles-ci ne sont pas respectées les outils les signalent. Par exemple, une erreur difficile à détecter dans un fichier texte basique est l’utilisation de O majuscule / minuscule à la place de zéros. Si cette erreur se produit elle sera signalée. En effet dans le code jSON :
 
Sélectionnez
"plafondRevenusCouplePourReduction": 42O74

où on a mis par inadvertance un O majuscule à la place du zéro dans [42074], Netbeans signale la faute :

Image non disponible

En effet, Netbeans reconnaît le O majuscule qui fait de [49O74] une chaîne de caractères. Il en conclut que la syntaxe devrait être [4-5] : la chaîne [47O74] devrait être entourée de guillemets. L’attention du développeur est donc attirée par la faute et peut la corriger : soit mettre les guillemets, soit remplacer le O par un zéro ;

Les autres éléments de la version 05 sont les suivants :

Image non disponible
  • en [6], les interfaces et classes de la couche [Dao] ;
  • en [7], les interfaces et classes de la couche [métier] ;
  • en [8], les scripts principaux de la version 05 ;

La version 05 a deux objectifs distincts :

  • remplir la base MySQL [dbimpots-2019] avec le contenu du fichier jSON [Data/txadmindata.json] ;
  • implémenter le calcul de l’impôt avec des données fiscales venant désormais de la base MySQL [dbimpots-2019] ;

Nous allons traiter ces deux objectifs séparément.

XIII-C. Remplissage de base de données [dbimpots-2019]

XIII-C-1. Objectif

Le fichier texte taxadmindata.json contient les données de l’administration fiscale :

 
Sélectionnez
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.
{
    "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
}

Notre objectif est de transférer ces données dans la base MySQL [dbimpots-2019] créée précédemment.

XIII-C-2. Les entités

Image non disponible

L’entité [Database] servira à encapsuler les données du fichier jSON [database.json] suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
{
    "dsn": "mysql:host=localhost;dbname=dbimpots-2019",
    "id": "admimpots",
    "pwd": "mdpimpots",
    "tableTranches": "tbtranches",
    "colLimites": "limites",
    "colCoeffR": "coeffr",
    "colCoeffN": "coeffn",
    "tableConstantes": "tbconstantes",
    "colPlafondQfDemiPart": "plafondQfDemiPart",
    "colPlafondRevenusCelibatairePourReduction": "plafondRevenusCelibatairePourReduction",
    "colPlafondRevenusCouplePourReduction": "plafondRevenusCouplePourReduction",
    "colValeurReducDemiPart": "valeurReducDemiPart",
    "colPlafondDecoteCelibataire": "plafondDecoteCelibataire",
    "colPlafondDecoteCouple": "plafondDecoteCouple",
    "colPlafondImpotCelibatairePourDecote": "plafondImpotCelibatairePourDecote",
    "colPlafondImpotCouplePourDecote": "plafondImpotCouplePourDecote",
    "colAbattementDixPourcentMax": "abattementDixPourcentMax",
    "colAbattementDixPourcentMin": "abattementDixPourcentMin"
}

L’entité [TaxAdminData] servira à encapsuler les données du fichier jSON [taxadmindata.json] suivant :

 
Sélectionnez
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.
{
    "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
}

L’entité [TaxPayerData] servira à encapsuler les données du fichier jSON [taxpayerdata.json] suivant :

 
Sélectionnez
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.
[
    {
        "marié": "oui",
        "enfants": 2,
        "salaire": 55555
    },
    {
        "marié": "ouix",
        "enfants": "2x",
        "salaire": "55555x"
    },
    {
        "marié": "oui",
        "enfants": "2",
        "salaire": 50000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 50000
    },
    {
        "marié": "non",
        "enfants": 2,
        "salaire": 100000
    },
    {
        "marié": "non",
        "enfants": 3,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 5,
        "salaire": 100000
    },
    {
        "marié": "non",
        "enfants": 0,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 2,
        "salaire": 30000
    },
    {
        "marié": "non",
        "enfants": 0,
        "salaire": 200000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 20000
    }
]
XIII-C-2-a. La classe de base [BaseEntity]

Pour simplifier le code des entités, nous adopterons la règle suivante : les attributs d’une entité ont les mêmes noms que les attributs du fichier jSON que l’entité doit encapsuler. Moyennant cette règle, les entités [Database, TaxAdminData, TaxPayerData] ont des points communs qui peuvent être factorisés dans une classe parent. Ce sera la classe [BaseEntity] suivante :

 
Sélectionnez
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.
<?php

namespace Application;

class BaseEntity {
  // attribut
  protected $arrayOfAttributes;

  // initialisation à partir d'un fichier jSON
  public function setFromJsonFile(string $jsonFilename) {
    // on récupère le contenu du fichier des données fiscales
    $fileContents = \file_get_contents($jsonFilename);
    $erreur = FALSE;
    // erreur ?
    if (!$fileContents) {
      // on note l'erreur
      $erreur = TRUE;
      $message = "Le fichier des données [$jsonFilename] n'existe pas";
    }
    if (!$erreur) {
      // on récupère le code jSON du fichier de configuration dans un tableau associatif
      $this->arrayOfAttributes = \json_decode($fileContents, true);
      // erreur ?
      if ($this->arrayOfAttributes === FALSE) {
        // on note l'erreur
        $erreur = TRUE;
        $message = "Le fichier de données jSON [$jsonFilename] n'a pu être exploité correctement";
      }
    }
    // erreur ?
    if ($erreur) {
      // on lance une exception
      throw new ExceptionImpots($message);
    }
    // initialisation des attributs de la classe
    foreach ($this->arrayOfAttributes as $key => $value) {
      $this->$key = $value;
    }
    // on rend l'objet
    return $this;
  }

  public function checkForAllAttributes() {
    // on vérifie que toutes les clés ont été initialisées
    foreach (\array_keys($this->arrayOfAttributes) as $key) {
      if ($key !== "arrayOfAttributes" && !isset($this->$key)) {
        throw new ExceptionImpots("L'attribut [$key] de la classe "
          . get_class($this) . " n'a pas été initialisé");
      }
    }
  }

  public function setFromArrayOfAttributes(array $arrayOfAttributes) {
    // on initialise certains attributs de la classe
    foreach ($arrayOfAttributes as $key => $value) {
      $this->$key = $value;
    }
    // on retourne l'objet
    return $this;
  }

  // toString
  public function __toString() {
    // attributs de l'objet
    $arrayOfAttributes = \get_object_vars($this);
    // on enlève l'attribut de la classe parent
    unset($arrayOfAttributes["arrayOfAttributes"]);
    // chaîne Json de l'objet
    return \json_encode($arrayOfAttributes, JSON_UNESCAPED_UNICODE);
  }

  // getter
  public function getArrayOfAttributes() {
    return $this->arrayOfAttributes;
  }

}

Commentaires

  • ligne 5 : la classe [BaseEntity] est destinée à être étendue par les classes [Database, TaxAdminData, TaxPayerData] ;
  • ligne 7 : l’attribut [$arrayOfAttributes] est un tableau contenant tous les attributs de la classe fille ayant étendu [BaseEntity] ainsi que leurs valeurs ;
  • lignes 9-41 : l’attribut [$arrayOfAttributes] est initialisé à partir du fichier jSON [$jsonFilename] passé en paramètre. Une exception de type [ExceptionImpot] est lancée si le fichier jSON n’a pu être lu ou si ce n’est pas un fichier jSON valide ;
  • lignes 36-38 : on a là un code spécial s’il est exécuté par une classe fille. Dans ce cas, [$this] représente une instance de la classe fille [Database, TaxAdminData, TaxPayerData] et dans ce cas là, les lignes 36-38 initialisent les attributs de cette classe fille, à condition que ces attributs aient la visibilitéprotected (ou public) (cf paragraphe lienVisibilité entre classe Parent et classe Fille). On a dit en effet que les attributs des entités [Database, TaxAdminData, TaxPayerData] étaient les mêmes que les attributs du fichier jSON qu’ils encapsulaient. Finalement, la méthode [setFromJsonFile] permet à une classe fille de s’initialiser à partir d’un fichier jSON ;
  • ligne 40 : on rend l’objet [$this] donc une instance de classe fille si la méthode [setFromJsonFile] a été appelée par une classe fille ;
  • lignes 43-51 : la méthode [checkForAllAttributes] permet à une classe fille de vérifier que tous ses attributs ont été initialisés. Si ce n’est pas le cas, une exception [ExceptionImpots] est lancée. Cette méthode permet à la classe fille de vérifier que son fichier jSON n’a pas oublié certains attributs ;
  • lignes 53-60 : la méthode [setFromArrayOfAttributes] permet à une classe fille d’initialiser tout ou partie de ses attributs à partir d’un tableau associatif dont les clés ont les mêmes noms que les attributs de la classe fille à initialiser ;
  • lignes 63-70 : la méthode [__toString] permet d’avoir la représentation jSON d’une classe fille ;
XIII-C-2-b. L’entité [Database]

L’entité [Database] est la suivante :

 
Sélectionnez
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.
<?php

namespace Application;

class Database extends BaseEntity {
  // attributs
  protected $dsn;
  protected $id;
  protected $pwd;
  protected $tableTranches;
  protected $colLimites;
  protected $colCoeffR;
  protected $colCoeffN;
  protected $tableConstantes;
  protected $colPlafondQfDemiPart;
  protected $colPlafondRevenusCelibatairePourReduction;
  protected $colPlafondRevenusCouplePourReduction;
  protected $colValeurReducDemiPart;
  protected $colPlafondDecoteCelibataire;
  protected $colPlafondDecoteCouple;
  protected $colPlafondImpotCelibatairePourDecote;
  protected $colPlafondImpotCouplePourDecote;
  protected $colAbattementDixPourcentMax;
  protected $colAbattementDixPourcentMin;}

La classe [Database] est utilisée pour encapsuler les données du fichier jSON [database.json] suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
{
    "dsn": "mysql:host=localhost;dbname=dbimpots-2019",
    "id": "admimpots",
    "pwd": "mdpimpots",
    "tableTranches": "tbtranches",
    "colLimites": "limites",
    "colCoeffR": "coeffr",
    "colCoeffN": "coeffn",
    "tableConstantes": "tbconstantes",
    "colPlafondQfDemiPart": "plafondQfDemiPart",
    "colPlafondRevenusCelibatairePourReduction": "plafondRevenusCelibatairePourReduction",
    "colPlafondRevenusCouplePourReduction": "plafondRevenusCouplePourReduction",
    "colValeurReducDemiPart": "valeurReducDemiPart",
    "colPlafondDecoteCelibataire": "plafondDecoteCelibataire",
    "colPlafondDecoteCouple": "plafondDecoteCouple",
    "colPlafondImpotCelibatairePourDecote": "plafondImpotCelibatairePourDecote",
    "colPlafondImpotCouplePourDecote": "plafondImpotCouplePourDecote",
    "colAbattementDixPourcentMax": "abattementDixPourcentMax",
    "colAbattementDixPourcentMin": "abattementDixPourcentMin"
}

La classe et le fichier jSON ont les mêmes attributs. Ceux-ci décrivent les caractéristiques de la base de données MySQL [dbimpots-2019] :

dsn Nom DSN de la base
id Propriétaire de la base
pwd Son mot de passe
tableTranches Nom de la table contenant les tranches d’imposition
colLimites
colCoeffR
colCoeffN
Noms des colonnes de la table [tableTranches]
tableConstantes Nom de la table contenant les constantes de calcul de l’impôt
colPlafondQfDemiPart
colPlafondRevenusCelibatairePourReduction
colPlafondRevenusCouplePourReduction
colValeurReducDemiPart
colPlafondDecoteCelibataire
colPlafondDecoteCouple
colPlafondImpotCelibatairePourDecote
colPlafondImpotCouplePourDecote
colAbattementDixPourcentMax
colAbattementDixPourcentMin
Noms des colonnes de la table [tableConstantes] contenant les constantes de calcul de l’impôt

Pourquoi nommer les tables et les colonnes alors qu’on connaît déjà leurs noms et que ce n’est pas quelque chose amené à changer ? Après le SGBD MySQL, on va utiliser le SGBD PostgreSQL pour stocker les données de l’administration fiscale. Or les noms des colonnes et tables Postgres ne suivent pas les mêmes règles que MySQL. On va être obligés d’utiliser d’autres noms. C’est également vrai pour d’autres SGBD. Si on veut avoir du code portable entre SGBD, il est alors préférable d’utiliser des paramètres plutôt que les noms en dur des tables et colonnes.

Revenons au code de la classe [Database] :

 
Sélectionnez
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.
<?php

namespace Application;

class Database extends BaseEntity {
  // attributs
  protected $dsn;
  protected $id;
  protected $pwd;
  protected $tableTranches;
  protected $colLimites;
  protected $colCoeffR;
  protected $colCoeffN;
  protected $tableConstantes;
  protected $colPlafondQfDemiPart;
  protected $colPlafondRevenusCelibatairePourReduction;
  protected $colPlafondRevenusCouplePourReduction;
  protected $colValeurReducDemiPart;
  protected $colPlafondDecoteCelibataire;
  protected $colPlafondDecoteCouple;
  protected $colPlafondImpotCelibatairePourDecote;
  protected $colPlafondImpotCouplePourDecote;
  protected $colAbattementDixPourcentMax;
  protected $colAbattementDixPourcentMin;

  // setter
  // initialisation
  public function setFromJsonFile(string $jsonFilename) {
    // parent
    parent::setFromJsonFile($jsonFilename);
    // on vérifie que tous les attributs ont été initialisés
    parent::checkForAllAttributes();
    // on retourne l'objet
    return $this;
  }

  // getters et setters
  public function getDsn() {
    return $this->dsn;
  }public function setDsn($dsn) {
    $this->dsn = $dsn;
    return $this;
  }}

Commentaires

  • lignes 7-24 : tous les attributs de la classe ont la visibilité [protected]. C’est la condition pour qu’ils puissent être modifiés depuis la classe parent [BaseEntity] (cf paragraphe lienVisibilité entre classe Parent et classe Fille) ;
  • lignes 28-35 : la méthode [setFromJsonFile] permet d’initialiser les attributs de la classe [Database] à partir du contenu d’un fichier jSON passé en paramètre. Il faut que les attributs du fichier jSON et ceux de la classe [Database] soient identiques. Si le fichier jSON n’est pas exploitable, une exception est lancée ;
  • ligne 30 : c’est la classe parent qui fait l’initialisation ;
  • ligne 32 : on demande à la classe parent de vérifier que tous les attributs de la classe [Database] ont été initialisés. Si ce n’est pas le cas, une exception est lancée ;
  • ligne 34 : on rend l’instance [Database] qui vient d’être initialisée ;
  • lignes 37 et au-delà : les getters et setters des attributs de la classe ;
XIII-C-2-c. L’entité [TaxAdminData]

L’entité [TaxAdminData] est la suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
<?php

namespace Application;

class TaxAdminData extends BaseEntity {
  // tranches d'impôt
  protected $limites;
  protected $coeffR;
  protected $coeffN;
  // constantes de calcul de l'impôt
  protected $plafondQfDemiPart;
  protected $plafondRevenusCelibatairePourReduction;
  protected $plafondRevenusCouplePourReduction;
  protected $valeurReducDemiPart;
  protected $plafondDecoteCelibataire;
  protected $plafondDecoteCouple;
  protected $plafondImpotCouplePourDecote;
  protected $plafondImpotCelibatairePourDecote;
  protected $abattementDixPourcentMax;
  protected $abattementDixPourcentMin;}

La classe [TaxAdminData] est utilisée pour encapsuler les données du fichier jSON [taxadmindata.json] suivant :

 
Sélectionnez
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.
{
    "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
}

La classe et le fichier jSON ont les mêmes attributs. Ceux-ci représentent les données de l’administration fiscale. Le reste du code de la classe [TaxAdminData] est le suivant :

 
Sélectionnez
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.
<?php

namespace Application;

class TaxAdminData extends BaseEntity {
  // tranches d'impôt
  protected $limites;
  protected $coeffR;
  protected $coeffN;
  // constantes de calcul de l'impôt
  protected $plafondQfDemiPart;
  protected $plafondRevenusCelibatairePourReduction;
  protected $plafondRevenusCouplePourReduction;
  protected $valeurReducDemiPart;
  protected $plafondDecoteCelibataire;
  protected $plafondDecoteCouple;
  protected $plafondImpotCouplePourDecote;
  protected $plafondImpotCelibatairePourDecote;
  protected $abattementDixPourcentMax;
  protected $abattementDixPourcentMin;

  // initialisation
  public function setFromJsonFile(string $taxAdminDataFilename) {
    // parent
    parent::setFromJsonFile($taxAdminDataFilename);
    // on vérifie que tous les attributs ont été initialisés
    parent::checkForAllAttributes();
    // on vérifie que les valeurs des attributs sont des réels >=0
    foreach ($this as $key => $value) {
      if ($key !== "arrayOfAttributes") {
        // $value doit être un nbre réel >=0 ou un tableau de réels >=0
        $result = $this->check($value);
        // erreur ?
        if ($result->erreur) {
          // on lance une exception
          throw new ExceptionImpots("La valeur de l'attribut [$key] est invalide");
        } else {
          // on note la valeur
          $this->$key = $result->value;
        }
      }
    }
    // on rend l'objet
    return $this;
  }

  protected function check($value): \stdClass {
    // $value est un tableau d'éléments de type string ou un unique élément
    if (!\is_array($value)) {
      $tableau = [$value];
    } else {
      $tableau = $value;
    }
    // on transforme le tableau de strings en tableau de réels
    $newTableau = [];
    $result = new \stdClass();
    // les éléments du tableau doivent être des nombres décimaux positifs ou nuls
    $modèle = '/^\s*([+]?)\s*(\d+\.\d*|\.\d+|\d+)\s*$/';
    for ($i = 0; $i < count($tableau); $i ++) {
      if (preg_match($modèle, $tableau[$i])) {
        // on met le float dans newTableau
        $newTableau[] = (float) $tableau[$i];
      } else {
        // on note l'erreur
        $result->erreur = TRUE;
        // on quitte
        return $result;
      }
    }
    // on rend le résultat
    $result->erreur = FALSE;
    if (!\is_array($value)) {
      // une seule valeur
      $result->value = $newTableau[0];
    } else {
      // une liste de valeurs
      $result->value = $newTableau;
    }
    return $result;
  }

  // getters et setters}

Commentaires

  • ligne 23 : la méthode [setFromJsonFile] sert à initialiser les attributs de la classe [TaxAdminData] à partir d’un fichier jSON passé en paramètre. Il faut que les attributs du fichier jSON existent sous le même nom dans la classe ;
  • ligne 25 : c’est la classe parent qui fait ce travail ;
  • ligne 27 : on demande à la classe parent de vérifier que tous les attributs de la classe fille ont été initialisés ;
  • lignes 29-42 : on vérifie localement que tous les attributs ont eu une valeur réelle positive ou nulle. Cette vérification a déjà été discutée au paragraphe lienLa classe [TaxAdminData] de la version 03 ;

XIII-C-3. La couche [dao]

Maintenant nous pouvons écrire le code qui va transférer les données du fichier texte [taxadmindata.json] dans les tables [tbtranches, tbconstantes] de la base MySQL [dbimpots-2019]. Nous adopterons l’architecture suivante :

Image non disponible
Image non disponible

La couche [dao] implémentera l’interface [InterfaceDao4TransferAdminDataFromFile2Database] suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
<?php

// espace de noms
namespace Application;

interface InterfaceDao4TransferAdminData2Database {

  public function transferAdminData2Database(): void;
}

Commentaires

  • ligne 8 : la méthode [transferAdminData2Database] a pour rôle de stocker les données de l’administration fiscale dans une base de données ;

L’interface [InterfaceDao4TransferAdminData2Database] sera implémentée par la classe [DaoTransferAdminDataFromJsonFile2Database] suivante :

 
Sélectionnez
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.
<?php

// espace de noms
namespace Application;

// définition d'une classe TransferAdminDataFromFile2DatabaseDao
class DaoTransferAdminDataFromJsonFile2Database implements InterfaceDao4TransferAdminData2Database {
  // attributs de la base de données cible
  private $database;
  // données de l'administration fiscale
  private $taxAdminData;

  // constructeur
  public function __construct(string $databaseFilename, string $taxAdminDataFilename) {
    // on mémorise la configuration de la bd
    $this->database = (new Database())->setFromJsonFile($databaseFilename);
    // on mémorise les données fiscales
    $this->taxAdminData = (new TaxAdminData())->setFromJsonFile($taxAdminDataFilename);
  }

  // transfère les données des tranches d'impôts d'un fichier texte
  // vers la base de données
  public function transferAdminData2Database(): void {
    // on travaille sur la base
    $database = $this->database;
    try {
      // on ouvre la connexion à la base de données
      $connexion = new \PDO($database->getDsn(), $database->getId(), $database->getPwd());
      // on veut qu'à chaque erreur de SGBD, une exception soit lancée
      $connexion->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
      // on démarre une transaction
      $connexion->beginTransaction();
      // on remplit la table des tranches d'impôt
      $this->fillTableTranches($connexion);
      // on remplit la table des constantes
      $this->fillTableConstantes($connexion);
      // on termine la transaction sur un succès
      $connexion->commit();
    } catch (\PDOException $ex) {
      // y-a-t-il une transaction en cours ?
      if (isset($connexion) && $connexion->inTransaction()) {
        // on termine la transaction sur un échec
        $connexion->rollBack();
      }
      // on remonte l'exception au code appelant
      throw new ExceptionImpots($ex->getMessage());
    } finally {
      // on ferme la connexion
      $connexion = NULL;
    }
  }


  // remplissage de la table des tranches d'impôt
  private function fillTableTranches($connexion): void {}

  // remplissage de la table des constantes
  private function fillTableConstantes($connexion): void {}

}

Commentaires

Nous utilisons ici ce que nous avons appris dans le chapitre sur MySQL.

  • ligne 7 : la classe [DaoTransferAdminDataFromJsonFile2Database] implémente l’interface [InterfaceDao4TransferAdminData2Database] ;
  • ligne 9 : l’attribut [$database] est l’objet de type [Database] encapsulant les données du fichier [database.json] ;
  • ligne 11 : l’attribut [$taxAdminData] est l’objet de type [TaxAdminData] encapsulant les données du fichier [taxadmindata.json] ;
  • lignes 14-19 : le constructeur reçoit en paramètres les noms des fichiers [database.json, taxadmindata.json] ;
  • ligne 16 : initialisation de l’attribut [$database] ;
  • ligne 18 : initialisation de l’attribut [$taxAdminData] ;
  • ligne 23 : on implémente l’unique méthode de l’interface [InterfaceDao4TransferAdminData2Database] ;
  • lignes 26-38 : on remplit les tables [tbtranches, tbconstantes] en deux temps :
    • ligne 34 : on remplit d’abord la table [tbtranches]. Cela se fait au sein d’une transaction (lignes 32, 38). La méthode [fillTableTranches] (ligne 55) lance une exception dès que quelque chose se passe mal. Dans ce cas, l’exécution se poursuit avec le catch / finally des lignes 39-50 ;
    • ligne 36 : on remplit la table [tbconstantes] de la même façon à l’aide de la méthode [fillTableConstantes] (ligne 60) ;
  • lignes 39-47 : cas où une exception a été lancée par le code ;
  • lignes 41-44 : si une transaction existe, elle est annulée ;
  • ligne 46 : on lance une exception de type [ExceptionImpots] avec le message de l’exception originelle qui est, elle, d’un type quelconque ;
  • lignes 47-50 : dans la clause [finally], la connexion est fermée ;

Le code de la méthode [fillTableTranches] est le suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
private function fillTableTranches($connexion): void {
    // raccourci pour la bd
    $database = $this->database;
    // les données à insérer dans la base de données
    $limites = $this->taxAdminData->getLimites();
    $coeffR = $this->taxAdminData->getCoeffR();
    $coeffN = $this->taxAdminData->getCoeffN();
    // on vide la table au cas où il y aurait qq chose dedans
    $statement = $connexion->prepare("delete from " . $database->getTableTranches());
    $statement->execute();
    // on prépare les insertions
    $sqlInsert = "insert into {$database->getTableTranches()} "
      . "({$database->getColLimites()}, {$database->getColCoeffR()},"
      . " {$database->getColCoeffN()}) values (:limites, :coeffR, :coeffN)";
    $statement = $connexion->prepare($sqlInsert);
    // on exécute l'ordre préparé avec les valeurs des tranches d'impôts
    for ($i = 0; $i < count($limites); $i++) {
      $statement->execute([
        "limites" => $limites[$i],
        "coeffR" => $coeffR[$i],
        "coeffN" => $coeffN[$i]]);
    }
  }

Commentaires

  • ligne 1 : la méthode [fillTableTranches] reçoit en paramètre une connexion ouverte. On sait de plus qu’une transaction a démarré au sein de cette connexion ;
  • lignes 5-7 : les valeurs à insérer dans la table sont fournies par l’attribut [$taxAdminData] ;
  • lignes 9-10 : on supprime le contenu actuel de la table [tbtranches] ;
  • lignes 12-15 : on prépare l’insertion de lignes dans la table. On utilise ici les noms des colonnes fournis par l’attribut [$database] ;
  • lignes 17-22 : on exécute autant de fois que nécessaire, l’instruction d’insertion préparée aux lignes 12-15 ;

Le code de la méthode [fillTableConstantes] est le suivant :

 
Sélectionnez
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.
private function fillTableConstantes($connexion): void {
    // raccourci
    $database = $this->database;
    // on vide la table au cas où il y aurait qq chose dedans
    $statement = $connexion->prepare("delete from {$database->getTableConstantes()}");
    $statement->execute();
    // on prépare l'insertion
    $taxAdminData = $this->taxAdminData;
    $sqlInsert = "insert into {$database->getTableConstantes()}"
      . " ({$database->getColPlafondQfDemiPart()},"
      . " {$database->getColPlafondRevenusCelibatairePourReduction()},"
      . " {$database->getColPlafondRevenusCouplePourReduction()},"
      . " {$database->getColValeurReducDemiPart()},"
      . " {$database->getColPlafondDecoteCelibataire()},"
      . " {$database->getColPlafondDecoteCouple()},"
      . " {$database->getColPlafondImpotCelibatairePourDecote()},"
      . " {$database->getColPlafondImpotCouplePourDecote()},"
      . " {$database->getColAbattementDixPourcentMax()},"
      . " {$database->getColAbattementDixPourcentMin()})"
      . " values ("
      . ":plafondQfDemiPart,"
      . ":plafondRevenusCelibatairePourReduction,"
      . ":plafondRevenusCouplePourReduction,"
      . ":valeurReducDemiPart,"
      . ":plafondDecoteCelibataire,"
      . ":plafondDecoteCouple,"
      . ":plafondImpotCelibatairePourDecote,"
      . ":plafondImpotCouplePourDecote,"
      . ":abattementDixPourcentMax,"
      . ":abattementDixPourcentMin)";
    $statement = $connexion->prepare($sqlInsert);
    // on exécute l'ordre préparé
    $statement->execute([
      "plafondQfDemiPart" => $taxAdminData->getPlafondQfDemiPart(),
      "plafondRevenusCelibatairePourReduction" => $taxAdminData->getPlafondRevenusCelibatairePourReduction(),
      "plafondRevenusCouplePourReduction" => $taxAdminData->getPlafondRevenusCouplePourReduction(),
      "valeurReducDemiPart" => $taxAdminData->getValeurReducDemiPart(),
      "plafondDecoteCelibataire" => $taxAdminData->getPlafondDecoteCelibataire(),
      "plafondDecoteCouple" => $taxAdminData->getPlafondDecoteCouple(),
      "plafondImpotCelibatairePourDecote" => $taxAdminData->getPlafondImpotCelibatairePourDecote(),
      "plafondImpotCouplePourDecote" => $taxAdminData->getPlafondImpotCouplePourDecote(),
      "abattementDixPourcentMax" => $taxAdminData->getAbattementDixPourcentMax(),
      "abattementDixPourcentMin" => $taxAdminData->getAbattementDixPourcentMin()
    ]);
  }

Commentaires

  • ligne 1 : la méthode [fillTableConstantes] reçoit en paramètre une connexion ouverte. On sait de plus qu’une transaction a démarré au sein de cette connexion ;
  • lignes 5-6 : la table [tbconstantes] est vidée ;
  • lignes 9-31 : préparation de l’ordre SQL d’insertion. Il est complexe du fait qu’il y a 10 colonnes à initialiser dans cette opération d’insertion et qu’il faut aller chercher les noms des colonnes dans l’attribut [$database] ;
  • ligne 33-44 : exécution de l’ordre d’insertion. Il n’y a qu’une ligne à insérer. Là encore, le code est rendu complexe du fait qu’il faille chercher les valeurs à insérer dans l’attribut [$taxAdminData] ;

XIII-C-4. Le script principal

Image non disponible
Image non disponible

Le script principal s’appuie sur la couche [dao] pour opérer le transfert de données :

 
Sélectionnez
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.
<?php

// respect strict des types déclarés des paramètres de foctions
declare (strict_types=1);

// espace de noms
namespace Application;

// gestion des erreurs par PHP
// ini_set("display_errors", "0");
// inclusion interface et classes
require_once __DIR__ . "/../Entities/BaseEntity.php";
require_once __DIR__ . "/../Entities/TaxAdminData.php";
require_once __DIR__ . "/../Entities/TaxPayerData.php";
require_once __DIR__ . "/../Entities/Database.php";
require_once __DIR__ . "/../Entities/ExceptionImpots.php";
require_once __DIR__ . "/../Utilities/Utilitaires.php";
require_once __DIR__ . "/../Dao/InterfaceDao.php";
require_once __DIR__ . "/../Dao/TraitDao.php";
require_once __DIR__ . "/../Dao/InterfaceDao4TransferAdminData2Database.php";
require_once __DIR__ . "/../Dao/DaoTransferAdminDataFromJsonFile2Database.php";
//
// définition des constantes
const DATABASE_CONFIG_FILENAME = "../Data/database.json";
const TAXADMINDATA_FILENAME = "../Data/taxadmindata.json";

//
try {
  // création de la couche [dao]
  $dao = new DaoTransferAdminDataFromJsonFile2Database(DATABASE_CONFIG_FILENAME, TAXADMINDATA_FILENAME);
  // transfert des données dans la base
  $dao->transferAdminData2Database();
} catch (ExceptionImpots $ex) {
  // on affiche l'erreur
  print "L'erreur suivante s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// fin
print "Terminé\n";
exit;

Commentaires

  • lignes 12-21 : chargement des classes et interfaces de l’application ;
  • lignes 24-24 : les deux fichiers jSON ;
  • ligne 30 : on instancie la couche [dao] en passant au constructeur les deux fichiers jSON ;
  • ligne 32 : on opère le transfert de données ;

Lorsque nous exécutons ce code, nous obtenons le résultat suivant dans la base de données :

Image non disponible

Colonne [3], on voit les valeurs attribuées par MySQL à la clé primaire [id]. La numérotation démarre à 1. La copie d’écran ci-dessus a été obtenue après plusieurs exécutions du script.

Image non disponible
Image non disponible

XIII-D. Calcul de l’impôt

Image non disponible

XIII-D-1. Architecture

La version 04 de l’application de calcul d’impôt utilisait une architecture en couches :

Image non disponible

La couche [dao] implémente une interface [InterfaceDao]. Nous avons construit une classe implémentant cette interface :

  • [DaoImpotsWithTaxAdminDataInJsonFile] qui allait chercher les données fiscales dans un fichier jSON. C’était la version 04 ;

Nous allons implémenter l’interface [InterfaceDao] par une nouvelle classe [DaoImpotsWithTaxAdminDataInDatabase] qui ira chercher les données de l’administration fiscale dans une base de données MySQL. La couche [dao], comme précédemment, écrira les résultats et les erreurs dans des fichiers texte et trouvera les données des contribuables également dans un fichier texte. Seulement cette fois-ci, ces fichiers texte seront des fichiers jSON. Par ailleurs, nous savons que si nous continuons à respecter l’interface [InterfaceDao], la couche [métier] n’aura pas à être modifiée.

Image non disponible

XIII-D-2. L’entité [TaxPayerData]

Image non disponible

La classe [TaxPayerData] sert à encapsuler dans une classe les données du fichier jSON [taxpayersdata.json] suivant :

 
Sélectionnez
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.
[
    {
        "marié": "oui",
        "enfants": 2,
        "salaire": 55555
    },
    {
        "marié": "ouix",
        "enfants": "2x",
        "salaire": "55555x"
    },
    {
        "marié": "oui",
        "enfants": "2",
        "salaire": 50000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 50000
    },
    {
        "marié": "non",
        "enfants": 2,
        "salaire": 100000
    },
    {
        "marié": "non",
        "enfants": 3,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 5,
        "salaire": 100000
    },
    {
        "marié": "non",
        "enfants": 0,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 2,
        "salaire": 30000
    },
    {
        "marié": "non",
        "enfants": 0,
        "salaire": 200000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 20000
    }
]

La classe [TaxPayerData] est la suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
<?php

// espace de noms
namespace Application;

// la classe des données
class TaxPayerData extends BaseEntity {
  // données nécessaires au calcul de l'impôt du contribuable
  protected $marié;
  protected $enfants;
  protected $salaire;
  // résultats du calcul de l'impôt
  protected $impôt;
  protected $surcôte;
  protected $décôte;
  protected $réduction;
  protected $taux;

  // getters et setters}

Commentaires

  • ligne 7 : la classe [TaxPayerData] étend la classe [BaseEntity]. Les méthodes de sa classe parent étant suffisantes, la classe [TaxPayerData] n’en définit pas elle-même. On rappelle que les attributs de la classe [TaxPayerData] sont identiques à ceux du fichier jSON [taxpayersdata.json] ;

XIII-D-3. La couche [dao]

XIII-D-3-a. Le trait [TraitDao]

Le trait [TraitDao] implémente une partie de l’interface [InterfaceDao]. Rappelons celle-ci :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
<?php

// espace de noms
namespace Application;

interface InterfaceDao {

  // lecture des données contribuables
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array;

  // lecture des données de l'administration fiscale (tranches d'impôts)
  public function getTaxAdminData(): TaxAdminData;

  // enregistrement des résultats
  public function saveResults(string $resultsFilename, array $taxPayersData): void;
}

Le trait [TraitDao] implémente les méthodes [getTaxPayersData, saveResults] de l’interface [InterfaceDao]. Du fait qu’entre les versions 04 et 05, on a changé la définition de l’entité [TaxPayerData], il nous faut revoir le code de [TraitDao] :

 
Sélectionnez
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.
<?php

// espace de noms
namespace Application;

trait TraitDao {

  // lecture des données contribuables
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array {
    // on récupère les données des contribuables dans un tableau
    $baseEntity = new BaseEntity();
    $baseEntity->setFromJsonFile($taxPayersFilename);
    $arrayOfAttributes = $baseEntity->getArrayOfAttributes();
    // tableau des données contribuables
    $taxPayersData = [];
    // tableau des erreurs
    $errors = [];
    // on boucle sur le tableau des attributs d'élements de type [TaxPayerData]
    $i = 0;
    foreach ($arrayOfAttributes as $attributesOfTaxPayerData) {
      // vérification
      $error = $this->check($attributesOfTaxPayerData);
      if (!$error) {
        // un contribuable de +
        $taxPayersData[] = (new TaxPayerData())->setFrOmArrayOfAttributes($attributesOfTaxPayerData);
      } else {
        // une erreur de + - on note le numéro de la donnée invalide
        $error = ["numéro" => $i] + $error;
        $errors[] = $error;
      }
      // suivant
      $i++;
    }
    // on sauve les erreurs dans un fichier json
    $string = "";
    foreach ($errors as $error) {
      $string .= \json_encode($error, JSON_UNESCAPED_UNICODE) . "\n";
    }
    $this->saveString($errorsFilename, $string);
    // résultat de la fonction
    return $taxPayersData;
  }

  private function check(array $attributesOfTaxPayerData): array {
    // on vérifie les données de [$taxPayerData]
    // la liste des atributs erronés
    $attributes = [];
    // le statut marital doit être oui ou non
    $marié = trim(strtolower($attributesOfTaxPayerData["marié"]));
    $erreur = ($marié !== "oui" and $marié !== "non");
    if ($erreur) {
      // on note l'erreur
      $attributes[] = ["marié" => $marié];
    }
    // le nombre d'enfants doit être un entier positif ou nul
    $enfants = trim($attributesOfTaxPayerData["enfants"]);
    if (!preg_match("/^\d+$/", $enfants)) {
      // on note l'erreur
      $erreur = TRUE;
      $attributes[] = ["enfants" => $enfants];
    } else {
      $enfants = (int) $enfants;
    }

    // le salaire doit être un entier positif ou nul (sans les centimes d'euros)
    $salaire = trim($attributesOfTaxPayerData["salaire"]);
    if (!preg_match("/^\d+$/", $salaire)) {
      // on note l'erreur
      $erreur = TRUE;
      $attributes[] = ["salaire" => $salaire];
    } else {
      $salaire = (int) $salaire;
    }

    // erreur ?
    if ($erreur) {
      // retour avec erreur
      return ["erreurs" => $attributes];
    } else {
      // retour sans erreur
      return [];
    }
  }

  // enregistrement des résultats
  public function saveResults(string $resultsFilename, array $taxPayersData): void {
    // enregistrement du tableau [$taxPayersData] dans le fichier texte [$resultsFileName]
    // si le fichier texte [$resultsFileName] n'existe pas, il est créé
    // construction de la chaîne jSON des résultats
    $string = "[" . implode(",
", $taxPayersData) . "]";
    // enregistrement de cette chaîne
    $this->saveString($resultsFilename, $string);
  }

  // enregistrement d'es résultats d'un tableau dans un fichier texte
  private function saveString(string $fileName, string $data): void {
    // enregistrement de la chaîne [$data] dans le fichier texte [$fileName]
    // si le fichier texte [$fileName] n'existe pas, il est créé
    if (file_put_contents($fileName, $data) === FALSE) {
      throw new ExceptionImpots("Erreur lors de l'enregistrement de données dans le fichier texte [$fileName]");
    }
  }

}

Commentaires

  • [TraitDao] implémente les méthodes [getTaxPayersData] (ligne 9) et [saveResults] (ligne 86) de l’interface [InterfaceDao] ;
  • ligne 9 : la méthode [getTaxPayersData] reçoit en paramètres :
    • [$taxPayersFilename] : le nom du fichier jSON des données des contribuables [taxpayersdata.json] ;
    • [$errorsFilename] : le nom du fichier jSON des erreurs [errors.json] ;
  • lignes 11-13 : le contenu du fichier jSON des données des contribuables est transféré dans un tableau associatif [$arrayOfAttributes]. Si le fichier jSON s’avère inexploitable, une exception [ExceptionImpots] a été lancée ;
  • ligne 15 : le tableau [$taxPayersData] va contenir les données des contribuables encapsulées dans des objets de type [TaxPayerData] ;
  • ligne 17 : on va cumuler les erreurs dans le tableau [$errors] ;
  • lignes 99-33 : construction du tableau [$taxPayersData] ;
  • ligne 22 : avant d’être encapsulées dans un type [TaxPayerData], les données sont vérifiées. La méthode [check] rend :
    • un tableau [‘erreurs’=>[…]] avec les attributs erronés si les données sont incorrectes ;
    • un tableau vide si les données sont correctes ;
  • ligne 25 : cas où les données sont valides. Un nouvel objet [TaxPayerData] est construit et ajouté au tableau [$taxPayersData] ;
  • lignes 26-30 : cas où les données sont invalides. On note dans l’erreur, le n° de l’objet [TaxPayerData] erroné dans le fichier jSON pour que l’utilisateur puisse le retrouver, puis l’erreur est ajoutée au tableau [$errors] ;
  • lignes 35-39 : on enregistre les erreurs rencontrées dans le fichier jSON [$errorsFilename] passé en paramètre, ligne 9 ;
  • ligne 41 : on rend le tableau des objets [TaxPayerData] construits : c’était l’objectif de la méthode ;
  • lignes 44-83 : la méthode privée [check] vérifie la validité des paramètres [marié, enfants, salaire] du tableau [$attributesOfTaxPayerData] passé en paramètre ligne 44. S’il y a des attributs erronés, elle les cumule dans le tableau [$attributes] (lignes 47, 53, 60, 70) sous la forme d’un tableau [‘attribut erroné’=> valeur de l’attribut erroné] ;
  • ligne 78 : s’il y a des erreurs, on rend un tableau [‘erreurs’=>$attributes] ;
  • ligne 81 : s’il n’y a pas d’erreurs, on rend un tableau d’erreurs vide ;
  • lignes 86-93 : implémentation de la méthode [saveResults] de l’interface [InterfaceDao] ;
  • ligne 90 : on construit la chaîne jSON à enregistrer dans le fichier jSON [$resultsFilename] passé en paramètre ligne 86. on doit construire la chaîne jSON d’un tableau :
    • chaque élément du tableau est séparé du suivant par une virgule et un saut de ligne ;
    • l’ensemble du tableau est entouré de crochets [] ;
  • ligne 92 : la chaîne jSON est enregistrée dans le fichier jSON [$resultsFilename] ;
XIII-D-3-b. La classe [DaoImpotsWithTaxAdminDataInDatabase]

La classe [DaoImpotsWithTaxAdminDataInDatabase] implémente l’interface [InterfaceDao] de la façon suivante :

 
Sélectionnez
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.
<?php

// espace de noms
namespace Application;

// définition d'une classe ImpotsWithDataInDatabase
class DaoImpotsWithTaxAdminDataInDatabase implements InterfaceDao {
  // usage d'un trait
  use TraitDao;
  // l'objet de type TaxAdminData qui contient les données des tranches d'impôts
  private $taxAdminData;
  // l'objet de type [Database] contennat les caractéristiques de la BD
  private $database;

  // constructeur
  public function __construct(string $databaseFilename) {
    // on mémorise la configuration jSON de la bd
    $this->database = (new Database())->setFromJsonFile($databaseFilename);
    // on prépare l'attribut
    $this->taxAdminData = new TaxAdminData();
    try {
      // on ouvre la connexion à la base de données
      $connexion = new \PDO(
        $this->database->getDsn(),
        $this->database->getId(),
        $this->database->getPwd());
      // on veut qu'à chaque erreur de SGBD, une exception soit lancée
      $connexion->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
      // on démarre une transaction
      $connexion->beginTransaction();
      // on remplit la table des tranches d'impôt
      $this->getTranches($connexion);
      // on remplit la table des constantes
      $this->getConstantes($connexion);
      // on termine la transaction sur un succès
      $connexion->commit();
    } catch (\PDOException $ex) {
      // y-a-t-il une transaction en cours ?
      if (isset($connexion) && $connexion->inTransaction()) {
        // on termine la transaction sur un échec
        $connexion->rollBack();
      }
      // on remonte l'exception au code appelant
      throw new ExceptionImpots($ex->getMessage());
    } finally {
      // on ferme la connexion
      $connexion = NULL;
    }
  }

  // lecture des données de la base
  private function getTranches($connexion): void {}

  // lecture de la table des constantes
  private function getConstantes($connexion): void {}

  // retourne les données permettant le calcul de l'impôt
  public function getTaxAdminData(): TaxAdminData {
    return $this->taxAdminData;
  }

}

Commentaires

  • ligne 4 : on garde l’espace de noms déjà utilisé pour les autres implémentations de la couche [dao] ;
  • ligne 7 : la classe [DaoImpotsWithTaxAdminDataInDatabase] implémente l’interface [InterfaceDao] ;
  • ligne 9 : on importe le trait [TraitDao]. On sait que ce trait implémente une partie de l’interface. La seule méthode qui reste à implémenter est la méthode [getTaxAdminData] des lignes 62-64. Cette méthode se contente de rendre l’attribut privé [taxAdminData] de la ligne 11. On en déduit que le constructeur devra initialiser cet attribut. C’est son unique rôle ;
  • ligne 16 : le constructeur reçoit comme unique paramètre [$databaseFilename] qui est le nom du fichier jSON [database.json] qui définit la base de données MySQL [dbimpots-2019] ;
  • ligne 18 : le fichier jSON [$databaseFilename] est utilisé pour créer un objet de type [Database] construit et mémorisé dans l’attribut [$database] de la ligne 13. Si le fichier jSON n’a pu être exploité correctement, une exception [ExceptionImpots] a été lancée ;
  • ligne 20 : on crée l’objet [$this->taxAdminData] que le constructeur doit initialiser ;
  • lignes 22-26 : on ouvre la connexion à la base de données. Notez la notation [\PDO] pour désigner la classe [PDO] de PHP. En effet, comme on est dans l’espace de noms [Application], si on écrivait simplement [PDO], ce nom relatif serait préfixé par l’espace de noms courant et donnerait donc la classe [Application\PDO] qui n’existe pas ;
  • ligne 28 : lors d’une erreur, le SGBD lancera une \PDOException (ligne 37) ;
  • ligne 30 : on démarre une transaction. Celle-ci n’est pas vraiment utile car seuls deux ordres SQL vont être exécutés, ordres qui ne modifient pas la base. On le fait néanmoins pour s’isoler des autres utilisateurs de la base ;
  • ligne 32 : la lecture de la table des tranches d’imposition [tbtranches] est faite par la méthode privée [getTranches] de la ligne 52 ;
  • ligne 34 : la lecture de la table des constantes de calcul [tbconstantes] est faite par la méthode privée [getConstantes] de la ligne 57 ;
  • ligne 36 : si on arrive à cette ligne c’est que tout s’est bien passé. On valide donc la transaction;
  • lignes 37-42 : si on arrive là c’est qu’une exception s’est produite. On invalide donc la transaction s’il y en avait une en cours (lignes 39-42). Ligne 44, pour avoir des exceptions homogènes, on relance le message de l’exception reçue sous la forme cette fois d’une exception de type [ExceptionImpots] ;
  • lignes 45-48 : dans tous les cas (exception ou pas) on ferme la connexion ;

La méthode [getTranches] est la suivante :

 
Sélectionnez
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.
private function getTranches($connexion): void {
    // raccourcis
    $database = $this->database;
    $taxAdminData = $this->taxAdminData;
    // on prépare la requête SELECT
    $statement = $connexion->prepare(
      "select {$database->getColLimites()}," .
      " {$database->getColCoeffR()}," .
      " {$database->getColCoeffN()}" .
      " from {$database->getTableTranches()}");
    // on exécute l'ordre préparé avec les valeurs des tranches d'impôts
    $statement->execute();
    // on exploite le résultat
    $limites = [];
    $coeffR = [];
    $coeffN = [];
    // remplissage des trois tableaux
    while ($tranche = $statement->fetch(\PDO::FETCH_OBJ)) {
      $limites[] = (float) $tranche->{$database->getColLimites()};
      $coeffR[] = (float) $tranche->{$database->getColCoeffR()};
      $coeffN[] = (float) $tranche->{$database->getColCoeffN()};
    }
    // on mémorise les données dans l'attribut [$taxAdminData] de la classe
    $taxAdminData->setFromArrayOfAttributes([
      "limites" => $limites,
      "coeffR" => $coeffR,
      "coeffN" => $coeffN
    ]);
  }

Commentaires

  • ligne 1 : la méthode reçoit en paramètre [$connexion] qui est une connexion ouverte et dans laquelle une transaction est en cours ;
  • lignes 2-4 : on crée deux raccourcis pour éviter d’avoir à écrire [$this->database] et [$taxAdminData = $this->taxAdminData] dans tout le code. On a là des copies de références d’objets et non pas une copie des objets eux-mêmes ;
  • ligne 6-10 : l’ordre SELECT est préparé, puis exécuté en ligne 12 ;
  • lignes 13-22 : le résultat du SELECT est exploité. Les informations reçues sont cumulées dans trois tableaux [limites, coeffR, coeffN] ;
  • lignes 24-28 : les trois tableaux sont utilisés pour initialiser l’attribut [$this->taxAdminData] de la classe ;

La méthode privée [getConstantes] est la suivante :

 
Sélectionnez
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.
private function getConstantes($connexion): void {
    // raccourcis
    $database = $this->database;
    $taxAdminData = $this->taxAdminData;
    // on prépare la requête SELECT
    $select = "select {$database->getColPlafondQfDemiPart()}," .
      "{$database->getColPlafondRevenusCelibatairePourReduction()}," .
      "{$database->getColPlafondRevenusCouplePourReduction()}," . "{$database->getColValeurReducDemiPart()}," .
      "{$database->getColPlafondDecoteCelibataire()}," . "{$database->getColPlafondDecoteCouple()}," .
      "{$database->getColPlafondImpotCelibatairePourDecote()}," . "{$database->getColPlafondImpotCouplePourDecote()}," .
      "{$database->getColAbattementDixPourcentMax()}," . "{$database->getColAbattementDixPourcentMin()}" .
      " from {$database->getTableConstantes()}";
    $statement = $connexion->prepare($select);
    // on exécute l'ordre préparé
    $statement->execute();
    // on exploite le résultat - 1 seule ligne ici
    $row = $statement->fetch(\PDO::FETCH_OBJ);
    // on initialise l'attribut [$taxAdminData]
    $taxAdminData->setPlafondQfDemiPart($row->{$database->getColPlafondQfDemiPart()});
    $taxAdminData->setPlafondRevenusCelibatairePourReduction(
      $row->{$database->getColPlafondRevenusCelibatairePourReduction()});
    $taxAdminData->setPlafondRevenusCouplePourReduction($row->{$database->getColPlafondRevenusCouplePourReduction()});
    $taxAdminData->setValeurReducDemiPart($row->{$database->getColValeurReducDemiPart()});
    $taxAdminData->setPlafondDecoteCelibataire($row->{$database->getColPlafondDecoteCelibataire()});
    $taxAdminData->setPlafondDecoteCouple($row->{$database->getColPlafondDecoteCouple()});
    $taxAdminData->setPlafondImpotCelibatairePourDecote($row->{$database->getColPlafondImpotCelibatairePourDecote()});
    $taxAdminData->setPlafondImpotCouplePourDecote($row->{$database->getColPlafondImpotCouplePourDecote()});
    $taxAdminData->setAbattementDixPourcentMax($row->{$database->getColAbattementDixPourcentMax()});
    $taxAdminData->setAbattementDixPourcentMin($row->{$database->getColAbattementDixPourcentMin()});
  }

Commentaires

  • ligne 1 : la méthode reçoit en paramètre [$connexion] qui est une connexion ouverte et dans laquelle une transaction est en cours ;
  • lignes 2-4 : on crée deux raccourcis pour éviter d’avoir à écrire [$this->database] et [$taxAdminData = $this->taxAdminData] dans tout le code. On a là des copies de références d’objets et non pas une copie des objets eux-mêmes ;
  • ligne 6-15 : l’ordre SELECT est préparé, puis exécuté en ligne 15 ;
  • lignes 17-29 : le résultat du SELECT est exploité. Les informations récupérées sont utilisées pour initialiser l’attribut [$this->taxAdminData] de la classe ;

Note : on remarquera que la classe ne dépend pas du SGBD MySQL. C’est le code appelant qui fixe le SGBD utilisé via le DSN de la base de données.

XIII-D-4. La couche [métier]

Image non disponible
  • nous venons d’implémenter la couche [dao] (3) ;
  • parce que nous avons respecté l’interface [InterfaceDao], la couche [métier] (2) peut en théorie rester inchangée. Cependant, nous n’avons pas seulement modifié la couche [dao]. Nous avons également modifié les entités qui elles sont partagées par toutes les couches ;

La couche [métier] implémente l’interface [InterfaceMetier] suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
<?php

// espace de noms
namespace Application;

interface InterfaceMetier {

  // calcul des impôts d'un contribuable
  public function calculerImpot(string $marié, int $enfants, int $salaire): array;

  // calcul des impôts en mode batch
  public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void;
}
  • ligne 12 : la méthode [executeBatchImpots] utilise désormais le fichier jSON [$taxPayersFileName] alors que dans la version 04, c’était un fichier texte basique. ;

Dans la version 04, la méthode [executeBatchImpots] était la suivante :

 
Sélectionnez
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.
public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
    // on laisse remonter les exceptions qui proviennent de la couche [dao]
    // on récupère les données contribuables
    $taxPayersData = $this->dao->getTaxPayersData($taxPayersFileName, $errorsFileName);
    // tableau des résultats
    $results = [];
    // on les exploite
    foreach ($taxPayersData as $taxPayerData) {
      // on calcule l'impôt
      $result = $this->calculerImpot(
        $taxPayerData->getMarié(),
        $taxPayerData->getEnfants(),
        $taxPayerData->getSalaire());
      // on complète [$taxPayerData]
      $taxPayerData->setMontant($result["impôt"]);
      $taxPayerData->setDécôte($result["décôte"]);
      $taxPayerData->setSurCôte($result["surcôte"]);
      $taxPayerData->setTaux($result["taux"]);
      $taxPayerData->setRéduction($result["réduction"]);
      // on met le résultat dans le tableau des résultats
      $results [] = $taxPayerData;
    }
    // enregistrement des résultats
    $this->dao->saveResults($resultsFileName, $results);
  }
  • la ligne 15 est désormais erronée. Dans la nouvelle définition de la classe [TaxPayerData], la méthode [setMontant] n’existe plus ;

Dans la version 05, la méthode [executeBatchImpots] sera la suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
    // on laisse remonter les exceptions qui proviennent de la couche [dao]
    // on récupère les données contribuables
    $taxPayersData = $this->dao->getTaxPayersData($taxPayersFileName, $errorsFileName);
    // tableau des résultats
    $results = [];
    // on les exploite
    foreach ($taxPayersData as $taxPayerData) {
      // on calcule l'impôt
      $result = $this->calculerImpot(
        $taxPayerData->getMarié(),
        $taxPayerData->getEnfants(),
        $taxPayerData->getSalaire());
      // on complète [$taxPayerData]
      $taxPayerData->setFromArrayOfAttributes($result);
      // on met le résultat dans le tableau des résultats
      $results [] = $taxPayerData;
    }
    // enregistrement des résultats
    $this->dao->saveResults($resultsFileName, $results);
  }

Commentaires

  • ligne 15 : au lieu d’utiliser les setters individuels de la la classe [TaxPayerData], on utilise son setter global [setFromArrayOfAttributes] ;
  • le reste du code n’a pas à être modifié ;

XIII-D-5. Le script principal

Image non disponible
  • nous venons d’implémenter les couches [dao] (3) et [métier] (2) ;
  • il nous reste à écrire le script principal (1) ;

Le script principal est analogue à celui de la version 04 :

 
Sélectionnez
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.
<?php

// respect strict des types déclarés des paramètres de foctions
declare (strict_types=1);

// espace de noms
namespace Application;

// gestion des erreurs par PHP
//ini_set("display_errors", "0");
// inclusion interface et classes
require_once __DIR__ . "/../Entities/BaseEntity.php";
require_once __DIR__ . "/../Entities/TaxAdminData.php";
require_once __DIR__ . "/../Entities/TaxPayerData.php";
require_once __DIR__ . "/../Entities/Database.php";
require_once __DIR__ . "/../Entities/ExceptionImpots.php";
require_once __DIR__ . "/../Utilities/Utilitaires.php";
require_once __DIR__ . "/../Dao/InterfaceDao.php";
require_once __DIR__ . "/../Dao/TraitDao.php";
require_once __DIR__ . "/../Dao/DaoImpotsWithTaxAdminDataInDatabase.php";
require_once __DIR__ . "/../Métier/InterfaceMetier.php";
require_once __DIR__ . "/../Métier/Metier.php";
//
// définition des constantes
const DATABASE_CONFIG_FILENAME = "../Data/database.json";
const TAXADMINDATA_FILENAME = "../Data/taxadmindata.json";
const RESULTS_FILENAME = "../Data/resultats.json";
const ERRORS_FILENAME = "../Data/errors.json";
const TAXPAYERSDATA_FILENAME = "../Data/taxpayersdata.json";

try {
  // création de la couche [dao]
  $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
  // création de la couche [métier]
  $métier = new Metier($dao);
  // calcul de l'impôts en mode batch
  $métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
  // on affiche l'erreur
  print "Une erreur s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// fin
print "Terminé\n";
exit;

Commentaires

  • lignes 12-22 : chargement de tous les fichiers de la version 05;
  • lignes 25-29 : les noms des différents fichiers jSON de l’application ;
  • ligne 33 : construction de la couche [dao] ;
  • ligne 35 : construction de la couche [métier] ;
  • ligne 37 : appel de la méthode [executeBatchImpots] de la couche [métier] ;

Résultats

L’application produit deux fichiers jSON :

  • [resultats.json] : les résultats des différents calcul d’impôts ;
  • [errors.json] : qui signale les erreurs trouvées dans le fichier jSON [taxpayersdata.json] ;

Le fichier [errors.json] est le suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
{
    "numéro": 1,
    "erreurs": [
        {
            "marié": "ouix"
        },
        {
            "enfants": "2x"
        },
        {
            "salaire": "55555x"
        }
    ]
}

Cela signifie que dans [taxpayersdata.json], l’élément n° 1 du tableau des contribuables est erroné. Le fichier [taxpayersdata.json] était le suivant :

 
Sélectionnez
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.
[
    {
        "marié": "oui",
        "enfants": 2,
        "salaire": 55555
    },

    {
        "marié": "ouix",
        "enfants": "2x",
        "salaire": "55555x"
    },
    {
        "marié": "oui",
        "enfants": "2",
        "salaire": 50000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 50000
    },
    {
        "marié": "non",
        "enfants": 2,
        "salaire": 100000
    },
    {
        "marié": "non",
        "enfants": 3,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 5,
        "salaire": 100000
    },
    {
        "marié": "non",
        "enfants": 0,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 2,
        "salaire": 30000
    },
    {
        "marié": "non",
        "enfants": 0,
        "salaire": 200000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 20000
    }
]

Le fichier des résultats [resultats.json] est lui le suivant :

 
Sélectionnez
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.
[
    {
        "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é": "non",
        "enfants": 3,
        "salaire": 100000,
        "impôt": 16782,
        "surcôte": 7176,
        "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é": "oui",
        "enfants": 5,
        "salaire": 100000,
        "impôt": 4230,
        "surcôte": 0,
        "décôte": 0,
        "réduction": 0,
        "taux": 0.14
    },
    {
        "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": 20000,
        "impôt": 0,
        "surcôte": 0,
        "décôte": 0,
        "réduction": 0,
        "taux": 0
    }
]

Ces résultats sont conformes à ceux de la version 04.

XIII-E. Tests [Codeception]

Comme il a été fait au paragraphe lienTests [Codeception] pour la version 04, nous allons écrire des tests [Codeception] pour la version 05.

Image non disponible

XIII-E-1. Test de la couche [dao]

Le test [DaoTest.php] est le suivant :

 
Sélectionnez
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.
<?php

// respect strict des types déclarés des paramètres de foctions
declare (strict_types=1);

// espace de noms
namespace Application;

// répertoires racines
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-05");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");

// inclusion interface et classes
require_once ROOT . "/Entities/BaseEntity.php";
require_once ROOT . "/Entities/TaxAdminData.php";
require_once ROOT . "/Entities/TaxPayerData.php";
require_once ROOT . "/Entities/Database.php";
require_once ROOT . "/Entities/ExceptionImpots.php";
require_once ROOT . "/Utilities/Utilitaires.php";
require_once ROOT . "/Dao/InterfaceDao.php";
require_once ROOT . "/Dao/TraitDao.php";
require_once ROOT . "/Dao/DaoImpotsWithTaxAdminDataInDatabase.php";
require_once ROOT . "/Métier/InterfaceMetier.php";
require_once ROOT . "/Métier/Metier.php";
// bibliothèques tierces
require_once VENDOR . "/autoload.php";

// définition des constantes
const DATABASE_CONFIG_FILENAME = ROOT ."/Data/database.json";
const TAXADMINDATA_FILENAME = ROOT ."/Data/taxadmindata.json";
const RESULTS_FILENAME = ROOT ."/Data/resultats.json";
const ERRORS_FILENAME = ROOT ."/Data/errors.json";
const TAXPAYERSDATA_FILENAME = ROOT ."/Data/taxpayersdata.json";

class DaoTest extends \Codeception\Test\Unit {
  // TaxAdminData
  private $taxAdminData;

  public function __construct() {
    parent::__construct();
    // création de la couche [dao]
    $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
    $this->taxAdminData = $dao->getTaxAdminData();
  }

  // tests
  public function testTaxAdminData() {
    // constantes de calcul
    $this->assertEquals(1551, $this->taxAdminData->getPlafondQfDemiPart());}

}

Commentaires

  • lignes 9-33 : définition de l’environnement du test. Nous utilisons le même que celui utilisé par le script principal [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase] décrit au paragraphe lienLe script principal ;
  • lignes 39-44 : construction de la couche [dao] ;
  • ligne 43 : l’attribut [$this->taxAdminData] contient les données à tester ;
  • lignes 47-51 : la méthode [testTaxAdminData] est celle décrite au paragraphe lienTests de la couche [dao] ;

Les résultats du test sont les suivants :

Image non disponible

XIII-E-2. Test de la couche [métier]

Le test [MetierTest.php] est le suivant :

 
Sélectionnez
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.
<?php

// respect strict des types déclarés des paramètres de foctions
declare (strict_types=1);

// espace de noms
namespace Application;

// répertoires racines
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-05");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");

// inclusion interface et classes
require_once ROOT . "/Entities/BaseEntity.php";
require_once ROOT . "/Entities/TaxAdminData.php";
require_once ROOT . "/Entities/TaxPayerData.php";
require_once ROOT . "/Entities/Database.php";
require_once ROOT . "/Entities/ExceptionImpots.php";
require_once ROOT . "/Utilities/Utilitaires.php";
require_once ROOT . "/Dao/InterfaceDao.php";
require_once ROOT . "/Dao/TraitDao.php";
require_once ROOT . "/Dao/DaoImpotsWithTaxAdminDataInDatabase.php";
require_once ROOT . "/Métier/InterfaceMetier.php";
require_once ROOT . "/Métier/Metier.php";
// bibliothèques tierces
require_once VENDOR . "/autoload.php";

// définition des constantes
const DATABASE_CONFIG_FILENAME = ROOT ."/Data/database.json";
const TAXADMINDATA_FILENAME = ROOT ."/Data/taxadmindata.json";
const RESULTS_FILENAME = ROOT ."/Data/resultats.json";
const ERRORS_FILENAME = ROOT ."/Data/errors.json";
const TAXPAYERSDATA_FILENAME = ROOT ."/Data/taxpayersdata.json";

class MetierTest extends \Codeception\Test\Unit {
  // couche métier
  private $métier;

  public function __construct() {
    parent::__construct();
    // création de la couche [dao]
    $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
    // création de la couche [métier]
    $this->métier = new Metier($dao);
  }

  // tests
  public function test1() {
    $result = $this->métier->calculerImpot("oui", 2, 55555);
    $this->assertEqualsWithDelta(2815, $result["impôt"], 1);
    $this->assertEqualsWithDelta(0, $result["surcôte"], 1);
    $this->assertEqualsWithDelta(0, $result["décôte"], 1);
    $this->assertEqualsWithDelta(0, $result["réduction"], 1);
    $this->assertEquals(0.14, $result["taux"]);
  }
…………………………………………………………………………………………………………………..
public function test11() {
    $result = $this->métier->calculerImpot("oui", 3, 200000);
    $this->assertEqualsWithDelta(42842, $result["impôt"], 1);
    $this->assertEqualsWithDelta(17283, $result["surcôte"], 1);
    $this->assertEqualsWithDelta(0, $result["décôte"], 1);
    $this->assertEqualsWithDelta(0, $result["réduction"], 1);
    $this->assertEquals(0.41, $result["taux"]);
  }

}

Commentaires

  • lignes 9-33 : définition de l’environnement du test. Nous utilisons le même que celui utilisé par le script principal [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase] décrit au paragraphe lienLe script principal ;
  • lignes 39-45 : construction des couches [dao] et [métier] ;
  • ligne 44 : l’attribut [$this->métier] référence la couche [métier] ;
  • lignes 47-64 : les méthodes [test1, test2…, test11] sont celles décrites au paragraphe lienTests de la couche [métier] ;

Les résultats du test sont les suivants :

Image non disponible

précédentsommairesuivant

Licence Creative Commons
Le contenu de cet article est rédigé par Serge Tahé et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2019 Developpez.com.