Skip to content

11. Ćwiczenie IMPOTS z usługą WEB i architekturą trójwarstwową

Wrócimy do ćwiczenia IMPOTS (patrz punkty 4.2, 4.3, 6) i przekształcimy je w aplikację klient-serwer. Skrypt serwera zostanie podzielony na trzy elementy:

  • warstwa o nazwie [dao] (Data Access Objects), która będzie odpowiedzialna za komunikację z bazą danych MySQL
  • warstwa o nazwie [métier], która będzie obliczać podatek
  • warstwa o nazwie [web], która będzie obsługiwać komunikację z klientami internetowymi.

Skrypt kliencki [1]:

  • przekazuje do skryptu serwerowego trzy informacje ($marié, $enfants, $salaire) niezbędne do obliczenia podatku
  • wyświetla odpowiedź serwera na konsoli

Skrypt serwerowy [2] składa się z warstwy serwerowej [web].

  • Po rozpoczęciu nowej sesji klienta umieści on dane z bazy danych MySQL i [dbimpots] w tablicach. W tym celu wykorzysta warstwę [dao]. Tak utworzone tablice zostaną umieszczone w sesji klienta, aby można było z nich korzystać w kolejnych zapytaniach klienta.
  • Podczas żądania klienta przekaże on trzy informacje ($marié, $enfants, $salaire) do warstwy [métier], która obliczy podatek $impot.
  • Skrypt serwera zwróci obliczoną kwotę podatku $impôt.

11.1. Skrypt kliencki (clients_impots_05_web)

Skrypt kliencki będzie klientem serwisu internetowego służącego do obliczania podatku. Wyśle on (POST) na serwer parametry w postaci:

params=$marié,$enfants,$salaire, gdzie

  • $marié będzie ciągiem znaków oui lub non,
  • $enfants będzie liczbą dzieci,
  • $salaire – wynagrodzenie podatnika

Trzy powyższe parametry znajdują się w pliku tekstowym o nazwie [data.txt] w postaci (stan cywilny, liczba dzieci, wynagrodzenie):

oui,2,200000
non,2,200000
oui,3,200000
non,3,200000
oui,5,50000
non,0,3000000

Skrypt kliencki

  • odczyta plik tekstowy [data.txt] wiersz po wierszu
  • wyśle ciąg znaków params=$marié,$enfants,$salaire do serwisu internetowego obliczającego podatek
  • pobierze odpowiedź z serwisu. Odpowiedź ta może mieć dwie formy:
<erreur>message d'erreur</erreur>
<impot>montant de l'impôt</impot>
  • zapisze odpowiedź serwera w pliku tekstowym o nazwie [resultats.txt] w jednej z dwóch poniższych form:
marié:enfants:salaire:erreur
marié:enfants:salaire:impôt

Kod skryptu klienckiego wygląda następująco:


<?php

// klient podatkowy
// obsługa błędów
ini_set("display_errors", "off");

// ---------------------------------------------------------------------------------
// klasa funkcji pomocniczych
class Utilitaires {

  function cutNewLinechar($ligne) {
    ...
  }

}

// main -----------------------------------------------------
// definicja stałych
$DATA = "data.txt";
$RESULTATS = "resultats.txt";
// dane serwera
$HOTE = "localhost";
$PORT = 80;
$urlServeur = "/exemples-web/impots_05_web.php";

// parametry osób podlegających opodatkowaniu (stan cywilny, liczba dzieci, roczne wynagrodzenie)
// zostały umieszczone w pliku tekstowym $DATA, po jednym wierszu na każdego podatnika
// wyniki (stan cywilny, liczba dzieci, roczne wynagrodzenie, podatek do zapłaty) 
// lub (stan cywilny, liczba dzieci, roczne wynagrodzenie, komunikat o błędzie) zostały umieszczone w
// plik tekstowy $RESULTATS, po jednym wyniku w każdym wierszu

// klasa Narzędzia
$u = new Utilitaires();

// otwieranie pliku z danymi podatników
$data = fopen($DATA, "r");
if (!$data) {
  print "Impossible d'ouvrir en lecture le fichier des données [$DATA]\n";
  exit;
}

