XII. Exercice IMPOTS avec un service WEB et une architecture à trois couches▲
Nous allons reprendre l'exercice IMPOTS (cf pages , , ) et allons en faire une application client / serveur. Le script serveur sera décomposé en trois éléments :
- une couche appelée [dao] (Data Access Objects) qui s'occupera des échanges avec la base de données MySQL
- une couche appelée [métier] qui fera le calcul de l'impôt
- une couche [web] qui s'occupera des échanges avec les clients web.
Le script client [1] :
- transmet au script serveur les trois informations ($marié, $enfants, $salaire) nécessaires au calcul de l'impôt
- affiche la réponse du serveur sur la console
Le script serveur [2] est constitué par la couche [web] du serveur.
- lors du début d'une nouvelle session client, il placera dans des tableaux les données de la base de données MySQL [dbimpots]. Pour cela, il fera appel à la couche [dao]. Les tableaux ainsi construits seront placés dans la session du client afin d'être utilisables dans les requêtes ultérieures du client.
- lors d'une requête client, il passera les trois informations ($marié, $enfants, $salaire) à la couche [métier] qui calculera l'impôt $impot.
- le script serveur renverra l'impôt $impôt calculé.
XII-A. Le script client (clients_impots_05_web)▲
Le script client sera un client du service web de calcul de l'impôt. Il postera (POST) au serveur des paramètres sous la forme :
params=$marié,$enfants,$salaire où
- $marié sera la chaîne oui ou non,
- $enfants sera le nombre d'enfants,
- $salaire le salaire du contibuable
Il trouve les trois paramètres précédents dans un fichier texte [data.txt] sous la forme (marié, enfants, salaire) :
Le script client
- lira le fichier texte [data.txt] ligne par ligne
- postera la chaîne params=$marié,$enfants,$salaire au service web de calcul de l'impôt
- récupèrera la réponse du service. Celle-ci pourra avoir deux formes :
- enregistrera la réponse du serveur dans un fichier texte [resultats.txt] sous l'une des deux formessuivantes :
Le code du script client est le suivant :
Commentaires
Le code du script client reprend des choses déjà vues :
- lignes 9-15 : la classe [Utilitaires] a été présentée dans la version 3 page
- lignes 17-68 : le programme principal est analogue à celui de la version 1 page . Il n'en diffère que par le calcul de l'impôt, ligne 56.
- ligne 56 : la fonction de calcul de l'impôt admet les paramètres suivants :
- $HOTE, $PORT, $urlServeur : permettent de se connecter au service web
- $cookie : est le cookie de session. Ce paramètre est passé par référence. Sa valeur est fixée par la fonction de calcul de l'impôt. Lors du 1er appel, il n'a pas de valeur. Ensuite il en a une.
- array($marié, $enfants, $salaire) : représente une ligne du fichier [data.txt]
La fonction de calcul de l'impôt rend un tableau de deux résultats ($erreur, $impôt) où $erreur est un éventuel message d'erreur et $impôt le montant de l'impôt.
- lignes 70 - 134 : on a là un classique client Http comme nous en avons beaucoup rencontré. On notera les points suivants :
- ligne 83 : les paramètres ($marié, $enfants, $salaire) sont transmis au serveur par un POST
- lignes 89-91 : si le client a un identifiant de session, il l'envoie au serveur
- ligne 93 : création du paramètre params
- ligne 101 : envoi du paramètre params
- lignes 104-115 : le client lit tous les en-têtes Http envoyés par le serveur jusqu'à rencontrer la ligne vide de fin des en-têtes. Il en profite pour récupérer l'identifiant de session lors de la réponse à sa première demande.
- lignes 123-125 : on exploite une éventuelle ligne de la forme <erreur>message</erreur>
- lignes 126-128 : on fait de même avec une éventuelle ligne de la forme <impot>montant</impot>
- ligne 133 : on rend le résultat
XII-B. Le service web de calcul de l'impôt▲
Nous nous intéressons ici aux trois scripts qui composent le serveur :
Le projet Netbeans correspondant est le suivant :
En [1], le serveur est formé des scripts PHP suivants :
- [impots_05_entites] contient les classes utilisées par le serveur
- [impots_05_dao] contient les classes et interfaces de la couche [dao]
- [impots_05_metier] contient les classes et interfaces de la couche [metier]
- [impots_05_web] contient les classes et interfaces de la couche [dao]
Nous commençons par présenter deux classes utilisées par les différentes couches du service web.
XII-B-1. Les entités du service web (impots_05_entites)▲
La base MySQL [dbimpots] a une table [impots] qui contient les données nécessaires au calcul de l'impôt [1] :
Nous stockerons les données de la table MySQL [impots] dans un tableau d'objets Tranche où Tranche est la classe suivante :
Les champs privés [$limite, $coeffR, $coeffN] serviront à stocker les colonnes [limites, coeffR, coeffN] d'une ligne de la table MySQL [impots].
Par ailleurs, le code serveur utilisera une exception qui lui sera propre, la classe ImpotsException :
- ligne 1 : la classe [ImpotsException] dérive de la classe [Exception] prédéfinie dans PHP 5
- ligne 3 : le constructeur de la classe [ImpotsException] admet deux paramètres :
- $message : un message d'erreur
- $code : un code d'erreur
XII-B-2. La couche [dao] (impots_05_dao)▲
La couche [dao] assure l'accès aux données de la base de données :
La couche [dao] présente l'interface suivante :
L'interface IImpotsDao n'expose que la fonction getData. Cette fonction place dans un tableau d'objets Tranche, les différentes lignes de la table MySQL [dbimpots.impots].
La classe d'implémentation est la suivante :
- ligne 5 : l'implémentation de l'interface [IImpotsDao] a besoin des classes définies dans le script [impots_05_entites].
- ligne 11 : définition d'une classe abstraite. Une classe abstraite est une classe qu'on ne peut instancier. Une classe abstraite doit être obligatoirement dérivée pour être instanciée. Une classe peut être déclarée abstraite parce qu'on ne peut pas l'instancier (certaines de ses méthodes ne sont pas définies) ou qu'on ne veut pas instancier. Ici, on ne veut pas instancier la classe [ImpotsDaoWithPdo]. On instanciera des classes dérivées.
- ligne 11 : la classe [ImpotsDaoWithPdo] implémente l'interface [IImpotsDao]. Elle doit donc définir la méthode getData . On trouve cette méthode lignes 72-74.
- ligne 14 : $ dsn (Data Source Name) est une chaîne de caractères qui identifie de façon unique le Sgbd et la base de données utilisée.
- ligne 15 : $ user identifie l'utilisateur qui se connecte à la base de données
- ligne 16 : $ passwd est le mot de passe de l'utilisateur précédent
- ligne 17 : $ tranches est le tableau d'objets Tranche dans lequel on va mémoriser la table MySQL [dbimpots.impots].
- lignes 45-70 : le constructeur de la classe. Ce code a déjà été rencontré dans la version 4, page . On notera que la construction de l'objet [ImpotsDaoWithPdo] peut échouer. Une exception de type [ImpotsException] est alors lancée.
- lignes 72-74 : la méthode [getData] de l'interface [IImpotsDao].
La classe [ImpotsDaoWithPdo] convient à tout Sgbd. Le constructeur de la classe, ligne 45, impose de connaître le Data Source Name de la base de données. Cette chaîne de caractères dépend du Sgbd utilisé. On choisit de ne pas imposer à l'utilisateur de la classe de connaître ce Data Source Name. Pour chaque Sgbd, il y aura une classe particulière dérivée de [ImpotsDaoWithPdo]. Pour le Sgbd MySQL, ce sera la classe suivante :
- ligne 3, le constructeur ne demande pas le Data Source Name, mais simplement le nom de la machine hôte du Sgbd ($host), son port d'écoute ($port) et le nom de la base de données ($base).
- ligne 4, le Data Source Name de la base de données MySQL est construit et utilisé pour appeler le constructeur de la classe parente.
On notera que pour s'adapter à un autre Sgbd, il suffit d'écrire la classe dérivée de [ImpotsDaoWithPdo] qui convient. Il s'agit à chaque fois de construire le Data Source Name propre au Sgbd utilisé.
XII-B-3. La couche [métier] (impots_05_metier)▲
La couche [metier] contient la logique du calcul de l'impôt :
La couche [métier] présente l'interface suivante :
L'interface [IImpotsMetier] n'expose qu'une méthode, la méthode [calculerImpot] qui permet de calculer l'impôt d'un contribuable à partir des paramètres suivants :
- $marié : chaîne oui / non selon que le contribuable est marié ou non
- $enfants : le nombre d'enfants du contribuable
- $salaire : son salaire
C'est la couche [web] qui lui fournira ces paramètres.
L'implémentation de l'interface [IImpotsMetier] est la suivante :
- ligne 2 : la couche [métier] a besoin des classes de la couche [dao] et des entités (Tranche, ImpotsException).
- ligne 6 : la classe [ImpotsMetier] implémente l'interface [IimpotsMetier].
- lignes 9-11 : les champs privés de la classe :
- $dao : référence sur la couche [dao]
- $data : tableau d'objets de type [Tranche] fourni par la couche [dao]
- lignes 26-30 : le constructeur de la classe initialise les deux champs précédents. Il reçoit comme paramètre une référence sur la couche [dao].
- lignes 32-61 : implémentation de la méthode [calculerImpot] de l'interface [IimpotsMetier]. Cette méthode a été rencontrée dès la version 1 (page ).
XII-B-4. La couche [web] (impots_05_web)▲
La couche [metier] contient la logique du calcul de l'impôt :
La couche [web] est constitué du service web qui répond aux clients web. On rappelle que ceux-ci font une demande au service web en postant le paramètre suivant : params=marié,enfants,salaire. On a affaire à un service web tel que nous avons pu les construire dans les paragraphes précédents. Son code est le suivant :
- ligne 4 : la couche [web] a besoin des classes de la couche [métier]
- lignes 30-40 : la référence sur la couche [métier] est mise en session. Si on se rappelle que cette couche [métier] a une référence sur la couche [dao] et que cette dernière mémorise les données du Sgbd, on comprend alors :
- que la 1re requête du client va provoquer un accès au Sgbd
- que les requêtes suivantes du même client vont utiliser les données mémorisées par la couche [dao]. Il n'y a donc pas d'accès au Sgbd.
- ligne 34 : construction d'une couche [métier] travaillant avec une couche [dao] implémentée pour le Sgbd MySQL
- lignes 35-37 : gestion d'une éventuelle erreur dans l'opération précédente. Dans ce cas, une ligne <erreur>message</erreur> est envoyé au client.
- ligne 43 : on récupère le paramètre 'params' qui a été posté par le client.
- lignes 46-49 : on vérifie le nombre d'informations trouvées dans 'params'
- lignes 51-55 : on vérifie la validité de la 1re information
- lignes 56-60 : idem pour la 2ième
- lignes 62-66 : idem pour la 3ième
- ligne 69 : c'est la couche [métier] qui calcule l'impôt.
- ligne 71 : envoi du résultat au client
Résultats
On se rappelle que le client [client_impots_web_05] exploite le fichier [data.txt] suivant :
A partir de ces lignes (marié, enfants, salaire), le client interroge le serveur de calcul d'impôt et inscrit les résultats dans le fichier texte [resultats.txt]. Après exécution du client, le contenu de ce fichier est le suivant :
où chaque ligne est de la forme (marié, enfants, salaire, impôt calculé).