// otwarcie pliku wyników
$résultats = fopen($RESULTATS, "w");
if (!$résultats) {
  print "Impossible de créer le fichier des résultats [$RESULTATS]\n";
  exit;
}

// przetwarzamy bieżący wiersz pliku z danymi podatników
while ($ligne = fgets($data, 100)) {
  // usuwamy ewentualny znacznik końca wiersza
  $ligne = $u->cutNewLineChar($ligne);
  // pobieranie trzech pól: „żonaty:dzieci:wynagrodzenie”, które tworzą $ligne
  list($marié, $enfants, $salaire) = explode(",", $ligne);
  // oblicza się podatek
  list($erreur, $impôt) = calculerImpot($HOTE, $PORT, $urlServeur, $cookie, array($marié, $enfants, $salaire));
  // wpisuje się wynik
  $résultat = $erreur ? "$marié:$enfants:$salaire:$erreur" : "$marié:$enfants:$salaire:$impôt";
  fputs($résultats, "$résultat\n");
  // kolejny rekord
}
// zamykamy pliki
fclose($data);
fclose($résultats);

// koniec
print "Terminé...\n";
exit;

function calculerImpot($HOTE, $PORT, $urlServeur, &$cookie, $params) {
  // łączy klienta z ($HOTE,$PORT,$urlServeur)
  // wysyła plik cookie o nazwie $cookie, jeśli jest on niepusty. $cookie jest przekazywany przez odwołanie
  // wysyła plik $params do serwera
  // przetwarza pojedynczą linię zwróconą przez serwer
  
  // otwarcie połączenia na porcie 80 z $HOTE
  $connexion = fsockopen($HOTE, $PORT);
  // błąd?
  if (!$connexion)
    return array("erreur lors de la connexion au serveur ($HOTE, $PORT)");
  // nagłówki (headers) protokołu HTTP muszą kończyć się pustym wierszem
  // POST
  fputs($connexion, "POST $urlServeur HTTP/1.1\n");
  // Host
  fputs($connexion, "Host: localhost\n");
  // Połączenie
  fputs($connexion, "Connection: close\n");
// ciasteczko jest wysyłane, jeśli nie jest puste
  if ($cookie) {
    fputs($connexion, "Cookie: $cookie\n");
  }//if
  // teraz wysyłamy instrukcję do klienta po jej zakodowaniu
  $infos = "params=" . urlencode(implode(",", $params));
  // określa się, jaki rodzaj informacji zostanie wysłany
  fputs($connexion, "Content-type: application/x-www-form-urlencoded\n");
  // wysyłamy rozmiar (liczbę znaków) informacji, które zostaną wysłane
  fputs($connexion, "Content-length: " . strlen($infos) . "\n");
  // wysyłamy pusty wiersz
  fputs($connexion, "\n");
  // wysyłamy informacje
  fputs($connexion, $infos);
  // wyświetla się odpowiedź serwera WWW
  // i należy zadbać o pobranie ewentualnego pliku cookie
  while ($ligne = fgets($connexion, 1000)) {
    // plik cookie – tylko przy pierwszej odpowiedzi
    if (!$cookie) {
      if (preg_match("/^Set-Cookie: (.*?)\s*$/", $ligne, $champs)) {
        $cookie = $champs[1];
      }//if
    }
    // gdy tylko pojawi się pusty wiersz, odpowiedź HTTP jest zakończona
    if (trim($ligne) == "") {
      break;
    }
  }//while
  // odczyt wiersza wyniku
  $ligne = fgets($connexion, 1000);
  // zamyka się połączenie
  fclose($connexion);
  // obliczanie wyniku
  $erreur="";
  $impôt="";
  if (preg_match("/^<erreur>(.*?)<\/erreur>\s*$/", $ligne, $champs)) {
    $erreur = $champs[1];
  } else {
    if (preg_match("/^<impot>(.*?)<\/impot>\s*$/", $ligne, $champs)) {
      $impôt = $champs[1];
    }else{
      $erreur="résultat du serveur non exploitable";
    }
  }
  // powrót
  return array($erreur, $impôt);
}

Komentarze

Kod skryptu klienckiego zawiera elementy, które już znamy:

  • wiersze 9–15: klasa [Utilitaires] została przedstawiona w wersji 3, punkt 6
  • wiersze 17–68: program główny jest analogiczny do programu z wersji 1, paragraf 4.2. Różni się od niego jedynie obliczeniem podatku w wierszu 56.
  • wiersz 56: funkcja obliczania podatku przyjmuje następujące parametry:
    • $HOTE, $PORT, $urlServeur: umożliwiają połączenie z serwisem internetowym
    • $cookie: jest to plik cookie sesji. Parametr ten jest przekazywany przez odwołanie. Jego wartość jest ustalana przez funkcję obliczającą podatek. Przy pierwszym wywołaniu nie ma on wartości. Później ją przyjmuje.
    • array($marié, $enfants, $salaire): reprezentuje wiersz pliku [data.txt]

Funkcja obliczająca podatek zwraca tablicę zawierającą dwa wyniki ($erreur, $impôt), gdzie $erreur to ewentualny komunikat o błędzie, a $impôt to kwota podatku.

  • wiersze 70–134: mamy tu do czynienia z klasycznym klientem HTTP, z jakim często się spotykamy. Należy zwrócić uwagę na następujące kwestie:
  • wiersz 83: parametry ($marié, $enfants, $salaire) są przesyłane do serwera za pomocą POST
  • wiersze 89–91: jeśli klient posiada identyfikator sesji, wysyła go do serwera
  • wiersz 93: utworzenie parametru params
  • wiersz 101: wysłanie parametru params
  • wiersze 104–115: klient odczytuje wszystkie nagłówki HTTP wysłane przez serwer, aż napotka pusty wiersz kończący sekcję nagłówków. Wykorzystuje tę okazję, aby pobrać identyfikator sesji zawarty w odpowiedzi na swoje pierwsze żądanie.
  • wiersze 123–125: przetwarzamy ewentualny wiersz w postaci <erreur>message</erreur>
  • wiersze 126–128: postępuje się analogicznie z ewentualnym wierszem o postaci <impot>montant</impot>
  • wiersz 133: zwracamy wynik

11.2. Serwis internetowy do obliczania podatku

W tym miejscu skupiamy się na trzech skryptach tworzących serwer:

Odpowiedni projekt NetBeans wygląda następująco:

W pliku [1] serwer składa się z następujących skryptów PHP:

  • [impots_05_entites] zawiera klasy wykorzystywane przez serwer
  • [impots_05_dao] zawiera klasy i interfejsy warstwy [dao]
  • [impots_05_metier] zawiera klasy i interfejsy warstwy [metier]
  • [impots_05_web] zawiera klasy i interfejsy warstwy [dao]

Na początek przedstawimy dwie klasy wykorzystywane przez różne warstwy serwisu internetowego.

11.2.1. Entities serwisu internetowego (impots_05_entites)

Baza danych MySQL [dbimpots] zawiera tabelę [impots], w której znajdują się dane niezbędne do obliczenia podatku [1]:

Dane z tabeli MySQL i [impots] zapiszemy w tablicy obiektów Tranche, gdzie Tranche jest następującą klasą:


<?php

// przedział podatkowy
class Tranche {

  // pola prywatne
  private $limite;
  private $coeffR;
  private $coeffN;

  // metody pobierające i ustawiające
  public function getLimite() {
    return $this->limite;
  }

  public function setLimite($limite) {
    $this->limite = $limite;
  }

  public function getCoeffR() {
    return $this->coeffR;
  }

  public function setCoeffR($coeffR) {
    $this->coeffR = $coeffR;
  }

  public function getCoeffN() {
    return $this->coeffN;
  }

  public function setCoeffN($coeffN) {
    $this->coeffN = $coeffN;
  }

  // konstruktor
  public function __construct($limite, $coeffR, $coeffN) {
    $this->setLimite($limite);
    $this->setCoeffR($coeffR);
    $this->setCoeffN($coeffN);
  }

  // toString
  public function __toString(){
    return "[$this->limite,$this->coeffR,$this->coeffN]";
  }
}

Pola prywatne [$limite, $coeffR, $coeffN] będą służyć do przechowywania kolumn [limites, coeffR, coeffN] z wiersza tabeli MySQL [impots].

Ponadto kod serwera będzie korzystał z własnego wyjątku, klasy ImpotsException:

1
2
3
4
5
6
7
class ImpotsException extends Exception {

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

}
  • wiersz 1: klasa [ImpotsException] wywodzi się z klasy [Exception] zdefiniowanej wcześniej w PHP 5
  • wiersz 3: konstruktor klasy [ImpotsException] przyjmuje dwa parametry:
    • $message: komunikat o błędzie
    • $code: kod błędu

11.2.2. Warstwa [dao] (impots_05_dao)

Warstwa [dao] zapewnia dostęp do danych z bazy danych:

Warstwa [dao] posiada następujący interfejs:

1
2
3
4
5
6
// interfejs Dao
interface IImpotsDao {

// zwraca tablicę instancji Tranche
  function getData();
}

Interfejs IImpotsDao udostępnia wyłącznie funkcję getData. Funkcja ta umieszcza w tablicy obiektów Tranche poszczególne wiersze tabeli MySQL [dbimpots.impots].

Klasa implementacyjna jest następująca:


<?php

// warstwa DAO
// zależności
require_once "impots_05_entites.php";
// stałe
define("TABLE", "impots");

// -----------------------------------------------------------------
// implementacja abstrakcyjna
abstract class ImpotsDaoWithPdo implements IImpotsDao {

// pola prywatne
  private $dsn;
  private $user;
  private $passwd;
  private $tranches;

// metody pobierające i ustawiające
  public function getDsn() {
    return $this->dsn;
  }

  public function setDsn($dsn) {
    $this->dsn = $dsn;
  }

  public function getUser() {
    return $this->user;
  }

  public function setUser($user) {
    $this->user = $user;
  }

  public function getPasswd() {
    return $this->passwd;
  }

  public function setPasswd($passwd) {
    $this->passwd = $passwd;
  }

  // konstruktor
  public function __construct($dsn, $user, $passwd) {
    // zapisywanie parametrów
    $this->setDsn($dsn);
    $this->setUser($user);
    $this->setPasswd($passwd);
    // pobieranie danych z SGBD
    // łączy ($user, $pwd) z bazą $dsn
    try {
      // połączenie
      $connexion = new PDO($dsn, $user, $passwd, array(PDO::ATTR_PERSISTENT => true));
      // odczytuje tabelę $TABLE
      $requête = "select limites,coeffR,coeffN from " . TABLE;
      // wykonuje zapytanie $requête na połączeniu $connexion
      $statement = $connexion->prepare($requête);
      $statement->execute();
      // przetwarzanie wyniku zapytania
      while ($colonnes = $statement->fetch()) {
        $this->tranches[] = new Tranche($colonnes[0], $colonnes[1], $colonnes[2]);
      }
      // rozłączenie
      $connexion=NULL;
    } catch (PDOException $e) {
      // powrót z błędem
      throw new ImpotsException($e->getMessage(), 1);
    }
  }

  public function getData(){
    return $this->tranches;
  }
}
  • wiersz 5: implementacja interfejsu [IImpotsDao] wymaga klas zdefiniowanych w skrypcie [impots_05_entites].
  • wiersz 11: definicja klasy abstrakcyjnej. Klasa abstrakcyjna to taka, której nie można instancjonować. Aby można było instancjonować klasę abstrakcyjną, musi ona być obowiązkowo pochodną innej klasy. Klasa może zostać zadeklarowana jako abstrakcyjna, ponieważ nie można jej instancjonować (niektóre z jej metod nie są zdefiniowane) lub po prostu nie chcemy jej instancjonować. W tym przypadku nie chcemy instancjonować klasy [ImpotsDaoWithPdo]. Będziemy instancjonować klasy pochodne.
  • wiersz 11: klasa [ImpotsDaoWithPdo] implementuje interfejs [IImpotsDao]. Musi zatem zdefiniować metodę getData. Metodę tę można znaleźć w wierszach 72–74.
  • wiersz 14: $dsn (Data Source Name) to ciąg znaków, który jednoznacznie identyfikuje SGBD oraz używaną bazę danych.
  • wiersz 15: $user identyfikuje użytkownika łączącego się z bazą danych
  • wiersz 16: $passwd to hasło poprzedniego użytkownika
  • wiersz 17: $tranches to tablica obiektów Tranche, w której zostaną zapisane tabele MySQL i [dbimpots.impots].
  • wiersze 45–70: konstruktor klasy. Kod ten pojawił się już w wersji 4, w paragrafie 8.2. Należy zauważyć, że tworzenie obiektu [ImpotsDaoWithPdo] może się nie powieść. W takim przypadku generowany jest wyjątek typu [ImpotsException].
  • wiersze 72–74: metoda [getData] interfejsu [IImpotsDao].

Klasa [ImpotsDaoWithPdo] jest odpowiednia dla każdego obiektu SGBD. Konstruktor tej klasy, w wierszu 45, wymaga podania nazwy źródła danych (Data Source Name) bazy danych. Ten ciąg znaków zależy od używanego obiektu SGBD. Zdecydowano, aby nie wymagać od użytkownika klasy znajomości tej nazwy źródła danych. Dla każdego pliku SGBD będzie istniała osobna klasa wywodząca się z klasy [ImpotsDaoWithPdo]. W przypadku SGBD i MySQL będzie to następująca klasa:


class ImpotsDaoWithMySQL extends ImpotsDaoWithPdo {

  public function __construct($host, $port, $base, $user, $passwd) {
    parent::__construct("mysql:host=$host;dbname=$base;port=$port", $user, $passwd);
  }

}
  • W wierszu 3 producent nie wymaga podania nazwy źródła danych (Data Source Name), a jedynie nazwy komputera hosta SGBD ($host), jego portu nasłuchowego ($port) oraz nazwy bazy danych ($base).
  • W wierszu 4 tworzona jest nazwa źródła danych (Data Source Name) bazy danych MySQL i wykorzystywana do wywołania konstruktora klasy nadrzędnej.

Należy zauważyć, że aby dostosować się do innego obiektu SGBD, wystarczy napisać odpowiednią klasę pochodną od [ImpotsDaoWithPdo]. Za każdym razem chodzi o utworzenie nazwy źródła danych właściwej dla używanego obiektu SGBD.

11.2.3. Warstwa [métier] (impots_05_metier)

Warstwa [metier] zawiera logikę obliczania podatku:

Warstwa [métier] posiada następujący interfejs:


<?php

// interfejs biznesowy
interface IImpotsMetier {
  public function calculerImpot($marié, $enfants, $salaire);
}

Interfejs [IImpotsMetier] udostępnia tylko jedną metodę – metodę [calculerImpot], która umożliwia obliczenie podatku podatnika na podstawie następujących parametrów:

  • $marié: ciąg znaków „tak” lub „nie”, w zależności od tego, czy podatnik jest w związku małżeńskim, czy nie
  • $enfants: liczba dzieci podatnika
  • $salaire: jego wynagrodzenie

Parametry te zostaną dostarczone przez warstwę [web].

Implementacja interfejsu [IImpotsMetier] wygląda następująco:


// zależności
require_once "impots_05_dao.php";

// ------------------------------------------------------------------
// klasa implementacyjna
class ImpotsMetier implements IImpotsMetier {

  // warstwa DAO
  private $dao;
   // tablica obiektów [Tranche]
  private $data;

  // metody getter i setter
  public function getDao() {
    return $this->dao;
  }

  public function setDao($dao) {
    $this->dao = $dao;
  }

  public function setData($data){
    $this->data=$data;
  }
  
  public function __construct($dao) {
    // pobieramy dane niezbędne do obliczenia podatku
    $this->setDao($dao);
    $this->setData($this->dao->getData());
  }

  public function calculerImpot($marié, $enfants, $salaire) {
    // $marié: tak, nie
    // $enfants: liczba dzieci
    // $salaire: roczne wynagrodzenie
    // liczba udziałów
    $marié = strtolower($marié);
    if ($marié == "oui")
      $nbParts = $enfants / 2 + 2;
    else
      $nbParts=$enfants / 2 + 1;
    // o 1/2 udziału więcej, jeśli jest co najmniej troje dzieci
    if ($enfants >= 3)
      $nbParts+=0.5;
    // dochód podlegający opodatkowaniu
    $revenuImposable = 0.72 * $salaire;
    // współczynnik rodzinny
    $quotient = $revenuImposable / $nbParts;
    // umieszczony na końcu tabeli limitów w celu zakończenia następującej pętli
    $N = count($this->data);
    $this->data[$N - 1]->setLimite($quotient);
    // obliczenie podatku
    $i = 0;
    while ($i < $N and $quotient > $this->data[$i]->getLimite()) {
      $i++;
    }
    // ponieważ umieszczono $quotient na końcu tablicy $limites, poprzednia pętla
    // nie może wykraczać poza tablicę $limites
    // teraz można obliczyć podatek
    return floor($revenuImposable * $this->data[$i]->getCoeffR() - $nbParts * $this->data[$i]->getCoeffN());
  }

}
  • wiersz 2: warstwa [métier] wymaga klas z warstwy [dao] oraz encji (Tranche, ImpotsException).
  • wiersz 6: klasa [ImpotsMetier] implementuje interfejs [IimpotsMetier].
  • wiersze 9–11: pola prywatne klasy:
    • $dao: odwołanie do warstwy [dao]
    • $data: tablica obiektów typu [Tranche] dostarczona przez warstwę [dao]
  • wiersze 26–30: konstruktor klasy inicjuje dwa poprzednie pola. Otrzymuje jako parametr odwołanie do warstwy [dao].
  • wiersze 32–61: implementacja metody [calculerImpot] z interfejsu [IimpotsMetier]. Metoda ta pojawiła się już w wersji 1 (punkt 4.2).

11.2.4. Warstwa [web] (impots_05_web)

Warstwa [metier] zawiera logikę obliczania podatku:

Warstwa [web] składa się z serwisu internetowego, który odpowiada na żądania klientów internetowych. Przypominamy, że klienci ci wysyłają żądanie do serwisu internetowego, przekazując następujący parametr: params=marié,enfants,salaire. Mamy do czynienia z serwisem internetowym takim, jaki udało nam się zbudować w poprzednich akapitach. Jego kod jest następujący:


<?php

// warstwa biznesowa
require_once "impots_05_metier.php";

// obsługa błędów
ini_set("display_errors", "off");

// nagłówek UTF-8
header("Content-Type: text/plain; charset=utf-8");

// ------------------------------------------------------------------------------
// serwis internetowy urzędu skarbowego
// definicja stałych
$HOTE = "localhost";
$PORT = 3306;
$BASE = "dbimpots";
$USER = "root";
$PWD = "";
// dane niezbędne do obliczenia podatku zostały umieszczone w tabeli MySQL IMPOTS
// należącej do bazy $BASE. Tabela ma następującą strukturę
// limity decimal(10,2), coeffR decimal(6,2), coeffN decimal(10,2)
// parametry osób podlegających opodatkowaniu (stan cywilny, liczba dzieci, roczne wynagrodzenie)
// są przesyłane przez klienta w postaci params=stan cywilny, liczba dzieci, roczne wynagrodzenie
// wyniki (stan cywilny, liczba dzieci, roczne wynagrodzenie, podatek do zapłaty) są zwracane klientowi
// w postaci <podatek>podatek</podatek>
// lub w postaci <błąd>błąd</błąd>, jeśli parametry są nieprawidłowe

// w sesji pobierana jest warstwa [métier]
session_start();
if (!isset($_SESSION['metier'])) {
// instancja warstwy [dao] oraz warstwy [métier]
  try {
    $_SESSION['metier'] = new ImpotsMetier(new ImpotsDaoWithMySQL($HOTE, $PORT, $BASE, $USER, $PWD));
  } catch (ImpotsException $ie) {
    print "<erreur>Erreur : " . utf8_encode($ie->getMessage() . "</erreur>");
    exit;
  }
}
$metier = $_SESSION['metier'];

// pobierany jest wiersz wysłany przez klienta
$params = utf8_encode(htmlspecialchars(strtolower(trim($_POST['params']))));
$items = explode(",", $params);
// powinny być tylko 3 parametry
if (count($items) != 3) {
  print "<erreur>[$params] : nombre de paramètres invalides</erreur>\n";
  exit;
}//if
// pierwszy parametr (stan cywilny) musi mieć wartość „tak” lub „nie”
$marié = trim($items[0]);
if ($marié != "oui" and $marié != "non") {
  print "<erreur>[$params] : 1er paramètre invalide</erreur>\n";
  exit;
}//if
// drugi parametr (liczba dzieci) musi być liczbą całkowitą
if (!preg_match("/^\s*(\d+)\s*$/", $items[1], $champs)) {
  print "<erreur>[$params] : 2ième paramètre invalide</erreur>\n";
  exit;
}//if
$enfants = $champs[1];
// trzeci parametr (wynagrodzenie) musi być liczbą całkowitą
if (!preg_match("/^\s*(\d+)\s*$/", $items[2], $champs)) {
  print "<erreur>[$params] : 3ième paramètre invalide</erreur>\n";
  exit;
}//if
$salaire = $champs[1];
// obliczamy podatek
$impôt = $metier->calculerImpot($marié, $enfants, $salaire);
// zwracamy wynik
print "<impot>$impôt</impot>\n";
// koniec
exit;
  • wiersz 4: warstwa [web] wymaga klas z warstwy [métier]
  • wiersze 30–40: odwołanie do warstwy [métier] jest aktywowane w sesji. Jeśli weźmiemy pod uwagę, że warstwa [métier] zawiera odniesienie do warstwy [dao], a ta ostatnia przechowuje dane z warstwy SGBD, to wynika z tego, że:
    • że pierwsze żądanie klienta spowoduje dostęp do warstwy SGBD
    • że kolejne żądania tego samego klienta będą korzystać z danych przechowywanych przez warstwę [dao]. Nie ma zatem dostępu do warstwy SGBD.
  • wiersz 34: utworzenie warstwy [métier] współpracującej z warstwą [dao] zaimplementowaną dla SGBD MySQL
  • wiersze 35–37: obsługa ewentualnego błędu w poprzedniej operacji. W takim przypadku do klienta wysyłany jest wiersz <erreur>message</erreur>.
  • wiersz 43: pobierany jest parametr „params”, który został przesłany przez klienta.
  • wiersze 46–49: sprawdzana jest liczba informacji znalezionych w „params”
  • wiersze 51–55: sprawdzana jest poprawność pierwszej informacji
  • wiersze 56–60: to samo dotyczy drugiego wpisu
  • wiersze 62–66: to samo dotyczy trzeciego
  • wiersz 69: warstwa [métier] oblicza podatek.
  • wiersz 71: wysłanie wyniku do klienta

Wyniki

Przypomnijmy, że klient [client_impots_web_05] wykorzystuje następujący plik [data.txt]:

oui,2,200000
non,2,200000
oui,3,200000
non,3,200000
oui,5,50000
non,0,3000000

Na podstawie tych wierszy (stan cywilny, dzieci, wynagrodzenie) klient wysyła zapytanie do serwera obliczającego podatek i zapisuje wyniki w pliku tekstowym [resultats.txt]. Po uruchomieniu klienta zawartość tego pliku wygląda następująco:

oui:2:200000:22504
non:2:200000:33388
oui:3:200000:16400
non:3:200000:22504
oui:5:50000:0
non:0:3000000:1354938

gdzie każdy wiersz ma postać (stan cywilny, dzieci, wynagrodzenie, obliczony podatek).