Skip to content

4. Przykładowa aplikacja

Chcielibyśmy zilustrować powyższą metodę na przykładzie obliczania podatków.

4.1. Problem

Chcemy napisać program umożliwiający obliczenie podatku podatnika. Rozważamy uproszczony przypadek podatnika, który ma do zadeklarowania wyłącznie swoje wynagrodzenie:

  • obliczamy liczbę udziałów pracownika nbParts=nbEnfants/2 +1, jeśli jest on osobą stanu wolnego, nbEnfants/2+2, jeśli jest żonaty, gdzie nbEnfants to liczba jego dzieci. Liczba udziałów zwiększa się o 0,5, jeśli ma troje lub więcej dzieci.
  • oblicza się jego dochód podlegający opodatkowaniu R = 0,72 * S, gdzie S to jego roczne wynagrodzenie
  • oblicza się jego współczynnik rodzinny Q = R/N
  • oblicza się podatek I na podstawie następujących danych
limit
coeffR
coeffN
12620,0
0
0
13190
0,05
631
15640
0,1
1290,5
24 740
0,15
2072,5
31810
0,2
3309,5
39 970
0,25
4900
48360
0,3
6898,5
55790
0,35
9316,5
92970
0,4
12106
127860
0,45
16754,5
151250
0,50
23147,5
172 040
0,55
30710
195 000
0,60
39312
0
0,65
49 062
Każdy wiersz zawiera 3 pola graniczne: coeffR, coeffN. Aby obliczyć podatek I, należy znaleźć pierwszy wiersz, w którym QF ≤ wartość graniczna. Na przykład, jeśli QF = 30000, znajdziemy wiersz: 31810 0,2 3309,5. Podatek I wynosi wówczas 0,2*R – 3309,5*nbParts. Jeśli QF jest takie, że relacja QF <= limit nigdy nie jest spełniona, wówczas stosuje się współczynniki z ostatniego wiersza: 0 0,65 49062, co daje podatek I = 0,65*R – 49062*nbParts.

4.2. Baza danych

Powyższe dane są zapisane w bazie o nazwie MySQL, zwanej dbimpots. Użytkownik seldbimpots z hasłem mdpseldbimpots ma dostęp tylko do odczytu do zawartości bazy. Baza ta zawiera jedną tabelę o nazwie impots, której struktura jest następująca:

Image

Jej zawartość jest następująca:

Image

4.3. Architektura aplikacji MVC

Aplikacja będzie miała następującą architekturę MVC:

  • kontroler main.php będzie kontrolerem ogólnym przedstawionym wcześniej
  • Żądanie klienta jest wysyłane do kontrolera w postaci zapytania o postaci main.php?action=xx. Wartość parametru „action” określa skrypt bloku ACTIONS, który ma zostać wykonany. Wykonany skrypt akcji zwraca do kontrolera zmienną wskazującą stan, w jakim należy ustawić aplikację internetową. Na podstawie tego stanu kontroler uruchamia jeden z generatorów widoków, aby wysłać odpowiedź do klienta.
  • impots-data.php to klasa odpowiedzialna za dostarczanie kontrolerowi potrzebnych danych
  • impots-calcul.php to klasa biznesowa umożliwiająca obliczenie podatku

4.4. Klasa dostępu do danych

Klasa dostępu do danych została zaprojektowana tak, aby ukryć przed aplikacją internetową pochodzenie danych. W jej interfejsie znajduje się metoda getData, która dostarcza trzy tablice danych niezbędne do obliczenia podatku. W naszym przykładzie dane są pobierane z bazy danych MySQL. Aby klasa była niezależna od rzeczywistego typu SGBD, wykorzystamy bibliotekę pear::DB opisaną w załączniku. Kod klasy wygląda następująco:

<?php

  // biblioteki
  require_once 'DB.php';

  class impots_data{  
    // klasa dostępu do źródła danych DBIMPOTS

      // atrybuty
    var $sDSN;                    // ciąg połączenia
      var $sDatabase;            // nazwa bazy danych
    var $oDB;                        // połączenie z bazą danych
    var $aErreurs;            // lista błędów
    var $oRésultats;        // wynik zapytania
        var $connecté;            // wartość logiczna wskazująca, czy nawiązano połączenie z bazą danych
        var $sQuery;                // ostatnie wykonane zapytanie

    // konstruktor
    function impots_data($dDSN){

        // $dDSN: słownik definiujący połączenie, które ma zostać nawiązane
      // $dDSN['sgbd']: typ obiektu SGBD, z którym należy się połączyć
      // $dDSN['host']: nazwa komputera hosta, na którym się znajduje      
      // $dDSN['database']: nazwa bazy danych, z którą należy się połączyć      
      // $dDSN['user']: użytkownik bazy danych
      // $dDSN['mdp']: jego hasło

      // tworzy w $oDB połączenie z bazą zdefiniowaną przez $dDSN pod tożsamością $dDSN['user']
      // jeśli połączenie się powiedzie  
          // zapisuje w $sDSN ciąg połączenia z bazą danych
          // zapisuje w zmiennej $sDataBase nazwę bazy danych, z którą nawiązano połączenie
        // ustawia $connecté na wartość true
      // jeśli połączenie się nie powiedzie
          // umieszcza odpowiednie komunikaty o błędach na liście $aErreurs
        // zamyka połączenie, jeśli zajdzie taka potrzeba
        // ustawia $connecté na „false” 

      // kasuje listę błędów
            $this->aErreurs=array();

      // tworzy się połączenie z bazą danych $sDSN
      $this->sDSN=$dDSN["sgbd"]."://".$dDSN["user"].":".$dDSN["mdp"]."@".$dDSN["host"]."/".$dDSN["database"];
      $this->sDatabase=$dDSN["database"];
      $this->connect();

      // połączono?
      if( ! $this->connecté) return;

      // połączenie nawiązano     
      $this->connecté=TRUE;     
    }//producent

    // ------------------------------------------------------------------
    function connect(){
        // (ponowne) połączenie z bazą danych
      // wyzerowanie listy błędów
            $this->aErreurs=array();

      // tworzy się połączenie z bazą danych $sDSN
        $this->oDB=DB::connect($this->sDSN,true);

        // błąd?
        if(DB::iserror($this->oDB)){
          // rejestruje się błąd
          $this->aErreurs[]="Echec de la connexion à la base [".$this->sDatabase."] : [".$this->oDB->getMessage()."]";
        // połączenie nie powiodło się
        $this->connecté=FALSE;
        // koniec
        return;
      }

      // połączenie nawiązane
      $this->connecté=TRUE;
    }//połączenie

    // ------------------------------------------------------------------
    function disconnect(){
      // jeśli połączenie jest aktywne, zamykamy połączenie z bazą $sDSN
        if($this->connecté){
              $this->oDB->disconnect();
          // jesteśmy odłączeni
        $this->connecté=FALSE;
            }//if
    }//rozłącz

    // -------------------------------------------------------------------
    function execute($sQuery){
        // $sQuery: zapytanie do wykonania

        // zapisujemy zapytanie
            $this->sQuery=$sQuery;    

      // czy jesteśmy zalogowani?
      if(! $this->connecté){
          // odnotowuje się błąd
        $this->aErreurs[]="Pas de connexion existante à la base [$this->sDatabase]";
        // koniec
        return;
      }//if

      // wykonanie zapytania
      $this->oRésultats=$this->oDB->query($sQuery);

        // błąd?
        if(DB::iserror($this->oRésultats)){
            // odnotowano błąd
            $this->aErreurs[]="Echec de la requête [$sQuery] : [".$this->oRésultats->getMessage()."]";
          // powrót
        return;
      }//if     
    }//wykonaj

    // ------------------------------------------------------------------
    function getData(){
      // pobieramy 3 serie danych: limity, współczynniki, współczynniki n
      $this->execute('select limites, coeffR, coeffN from impots');
      // czy są błędy?
      if(count($this->aErreurs)!=0) return array();
      // przeglądamy wynik zapytania SELECT
      while ($ligne = $this->oRésultats->fetchRow(DB_FETCHMODE_ASSOC)) {
        $limites[]=$ligne['limites'];
        $coeffr[]=$ligne['coeffR'];
        $coeffn[]=$ligne['coeffN'];                
      }//while
      return array($limites,$coeffr,$coeffn);      
    }//getDataImpots

  }//klasa
?>      

Program testowy mógłby wyglądać następująco:

<?php

  // biblioteka
  require_once "c-impots-data.php";  
  require_once "DB.php";

    // test klasy impots-data
  ini_set('track_errors','on');
  ini_set('display_errors','on');

     // konfiguracja bazy danych dbimpots
    $dDSN=array(
        "sgbd"=>"mysql",
        "user"=>"seldbimpots",
        "mdp"=>"mdpseldbimpots",
        "host"=>"localhost",
        "database"=>"dbimpots"
    );

  // logowanie
  $oImpots=new impots_data($dDSN);
  // błędy?
  if(checkErreurs($oImpots)){
    exit(0);
  }
  // monitorowanie
  echo "Connecté à la base...\n";

  // pobieranie danych dotyczących limitów, współczynników coeffr i coeffn
  list($limites,$coeffr,$coeffn)=$oImpots->getData();
  // błędy?
  if( ! checkErreurs($oImpots)){
    // treść
    echo "données : \n";
    for($i=0;$i<count($limites);$i++){
      echo "[$limites[$i],$coeffr[$i],$coeffn[$i]]\n";
    }//for
  }//if

  // rozłączamy się
  $oImpots->disconnect();
  // śledzenie
  echo "Déconnecté de la base...\n";  
  // koniec
  exit(0);

  // ----------------------------------
  function checkErreurs(&$oImpots){
      // czy są jakieś błędy?
    if(count($oImpots->aErreurs)!=0){
        // wyświetlanie
      for($i=0;$i<count($oImpots->aErreurs);$i++){
          echo $oImpots->aErreurs[$i]."\n";
      }//dla
      // błędów
      return true;
    }//if
    // brak błędów
    return false;
  }//checkErreurs  

?>     

Uruchomienie tego programu testowego daje następujące wyniki:

Connecté à la base...
données : 
[12620,0,0]
[13190,0.05,631]
[15640,0.1,1290.5]
[24740,0.15,2072.5]
[31810,0.2,3309.5]
[39970,0.25,4900]
[48360,0.3,6898]
[55790,0.35,9316.5]
[92970,0.4,12106]
[127860,0.45,16754]
[151250,0.5,23147.5]
[172040,0.55,30710]
[195000,0.6,39312]
[0,0.65,49062]
Déconnecté de la base...

4.5. Klasa służąca do obliczania podatku

Klasa ta służy do obliczania podatku podatnika. Do jej konstruktora przekazywane są dane niezbędne do wykonania obliczeń. Następnie klasa ta oblicza odpowiedni podatek. Kod klasy jest następujący:

<?php

  class impots_calcul{  
    // klasa obliczania podatku

    // konstruktor
    function impots_calcul(&$perso,&$data){
      // $perso: słownik z następującymi kluczami
      // dzieci: liczba dzieci
      // wynagrodzenie: roczne wynagrodzenie
      // marié(e): wartość logiczna wskazująca, czy podatnik jest w związku małżeńskim, czy nie
      // podatek(i): podatek do zapłaty obliczony przez tę funkcję
      // $data: słownik zawierający następujące klucze
      // limity: tabela limitów przedziałów podatkowych
      // współczynniki: tabela współczynników dochodu
      // współczynniki: tabela współczynników liczby udziałów
      // wszystkie 3 tabele mają taką samą liczbę elementów

      // obliczenie liczby udziałów
      if($perso['marié'])
        $nbParts=$perso['enfants']/2+2;
        else $nbParts=$perso['enfants']/2+1;
      if ($perso['enfants']>=3) $nbParts+=0.5;

      // dochód podlegający opodatkowaniu
      $revenu=0.72*$perso['salaire'];

      // współczynnik rodzinny
      $QF=$revenu/$nbParts;

      // wyszukiwanie przedziału podatkowego odpowiadającego QF
      $nbTranches=count($data['limites']);
      $i=0;
      while($i<$nbTranches-2 && $QF>$data['limites'][$i]) $i++;

      // podatek
      $perso['impot']=floor($data['coeffr'][$i]*$revenu-$data['coeffn'][$i]*$nbParts);
    }//deweloper
  }//klasa
?>

Program testowy mógłby wyglądać następująco:

<?php

  // biblioteka
  require_once "c-impots-data.php";
  require_once "c-impots-calcul.php";  

     // konfiguracja bazy dbimpots
    $dDSN=array(
        "sgbd"=>"mysql",
        "user"=>"seldbimpots",
        "mdp"=>"mdpseldbimpots",
        "host"=>"localhost",
        "database"=>"dbimpots"
    );

  // logowanie
  $oImpots=new impots_data($dDSN);
  // błędy?
  if(checkErreurs($oImpots)){
    exit(0);
  }
  // monitorowanie
  echo "Connecté à la base...\n";  
  // pobieranie danych dotyczących limitów, współczynników coeffr i coeffn
  list($limites,$coeffr,$coeffn)=$oImpots->getData();
  // błędy?
  if(checkErreurs($oImpots)){
    exit(0);
  }
  // rozłączamy się
  $oImpots->disconnect();
  // śledzenie
  echo "Déconnecté de la base...\n";  

  // obliczanie podatku
  $dData=array('limites'=>&$limites,'coeffr'=>&$coeffr,'coeffn'=>&$coeffn);
  $dPerso=array('enfants'=>2,'salaire'=>200000,'marié'=>true,'impot'=>0);
  new impots_calcul($dPerso,$dData);
  dump($dPerso);
  $dPerso=array('enfants'=>3,'salaire'=>200000,'marié'=>false,'impot'=>0);
  new impots_calcul($dPerso,$dData);
  dump($dPerso);
  $dPerso=array('enfants'=>3,'salaire'=>20000,'marié'=>true,'impot'=>0);
  new impots_calcul($dPerso,$dData);
  dump($dPerso);
  $dPerso=array('enfants'=>3,'salaire'=>2000000,'marié'=>true,'impot'=>0);
  new impots_calcul($dPerso,$dData);
  dump($dPerso);

  // koniec
  exit(0);

  // ----------------------------------
  function checkErreurs(&$oImpots){
      // błędy?
    if(count($oImpots->aErreurs)!=0){
        // wyświetlanie
      for($i=0;$i<count($oImpots->aErreurs);$i++){
          echo $oImpots->aErreurs[$i]."\n";
      }//dla
      // błędy
      return true;
    }//if
    // brak błędów
    return false;
  }//checkErreurs  

?>        

Uruchomienie tego programu testowego daje następujące wyniki:

Connecté à la base...
Déconnecté de la base...
[enfants,2] [salaire,200000] [marié,1] [impot,22506] 
[enfants,3] [salaire,200000] [marié,] [impot,22506] 
[enfants,3] [salaire,20000] [marié,1] [impot,0] 
[enfants,3] [salaire,2000000] [marié,1] [impot,706752]

4.6. Działanie aplikacji

Po uruchomieniu internetowej aplikacji do obliczania podatku wyświetla się następujący ekran [v-formulaire]:

Użytkownik wypełnia pola i zleca obliczenie podatku:

Warto zauważyć, że formularz jest odtwarzany w stanie, w jakim został zatwierdzony przez użytkownika, a ponadto wyświetla kwotę podatku do zapłaty. Użytkownik może popełnić błędy podczas wprowadzania danych. Są one sygnalizowane na stronie błędów, którą nazwiemy widokiem [v-erreurs].

Link [Retour au formulaire de saisie] umożliwia użytkownikowi wyświetlenie formularza w postaci, w jakiej został zatwierdzony.

Wreszcie przycisk [Effacer le formulaire] przywraca formularz do stanu początkowego, c.a.d, w jakim użytkownik go otrzymał podczas pierwotnego żądania.

4.7. Omówienie architektury aplikacji MVC

Aplikacja ma następującą architekturę MVC:

Właśnie opisaliśmy dwie klasy: impots-data.php i impots-calcul.php. Teraz omówimy pozostałe elementy architektury.

4.8. Kontroler aplikacji

Kontroler aplikacji main.php to ten, który został opisany w pierwszej części tego rozdziału. Jest to kontroler ogólny, niezależny od aplikacji.

<?php
     // kontroler ogólny

  // odczyt konfiguracji
  include 'config.php';

  // dołączanie bibliotek
  for($i=0;$i<count($dConfig['includes']);$i++){
      include($dConfig['includes'][$i]);
  }//for  

  // rozpoczynamy lub wznawiamy sesję
  session_start();
  $dSession=$_SESSION["session"];
  if($dSession) $dSession=unserialize($dSession);

  // pobieranie akcji do wykonania
  $sAction=$_GET['action'] ? strtolower($_GET['action']) : 'init';
  $sAction=strtolower($_SERVER['REQUEST_METHOD']).":$sAction";

     // czy sekwencja działań przebiega prawidłowo?
  if( ! enchainementOK($dConfig,$dSession,$sAction)){  
    // nienormalna sekwencja
    $sAction='enchainementinvalide';
  }//if

     // przetwarzanie akcji
  $scriptAction=$dConfig['actions'][$sAction] ? 
    $dConfig['actions'][$sAction]['url'] : 
    $dConfig['actions']['actionInvalide']['url'];
  include $scriptAction;


  // wysłanie odpowiedzi (widoku) do klienta
  $sEtat=$dSession['etat']['principal'];
  $scriptVue=$dConfig['etats'][$sEtat]['vue'];
  include $scriptVue;

  // koniec skryptu – nie powinno się tu dotrzeć, chyba że występuje błąd
  trace ("Erreur de configuration.");
  trace("Action=[$sAction]");
  trace("scriptAction=[$scriptAction]");
  trace("Etat=[$sEtat]");
  trace("scriptVue=[$scriptVue]");
  trace ("Vérifiez que les script existent et que le script [$scriptVue] se termine par l'appel à finSession.");
  exit(0);

  // ---------------------------------------------------------------
  function finSession(&$dConfig,&$dReponse,&$dSession){
    // $dConfig: słownik konfiguracyjny
      // $dSession: słownik zawierający informacje o sesji
         // $dReponse: słownik argumentów strony odpowiedzi

    // zapis sesji
    if(isset($dSession)){
      // parametry żądania są umieszczane w sesji
      $dSession['requete']=strtolower($_SERVER['REQUEST_METHOD'])=='get' ? $_GET :
          strtolower($_SERVER['REQUEST_METHOD'])=='post' ? $_POST : array();
        $_SESSION['session']=serialize($dSession);
      session_write_close();
    }else{    
        // brak sesji
      session_destroy();
    }

        // wyświetlanie odpowiedzi
        include $dConfig['vuesReponse'][$dReponse['vuereponse']]['url'];

    // koniec skryptu
    exit(0);
  }//zakończenie sesji      

  //--------------------------------------------------------------------
    function enchainementOK(&$dConfig,&$dSession,$sAction){
      // sprawdza, czy bieżąca akcja jest dozwolona w odniesieniu do poprzedniego stanu
    $etat=$dSession['etat']['principal'];
    if(! isset($etat)) $etat='sansetat';

    // weryfikacja akcji
    $actionsautorisees=$dConfig['etats'][$etat]['actionsautorisees'];
    $autorise= ! isset($actionsautorisees) || in_array($sAction,$actionsautorisees);
        return $autorise;    
  }

  //--------------------------------------------------------------------
  function dump($dInfos){
      // wyświetla słownik informacji
    while(list($clé,$valeur)=each($dInfos)){
        echo "[$clé,$valeur]<br>\n";
    }//while
  }//monitorowanie

  //--------------------------------------------------------------------
  function trace($msg){
      echo $msg."<br>\n";
  }//monitorowanie
?>

4.9. Akcje w aplikacji internetowej

Istnieją cztery akcje:

  • get:init: jest to akcja uruchamiana podczas pierwszego żądania skierowanego do kontrolera bez parametrów. Generuje ona pusty widok „formularza”.
  • post:effacerformulaire: akcja uruchamiana przez przycisk [Effacer le formulaire]. Generuje pusty widok [v-formulaire].
  • post:calculerimpot: akcja uruchamiana przez przycisk [Calculer l'impôt]. Generuje ona albo widok [v-formulaire] z kwotą podatku do zapłaty, albo widok [v-erreurs].
  • get:retourformulaire: akcja uruchamiana przez link [Retour au formulaire de saisie]. Generuje widok [v-formulaire] wstępnie wypełniony błędnymi danymi.

Działania te są skonfigurowane w następujący sposób w pliku konfiguracyjnym:

<?php

  // konfiguracja działań aplikacji
  $dConfig['actions']['get:init']=array('url'=>'a-init.php');  
  $dConfig['actions']['post:calculerimpot']=array('url'=>'a-calculimpot.php');
  $dConfig['actions']['get:retourformulaire']=array('url'=>'a-retourformulaire.php');
  $dConfig['actions']['post:effacerformulaire']=array('url'=>'a-init.php');
  $dConfig['actions']['enchainementinvalide']=array('url'=>'a-enchainementinvalide.php');
  $dConfig['actions']['actionInvalide']=array('url'=>'a-actioninvalide.php');          

Każdej akcji przypisany jest skrypt odpowiedzialny za jej przetworzenie. Każda akcja przenosi aplikację internetową do stanu określonego przez element $dSession['etat']['principal']. Stan ten ma zostać zapisany w sesji. Ponadto akcja zapisuje w słowniku $dReponse informacje niezbędne do wyświetlenia widoku powiązanego z nowym stanem, w którym znajdzie się aplikacja.

4.10. Raporty aplikacji internetowej

Są dwa:

  • [e-formulaire]: stan, w którym prezentowane są różne warianty widoku [v-formulaire].
  • [e-erreurs]: stan, w którym wyświetlany jest widok [v-erreurs].

Działania dozwolone w tych stanach są następujące:

<?php

  // konfiguracja stanów aplikacji
  $dConfig['etats']['formulaire']=array(
       'actionsautorisees'=>array('post:calculerimpot','get:init','post:effacerformulaire'),
    'vue'=>'e-formulaire.php');
  $dConfig['etats']['erreurs']=array(
      'actionsautorisees'=>array('get:retourformulaire','get:init'),
      'vue'=>'e-erreurs.php');
  $dConfig['etats']['sansetat']=array('actionsautorisees'=>array('get:init'));        

W danym stanie dozwolone działania odpowiadają celom linków lub przycisków [submit] w widoku powiązanym z tym stanem. Ponadto działanie „get:init” jest zawsze dozwolone. Pozwala to użytkownikowi na pobranie URL main.php z listy URL w przeglądarce i ponowne odtworzenie go niezależnie od stanu aplikacji. Jest to swego rodzaju „ręczne” zresetowanie. Stan „bezstanu” występuje wyłącznie podczas uruchamiania aplikacji.

Z każdym stanem aplikacji powiązany jest skrypt odpowiedzialny za wygenerowanie widoku odpowiadającego temu stanowi:

  • stan [e-formulaire]: skrypt e-formulaire.php
  • stan [e-erreurs]: skrypt e-erreurs.php

Raport [e-formulaire] będzie zawierał widok [v-formulaire] wraz z wariantami. Widok [v-formulaire] może bowiem być wyświetlany jako pusty, wstępnie wypełniony lub z kwotą podatku. Czynność, która spowoduje przejście aplikacji do stanu [e-formulaire], określa w zmiennej $dSession['etat']['principal'] główny stan aplikacji. Kontroler wykorzystuje wyłącznie tę informację. W naszej aplikacji akcja, która prowadzi do stanu [e-formulaire], doda do $dSession['etat']['secondaire'] dodatkową informację, dzięki której generator odpowiedzi będzie wiedział, czynależy wygenerować pusty formularz, formularz wstępnie wypełniony, z kwotą podatku lub bez niej. Można było postąpić inaczej, uznając, że mamy tu do czynienia z trzema różnymi stanami, a zatem z trzema generatorami widoku, które należałoby napisać.

4.11. Plik konfiguracyjny aplikacji internetowej config.php

<?php

     // konfiguracja PHP
  ini_set("register_globals","off");
  ini_set("display_errors","off");  
  ini_set("expose_php","off");

  // lista modułów do dołączenia
  $dConfig['includes']=array('c-impots-data.php','c-impots-calcul.php');

  // kontroler aplikacji
  $dConfig['webapp']=array('titre'=>"Calculez votre impôt");

  // konfiguracja widoków aplikacji
  $dConfig['vuesReponse']['modele1']=array('url'=>'m-reponse.php');
  $dConfig['vuesReponse']['modele2']=array('url'=>'m-reponse2.php');  
  $dConfig['vues']['formulaire']=array('url'=>'v-formulaire.php');
  $dConfig['vues']['erreurs']=array('url'=>'v-erreurs.php');
  $dConfig['vues']['formulaire2']=array('url'=>'v-formulaire2.php');
  $dConfig['vues']['erreurs2']=array('url'=>'v-erreurs2.php');
  $dConfig['vues']['bandeau']=array('url'=>'v-bandeau.php');
  $dConfig['vues']['menu']=array('url'=>'v-menu.php');     
  $dConfig['style']['url']='style1.css';  

  // konfiguracja akcji aplikacji
  $dConfig['actions']['get:init']=array('url'=>'a-init.php');  
  $dConfig['actions']['post:calculerimpot']=array('url'=>'a-calculimpot.php');
  $dConfig['actions']['get:retourformulaire']=array('url'=>'a-retourformulaire.php');
  $dConfig['actions']['post:effacerformulaire']=array('url'=>'a-init.php');
  $dConfig['actions']['enchainementinvalide']=array('url'=>'a-enchainementinvalide.php');
  $dConfig['actions']['actionInvalide']=array('url'=>'a-actioninvalide.php');          

  // konfiguracja stanów aplikacji
  $dConfig['etats']['e-formulaire']=array(
       'actionsautorisees'=>array('post:calculerimpot','get:init','post:effacerformulaire'),
    'vue'=>'e-formulaire.php');
  $dConfig['etats']['e-erreurs']=array(
      'actionsautorisees'=>array('get:retourformulaire','get:init'),
      'vue'=>'e-erreurs.php');
  $dConfig['etats']['sansetat']=array('actionsautorisees'=>array('get:init'));

  // konfiguracja szablonu aplikacji
    $dConfig["DSN"]=array(
        "sgbd"=>"mysql",
        "user"=>"seldbimpots",
        "mdp"=>"mdpseldbimpots",
        "host"=>"localhost",
        "database"=>"dbimpots"
    );
?>

4.12. Działania aplikacji internetowej

4.12.1. Ogólne działanie skryptów akcji

  • Skrypt akcji jest wywoływany przez kontroler na podstawie parametru „action”, który otrzymał od klienta.
  • Po wykonaniu skrypt akcji musi wskazać kontrolerowi stan, w jakim ma zostać ustawiona aplikacja. Stan ten należy wskazać w $dSession['etat']['principal'].
  • Skrypt akcji może chcieć umieścić informacje w sesji. Czyni to, umieszczając je w słowniku $dSession, który jest automatycznie zapisywany w sesji przez kontroler pod koniec cyklu żądanie-odpowiedź.
  • Skrypt akcji może zawierać informacje, które należy przekazać do widoków. Ten aspekt jest niezależny od kontrolera. Jest to interfejs między akcjami a widokami, specyficzny dla każdej aplikacji. W omawianym tutaj przykładzie akcje będą dostarczać informacje do generatorów widoków za pośrednictwem słownika o nazwie $dReponse.

4.12.2. Akcja get:init

Jest to akcja, która generuje pusty formularz. Plik konfiguracyjny wskazuje, że zostanie ona przetworzona przez skrypt a-init.php:

  $dConfig['actions']['get:init']=array('url'=>'a-init.php');

Kod skryptu a-init.php wygląda następująco:

<?php
     // wyświetla się formularz wprowadzania danych
  $dSession['etat']=array('principal'=>'e-formulaire', 'secondaire'=>'init');
?>  

Ten skrypt ogranicza się do ustawienia w $dSession['etat']['principal'] stanu, w jakim powinna znajdować się aplikacja, stan [e-formulaire], a w pliku $dSession['etat']['secondaire'] podaje szczegółowe informacje na temat tego stanu. Plik konfiguracyjny wskazuje, że kontroler uruchomi skrypt e-formulaire.php w celu wygenerowania odpowiedzi dla klienta.

<?php

  $dConfig['etats']['e-formulaire']=array(
       'actionsautorisees'=>array('post:calculerimpot','get:init','post:effacerformulaire'),
    'vue'=>'e-formulaire.php');

Skrypt e-formulaire.php wygeneruje widok [v-formulaire] w jego wariantach [init] i c.a.d – pusty formularz.

4.12.3. Akcja post:calculerimpot

Jest to akcja umożliwiająca obliczenie podatku na podstawie danych wprowadzonych w formularzu. Plik konfiguracyjny wskazuje, że akcję tę przetworzy skrypt a-calculimpot.php:

  $dConfig['actions']['post:calculerimpot']=array('url'=>'a-calculimpot.php');

Kod skryptu a-calculimpot.php jest następujący:

<?php
     // wniosek o obliczenie podatku

  // najpierw sprawdzana jest poprawność parametrów
  $sOptMarie=$_POST['optmarie'];
  if($sOptMarie!='oui' && $sOptMarie!='non'){
      $erreurs[]="L'état marital [$sOptMarie] est erroné";
  }
  $sEnfants=trim($_POST['txtenfants']);
  if(! preg_match('/^\d{1,3}$/',$sEnfants)){
      $erreurs[]="Le nombre d'enfants [$sEnfants] est erroné";
  }
  $sSalaire=trim($_POST['txtsalaire']);
  if(! preg_match('/^\d+$/',$sSalaire)){
      $erreurs[]="Le salaire annuel [$sSalaire] est erroné";
  }

  // jeśli występują błędy, proces się kończy
  if(count($erreurs)!=0){
      // przygotowanie strony błędów
    $dReponse['erreurs']=&$erreurs;
    $dSession['etat']=array('principal'=>'e-erreurs','secondaire'=>'saisie');
      return;
  }//if

  // wprowadzone dane są poprawne
  // pobieramy dane niezbędne do obliczenia podatku
  if(! $dSession['limites']){
      // dane nie znajdują się w sesji
    // pobierane są one ze źródła danych
    list($erreurs,$limites,$coeffr,$coeffn)=getData($dConfig['DSN']);
    // jeśli występują błędy, wyświetlana jest strona błędów
    if(count($erreurs)!=0){
        // przygotowanie strony błędów
      $dReponse['erreurs']=&$erreurs;
        $dSession['etat']=array('principal'=>'e-erreurs','secondaire'=>'database');
      return;
    }//if
    // brak błędów – dane są zapisywane w sesji
    $dSession['limites']=&$limites;
    $dSession['coeffr']=&$coeffr;
    $dSession['coeffn']=&$coeffn;
  }//if

  // tutaj mamy dane niezbędne do obliczenia podatku
  // obliczamy podatek
  $dData=array('limites'=>&$dSession['limites'],
      'coeffr'=>&$dSession['coeffr'],
    'coeffn'=>&$dSession['coeffn']);
  $dPerso=array('enfants'=>$sEnfants,'salaire'=>$sSalaire,'marié'=>($sOptMarie=='oui'),'impot'=>0);
  new impots_calcul($dPerso,$dData);

    // przygotowanie strony odpowiedzi
  $dSession['etat']=array('principal'=>'e-formulaire','secondaire'=>'calculimpot');
  $dReponse['impot']=$dPerso['impot'];
  return;

  //-----------------------------------------------------------------------
  function getData($dDSN){
      // połączenie ze źródłem danych zdefiniowanym w słowniku $dDSN
        $oImpots=new impots_data($dDSN);
    if(count($oImpots->aErreurs)!=0) return array($oImpots->aErreurs);
    // pobieranie danych limitów, współczynnika, współczynnika n
        list($limites,$coeffr,$coeffn)=$oImpots->getData();
         // rozłączamy się
        $oImpots->disconnect();
    // zwracanie wyniku
    if(count($oImpots->aErreurs)!=0) return array($oImpots->aErreurs);
        else return array(array(),$limites,$coeffr,$coeffn);
  }//getData

Skrypt wykonuje swoje zadanie: oblicza podatek. Rozszyfrowanie kodu przetwarzania pozostawiamy czytelnikowi. Interesują nas stany, które mogą wystąpić w wyniku tej operacji:

  • wprowadzone dane są nieprawidłowe lub występują problemy z dostępem do danych: aplikacja przechodzi w stan [e-erreurs]. Plik konfiguracyjny wskazuje, że za wygenerowanie widoku odpowiedzi odpowiedzialny będzie skrypt e-erreurs.php:
<?php

  $dConfig['etats']['e-erreurs']=array(
      'actionsautorisees'=>array('get:retourformulaire','get:init'),
      'vue'=>'e-erreurs.php');
  • we wszystkich pozostałych przypadkach aplikacja przechodzi do stanu [e-formulaire] z wariantem calculimpot wskazanym w $dSession['etat']['secondaire']. Plik konfiguracyjny wskazuje, że to skrypt e-formulaire.php wygeneruje widok odpowiedzi. Skrypt ten wykorzysta wartość z pliku $dSession['etat']['secondaire'] do wygenerowania formularza wstępnie wypełnionego wartościami wprowadzonymi przez użytkownika oraz kwotą podatku.

4.12.4. Akcja post:effacerformulaire

Jest ona powiązana w konfiguracji ze skryptem a-init.php, który został już opisany.

  $dConfig['actions']['post:effacerformulaire']=array('url'=>'a-init.php');

4.12.5. Akcja get:powrót_do_formularza

Umożliwia powrót do stanu [e-formulaire] ze stanu [e-erreurs]. Akcję tę obsługuje skrypt a-retourformulaire.php:

  $dConfig['actions']['get:retourformulaire']=array('url'=>'a-retourformulaire.php');

Skrypt a-retourformulaire.php ma następującą treść:

<?php
     // wyświetlanie formularza wprowadzania danych
  $dSession['etat']=array('principal'=>'e-formulaire','secondaire'=>'retourformulaire');
?>    

Wystarczy po prostu ustawić aplikację w stanie [e-formulaire] w jej wariancie [retourformulaire]. Plik konfiguracyjny wskazuje, że kontroler uruchomi skrypt e-formulaire.php w celu wygenerowania odpowiedzi dla klienta.

<?php

  $dConfig['etats']['e-formulaire']=array(
       'actionsautorisees'=>array('post:calculerimpot','get:init','post:effacerformulaire'),
    'vue'=>'e-formulaire.php');

Skrypt e-formulaire.php wygeneruje widok [v-formulaire] w jego wariantach [retourformulaire] i c.a.d. formularz wstępnie wypełniony wartościami wprowadzonymi przez użytkownika, ale bez kwoty podatku.

4.13. Nieprawidłowa sekwencja działań

Dopuszczalne działania w danym stanie aplikacji są określone w konfiguracji:

<?php

  // konfiguracja stanów aplikacji
  $dConfig['etats']['e-formulaire']=array(
       'actionsautorisees'=>array('post:calculerimpot','get:init','post:effacerformulaire'),
    'vue'=>'e-formulaire.php');
  $dConfig['etats']['e-erreurs']=array(
      'actionsautorisees'=>array('get:retourformulaire','get:init'),
      'vue'=>'e-erreurs.php');
  $dConfig['etats']['sansetat']=array('actionsautorisees'=>array('get:init'));

Wyjaśniliśmy już tę konfigurację. W przypadku wykrycia nieprawidłowej sekwencji działań uruchamiany jest skrypt a-enchainementinvalide.php:

  $dConfig['actions']['enchainementinvalide']=array('url'=>'a-enchainementinvalide.php');

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

<?php 
     // nieprawidłowa sekwencja działań
  $dReponse['erreurs']=array("Enchaînement d'actions invalide");
  $dSession['etat']=array('principal'=>'e-erreurs','secondaire'=>'enchainementinvalide');  
?>

Polega to na ustawieniu aplikacji w stanie [e-erreurs]. W stanach $dSession, ['etat'] i ['secondaire'] podajemy informacje, które zostaną wykorzystane przez generator strony błędów. Jak już widzieliśmy, generatorem tym jest e-erreurs.php:

<?php

  $dConfig['etats']['e-erreurs']=array(
      'actionsautorisees'=>array('get:retourformulaire','get:init'),
      'vue'=>'e-erreurs.php');

Kod tego generatora omówimy później. Widok wysyłany do klienta wygląda następująco:

Image

4.14. Widoki aplikacji

4.14.1. Wyświetlanie widoku końcowego

Przyjrzyjmy się, w jaki sposób kontroler wysyła odpowiedź do klienta po wykonaniu akcji, o którą ten poprosił:

<?php

....
  // rozpoczęcie lub wznowienie sesji
  session_start();
  $dSession=$_SESSION["session"];
  if($dSession) $dSession=unserialize($dSession);

  // pobieranie akcji do wykonania
  $sAction=$_GET['action'] ? strtolower($_GET['action']) : 'init';
  $sAction=strtolower($_SERVER['REQUEST_METHOD']).":$sAction";

     // czy sekwencja działań jest prawidłowa?
  if( ! enchainementOK($dConfig,$dSession,$sAction)){  
    // nieprawidłowa sekwencja
    $sAction='enchainementinvalide';
  }//if

     // przetwarzanie akcji
  $scriptAction=$dConfig['actions'][$sAction] ? 
    $dConfig['actions'][$sAction]['url'] : 
    $dConfig['actions']['actionInvalide']['url'];
  include $scriptAction;

  // wysłanie odpowiedzi (widoku) do klienta
  $sEtat=$dSession['etat']['principal'];
  $scriptVue=$dConfig['etats'][$sEtat]['vue'];
  include $scriptVue;

.....

  // ---------------------------------------------------------------
  function finSession(&$dConfig,&$dReponse,&$dSession){
    // $dConfig: słownik konfiguracyjny
      // $dSession: słownik zawierający informacje o sesji
         // $dReponse: słownik argumentów strony odpowiedzi

    // zapis sesji
...

         // wysłanie odpowiedzi do klienta
        include $dConfig['vuesReponse'][$dReponse['vuereponse']]['url'];

    // koniec skryptu
    exit(0);
  }//zakończenie sesji      

Po powrocie ze skryptu akcji kontroler pobiera z plików $dSession, ['etat'] oraz ['principal'] stan, w jaki ma przełączyć aplikację. Stan ten został ustalony przez właśnie wykonaną akcję. Następnie kontroler uruchamia generator widoku powiązanego z tym stanem. Nazwę tego widoku odnajduje w pliku konfiguracyjnym. Rola generatora widoku polega na:

  • ustala w pliku $dReponse['vuereponse'] nazwę szablonu odpowiedzi, który ma zostać użyty. Informacja ta zostanie przekazana do kontrolera. Szablon jest kompozycją podstawowych widoków, które po połączeniu tworzą widok końcowy.
  • przygotowuje dynamiczne informacje, które mają zostać wyświetlone w widoku końcowym. Ten etap jest niezależny od kontrolera. Stanowi on interfejs między generatorem widoków a widokiem końcowym. Jest on specyficzny dla każdej aplikacji.
  • musi kończyć się wywołaniem funkcji finSession kontrolera. Funkcja ta
    • zapisze sesję
    • wysłać odpowiedź

Kod funkcji finSession jest następujący:

<?php

  // ---------------------------------------------------------------
  function finSession(&$dConfig,&$dReponse,&$dSession){
    // $dConfig: słownik konfiguracyjny
      // $dSession: słownik zawierający informacje o sesji
         // $dReponse: słownik argumentów strony odpowiedzi

    // zapis sesji
...

         // wysłanie odpowiedzi do klienta
        include $dConfig['vuesReponse'][$dReponse['vuereponse']]['url'];

    // koniec skryptu
    exit(0);
  }//zakończenie sesji      

Widok wysyłany do użytkownika jest definiowany przez encję $dReponse['vuereponse'], która określa szablon, jaki ma zostać użyty w ostatecznej odpowiedzi.

4.14.2. Szablon odpowiedzi

Aplikacja wygeneruje różne odpowiedzi zgodnie z następującym, jednolitym szablonem:

Szablon ten jest powiązany z kluczem modele1 w słowniku $dConfig['vuesreponse']:

  $dConfig['vuesReponse']['modele1']=array('url'=>'m-reponse.php');

Skrypt m-reponse.php odpowiada za wygenerowanie tego szablonu:

<html>
    <head>
      <title>Application impots</title>
      <link type="text/css" href="<?php echo $dReponse['urlstyle'] ?>" rel="stylesheet" />
  </head>
  <body>
    <?php
            include $dReponse['vue1'];
        ?>
    <hr>
    <?php
            include $dReponse['vue2'];
        ?>
    </body>
</html>

Ten skrypt zawiera trzy elementy dynamiczne umieszczone w słowniku $dReponse i powiązane z następującymi kluczami:

  • urlstyle: adres URL arkusza stylów szablonu
  • vue1: nazwa skryptu odpowiedzialnego za wygenerowanie widoku vue1
  • vue2: nazwa skryptu odpowiedzialnego za wygenerowanie widoku vue2

Generator widoku, który chce korzystać z modelu modèle1, musi zdefiniować te trzy elementy dynamiczne. Teraz zdefiniujemy widoki elementarne, które mogą zastąpić elementy [vue1] i [vue2] w modelu.

4.14.3. Widok podstawowy v-bandeau.php

Skrypt v-bandeau.php generuje widok, który zostanie umieszczony w obszarze [vue1]:

<table>
    <tr>
      <td><img src="univ01.gif"></td>
    <td>
      <table>
        <tr>
          <td><div class='titre'><?php echo $dReponse['titre'] ?></div></td>
        </tr>
        <tr>
            <td><div class='resultat'><?php echo $dReponse['resultat']?></div></td>
        </tr>
      </table>
    </td>
  </tr>
</table>    

Generator widoku powinien zdefiniować dwa elementy dynamiczne umieszczone w słowniku $dReponse i powiązane z następującymi kluczami:

  • tytuł: tytuł do wyświetlenia
  • wynik: kwota podatku do zapłaty

4.14.4. Widok podstawowy v-formulaire.php

Część [vue2] odpowiada albo widokowi [v-formulaire], albo widokowi [v-erreurs]. Widok [v-formulaire] jest generowany przez skrypt v-formulaire.php:

<form method="post" action="main.php?action=calculerimpot">
    <table>
      <tr>
        <td class="libelle">Etes-vous marié(e)</td>
      <td class="valeur">
          <input type="radio" name="optmarie" <?php echo $dReponse['optoui'] ?> value="oui">oui
          <input type="radio" name="optmarie" <?php echo $dReponse['optnon'] ?> value="non">non        
      <td>
    <tr>
        <td class="libelle">Nombre d'enfants</td>
      <td class="valeur">
          <input type="text" class="text" name="txtenfants" size="3" value="<?php echo $dReponse['enfants'] ?>"        
      </td>
    </tr>
    <tr>
        <td class="libelle">Salaire annuel</td>
      <td class="valeur">
          <input type="text" class="text" name="txtsalaire" size="10" value="<?php echo $dReponse['salaire'] ?>"        
      </td>
    </tr>
    </table>
  <hr>
  <input type="submit" class="submit" value="Calculer l'impôt">  
</form>
<form method="post" action="main.php?action=effacerformulaire">
  <input type="submit" class="submit" value="Effacer le formulaire">
</form>                                            

Dynamiczne elementy tego widoku, które muszą zostać zdefiniowane przez generator widoku, są powiązane z następującymi kluczami słownika $dReponse:

  • optoui: stan przycisku opcji o nazwie optoui
  • optnon: stan przycisku opcji o nazwie optnon
  • dzieci: liczba dzieci, którą należy wpisać w polu txtenfants
  • wynagrodzenie: roczne wynagrodzenie, które należy umieścić w polu txtsalaire

4.14.5. Widok podstawowy v-erreurs.php

Widok [v-erreurs] jest generowany przez skrypt v-erreurs.php:

Les erreurs suivantes se sont produites :
<ul>
    <?php
        for($i=0;$i<count($dReponse["erreurs"]);$i++){
            echo "<li class='erreur'>".$dReponse["erreurs"][$i]."</li>\n";
        }//for
    ?>
</ul>
<div class="info"><?php echo $dReponse["info"] ?></div>
<br>
<a href="<?php echo $dReponse["href"] ?>"><?php echo $dReponse["lien"] ?></a>

Dynamiczne elementy tego widoku, które należy zdefiniować za pomocą generatora widoków, są powiązane z następującymi kluczami słownika $dReponse:

  • błędy: tablica komunikatów o błędach
  • info: komunikat informacyjny
  • link: tekst linku
  • href: adres URL docelowy powyższego linku

4.14.6. Arkusz stylów

Wszystkie widoki są „formatowane” za pomocą arkusza stylów. Aby zmienić wygląd aplikacji, należy zmodyfikować jej arkusz stylów. Poniższy arkusz stylów style1.css:

div.menu {
    background-color: #FFD700;
    color: #F08080;
    font-weight: bolder;
    text-align: center;
}
td.separateur {
    background: #FFDAB9;
    width: 20px;
}

table.modele2 {
    width: 600px;
}

BODY {
    background-image : url(standard.jpg);  
    margin-left : 0px;
    margin-top : 6px;
    color : #4A1919;
    font-size: 10pt;
    font-family: Arial, Helvetica, sans-serif;
    scrollbar-face-color:#F2BE7A;
    scrollbar-arrow-color:#4A1919;
    scrollbar-track-color:#FFF1CC;
    scrollbar-3dlight-color:#CBB673;
    scrollbar-darkshadow-color:#CBB673;
}

div.titre {
    font: 30pt Garamond;
    color: #FF8C00;
    background-color: Yellow;
}

table.menu {
    background-color: #ADD8E6;
}


A:HOVER {
    text-decoration: underline;
    color: #FF0000;
    background-color : transparent;
}
A:ACTIVE {
    text-decoration: underline;
    color : #BF4141;
    background-color : transparent;
}
A:VISITED {
    color : #BF4141;
    background-color : transparent;
}

.error {
    color : red;
    font-weight : bold;
}

INPUT.text {
    margin-left : 3px;
    font-size:8pt;
    font-weight:bold;
    color:#4A1919;
    background-color:#FFF6E0;
    border-right:1px solid;
    border-left:1px solid; 
    border-top:1px solid;
    border-bottom:1px solid;
}
td.libelle {
    background-color: #F0FFFF;
    color: #0000CD;
}

td.valeur {
    background-color: #DDA0DD;
}

DIV.resultat {
    background-color : #FFA07A;
    font : bold 12pt;
}

div.info {
    color: #FA8072;
}

li.erreur {
    color: #DC143C;
}

INPUT.submit {
    margin-left : 6px;
    font-size:8pt;
    font-weight:bold;
    color:#4A1919;
    background-color:#FFF1CC;
    border-right:1px solid;
    border-left:1px solid; 
    border-top:1px solid;
    border-bottom:1px solid;
}

4.15. Generatory widoków

4.15.1. Rola generatora widoku

Przypomnijmy sekwencję kodu kontrolera, która uruchamia generator widoku:

<?php

     // przetwarzanie akcji
  $scriptAction=$dConfig['actions'][$sAction] ? 
    $dConfig['actions'][$sAction]['url'] : 
    $dConfig['actions']['actionInvalide']['url'];
  include $scriptAction;

  // wysyłanie odpowiedzi (widoku) do klienta
  $sEtat=$dSession['etat']['principal'];
  $scriptVue=$dConfig['etats'][$sEtat]['vue'];
  include $scriptVue;

Generator widoku jest powiązany ze stanem, w którym zostanie umieszczona aplikacja. Powiązanie między stanem a generatorem widoku ustala się poprzez konfigurację:

<?php

  // konfiguracja stanów aplikacji
  $dConfig['etats']['e-formulaire']=array(
       'actionsautorisees'=>array('post:calculerimpot','get:init','post:effacerformulaire'),
    'vue'=>'e-formulaire.php');
  $dConfig['etats']['e-erreurs']=array(
      'actionsautorisees'=>array('get:retourformulaire','get:init'),
      'vue'=>'e-erreurs.php');
  $dConfig['etats']['sansetat']=array('actionsautorisees'=>array('get:init'));

Wspomnieliśmy już, jaką rolę pełni generator widoku. Przypomnijmy to tutaj. Generator widoku:

  • określa w pliku $dReponse['vuereponse'] nazwę szablonu odpowiedzi, który ma zostać użyty. Informacja ta zostanie przekazana do kontrolera. Szablon to zbiór podstawowych widoków, które po połączeniu tworzą ostateczny widok.
  • przygotowuje dynamiczne informacje, które mają zostać wyświetlone w widoku końcowym. Ten element jest niezależny od kontrolera. Stanowi on interfejs między generatorem widoków a widokiem końcowym. Jest on specyficzny dla każdej aplikacji.
  • musi kończyć się wywołaniem funkcji finSession kontrolera. Funkcja ta
    • zapisze sesję
    • wysłać odpowiedź

4.15.2. Generator widoku powiązany ze stanem [e-formulaire]

Skrypt odpowiedzialny za generowanie widoku powiązanego z raportem [e-formulaire] nosi nazwę e-formulaire.php:

<?php

  $dConfig['etats']['e-formulaire']=array(
       'actionsautorisees'=>array('post:calculerimpot','get:init','post:effacerformulaire'),
    'vue'=>'e-formulaire.php');

Jego kod wygląda następująco:

<?php
  // przygotowywanie odpowiedzi w formie formularza
  $dReponse['titre']=$dConfig['webapp']['titre'];
    $dReponse['vuereponse']='modele1';
  $dReponse['vue1']=$dConfig['vues']['bandeau']['url'];
  $dReponse['vue2']=$dConfig['vues']['formulaire']['url'];  
  $dReponse['urlstyle']=$dConfig['style']['url'];
  $dReponse['titre']=$dConfig['webapp']['titre'];

  // konfiguracja w zależności od typu generowanego formularza
    $type=$dSession['etat']['secondaire'];
  if($type=='init'){
      // pusty formularz
    $dReponse['optnon']='checked';
  }//if
  if($type=='calculimpot'){
      // należy ponownie wyświetlić parametry wprowadzania danych zapisane w zapytaniu
    $dReponse['optoui']=$_POST['optmarie']=='oui' ? 'checked' : '';
    $dReponse['optnon']=$dReponse['optoui'] ? '' : 'checked';
    $dReponse['enfants']=$_POST['txtenfants'];
    $dReponse['salaire']=$_POST['txtsalaire'];
    $dReponse['resultat']='Impôt à payer : '.$dReponse['impot'].' F';    
  }//if
  if($type=='retourformulaire'){
      // należy ponownie wyświetlić parametry wprowadzania danych zapisane w sesji
    $dReponse['optoui']=$dSession['requete']['optmarie']=='oui' ? 'checked' : '';
    $dReponse['optnon']=$dReponse['optoui']=='' ? 'checked' : '';  
    $dReponse['enfants']=$dSession['requete']['txtenfants'];
    $dReponse['salaire']=$dSession['requete']['txtsalaire'];
  }//if
  // wysyłamy odpowiedź
  finSession($dConfig,$dReponse,$dSession);
?>      

Należy zauważyć, że generator widoku spełnia warunki nałożone na generator widoku:

  • określa w pliku $dReponse['vuereponse'] szablon odpowiedzi, który ma być użyty
  • przekazać informacje do tego szablonu. W tym przypadku są one przekazywane za pośrednictwem słownika $dReponse.
  • zakończenie wywołaniem funkcji finSession kontrolera

W tym przypadku używanym szablonem jest „modèle1”. Generator definiuje zatem dwie informacje potrzebne temu szablonowi: $dReponse['vue1'] oraz $dReponse['vue2'].

W konkretnym przypadku naszej aplikacji widok powiązany ze stanem [e-formulaire] zależy od informacji przechowywanej w zmiennej $dSession['etat']['secondaire']. Jest to wybór programistyczny. Inna aplikacja mogłaby zdecydować się na przekazywanie dodatkowych informacji w inny sposób. Ponadto w tym przypadku wszystkie informacje niezbędne do wyświetlenia widoku końcowego są umieszczone w słowniku $dReponse. Również w tym przypadku jest to wybór należący do programisty. Raport [e-formulaire] może zostać wygenerowany po wykonaniu czterech różnych akcji: init, calculateTax, returnForm, clearForm. Widok, który ma zostać wyświetlony, nie jest dokładnie taki sam we wszystkich przypadkach. Dlatego w $dSession['etat']['secondaire'] wyróżniono trzy przypadki:

  • init: formularz jest wyświetlany jako pusty
  • obliczPodatek: formularz jest wyświetlany z kwotą podatku oraz danymi, na podstawie których został on obliczony
  • retourformulaire: formularz jest wyświetlany z danymi wprowadzonymi początkowo

Powyższy skrypt e-formulaire.php wykorzystuje te informacje do wyświetlenia odpowiedzi zgodnie z tymi trzema wariantami.

4.15.3. Widok powiązany z raportem [e-erreurs]

Skrypt odpowiedzialny za generowanie widoku powiązanego z raportem [e-erreurs] nosi nazwę e-erreurs.php i ma następującą treść:

<?php

  // przygotowujemy odpowiedź na błędy
  $dReponse['titre']=$dConfig['webapp']['titre'];
    $dReponse['vuereponse']='modele1';
  $dReponse['vue1']=$dConfig['vues']['bandeau']['url'];
  $dReponse['vue2']=$dConfig['vues']['erreurs']['url'];  
  $dReponse['urlstyle']=$dConfig['style']['url'];
  $dReponse['titre']=$dConfig['webapp']['titre'];
  $dReponse['lien']='Retour au formulaire de saisie';
  $dReponse['href']='main.php?action=retourformulaire';

  // dodatkowe informacje
  $type=$dSession['etat']['secondaire'];
  if($type=='database'){
      $dReponse['info']="Veuillez avertir l'administrateur de l'application";
  }

  // wysyłamy odpowiedź
  finSession($dConfig,$dReponse,$dSession);
?>

4.15.4. Wyświetlanie widoku końcowego

Oba skrypty generujące dwa widoki końcowe kończą się wywołaniem funkcji kontrolera finSession:

<?php

  function finSession(&$dConfig,&$dReponse,&$dSession){
    // $dConfig: słownik konfiguracyjny
      // $dSession: słownik zawierający informacje o sesji
         // $dReponse: słownik argumentów strony odpowiedzi

....

         // wyświetlanie odpowiedzi
        include $dConfig['vuesReponse'][$dReponse['vuereponse']]['url'];

    // koniec skryptu
    exit(0);
  }//koniec sesji      

Widok wysyłany do użytkownika jest definiowany przez encję $dReponse['vuereponse'], która określa szablon, jaki ma zostać użyty w ostatecznej odpowiedzi. W przypadku raportów [e-formulaire] i [e-erreurs] ten szablon został zdefiniowany jako modele1:

    $dReponse['vuereponse']='modele1';

Szablon ten odpowiada skryptowi m-reponse.php:

  $dConfig['vuesReponse']['modele1']=array('url'=>'m-reponse.php');

4.16. Modyfikacja szablonu odpowiedzi

Zakładamy tutaj, że zdecydowano się zmienić wygląd odpowiedzi wysyłanej do klienta i chcemy zrozumieć, jakie konsekwencje ma to dla kodu.

4.16.1. Nowy szablon

Struktura odpowiedzi będzie teraz wyglądać następująco:

Ten szablon będzie nosił nazwę modele2, a skrypt odpowiedzialny za jego generowanie będzie nosił nazwę m-reponse2.php:

  $dConfig['vuesReponse']['modele2']=array('url'=>'m-reponse2.php');

Skrypt odpowiadający temu szablonowi wygląda następująco:

<html>
    <head>
      <title>Application impots</title>
      <link type="text/css" href="<?php echo $dReponse['urlstyle'] ?>" rel="stylesheet" />
  </head>
  <body>
      <table class='modele2'>
        <!-- początek banera -->
        <tr>
          <td colspan="2"><?php    include $dReponse['vue1']; ?></td>
      </tr>
        <!-- koniec banera -->            
      <tr>
            <!-- początek menu -->      
          <td><?php include $dReponse['vue2']; ?></td>
            <!-- koniec menu -->
            <!-- początek strefy 3 -->                        
          <td><?php include $dReponse['vue3']; ?></td>
            <!-- koniec strefy 3 -->        
      </tr>
   </table>
    </body>
</html>

Dynamiczne elementy szablonu są następujące:

  • $dReponse['urlstyle']: arkusz stylów, którego należy użyć
  • $dReponse['vue1']: skrypt, którego należy użyć do wygenerowania [vue1]
  • $dReponse['vue2']: skrypt, którego należy użyć do wygenerowania pliku [vue2]
  • $dReponse['vue3']: skrypt służący do wygenerowania pliku [vue3]

Elementy te muszą zostać ustalone przez osoby odpowiedzialne za generowanie widoków.

4.16.2. Różne strony odpowiedzi

Aplikacja będzie teraz wyświetlać użytkownikowi następujące odpowiedzi. Przy pierwszym wywołaniu strona odpowiedzi będzie wyglądać następująco:

Jeśli użytkownik poda prawidłowe dane, podatek zostanie obliczony:

Jeśli popełni błędy podczas wprowadzania danych, wyświetli się strona błędów:

Jeśli skorzysta z linku [Retour au formulaire de saisie], powróci do formularza w stanie, w jakim go zatwierdził:

Jeśli w powyższym przypadku skorzysta z linku [Réinitialiser le formulaire], wyświetli się pusty formularz:

Należy zauważyć, że aplikacja wykorzystuje te same akcje, co poprzednio. Zmienił się jedynie wygląd odpowiedzi.

4.16.3. Widoki elementarne

Widok podstawowy [vue1] będzie, podobnie jak w poprzednim przykładzie, powiązany ze skryptem v-bandeau.php:

<table>
    <tr>
      <td><img src="univ01.gif"></td>
    <td>
      <table>
        <tr>
          <td><div class='titre'><?php echo $dReponse['titre'] ?></div></td>
        </tr>
        <tr>
            <td><div class='resultat'><?php echo $dReponse['resultat']?></div></td>
        </tr>
      </table>
    </td>
  </tr>
</table>  

Ten widok zawiera dwa elementy dynamiczne:

  • $dReponse['titre']: tytuł do wyświetlenia
  • $dReponse['resultat']: kwota podatku do zapłaty

Widok elementarny [vue2] zostanie powiązany z następującym skryptem v-menu.php:

<table class="menu">
    <tr>
      <td><div class="menu">Options</div></td>
  </tr>
  <?php
      for($i=0;$i<count($dReponse['liens']);$i++){
        echo '<tr><td><div class="option"><a href="'.
          $dReponse['liens'][$i]['url'].
        '">'.$dReponseQZXW2HTMLBWydsaWVucyddZQXQZXW2HTMLBWyRpXQZQXQZXW2HTMLBWyd0ZXh0ZSddZQX."</a></div></td></tr>\n";
    }//$i
  ?>
</table>

Ten widok zawiera następujące elementy dynamiczne:

  • $dReponse['liens']: tablica linków do wyświetlenia w [vue2]. Każdy element tablicy jest słownikiem zawierającym dwa klucze:
    • „url”: docelowy adres URL linku
    • 'tekst': tekst linku

Widok podstawowy [vue3] zostanie powiązany ze skryptem v-formulaire2.php, jeśli chcemy wyświetlić formularz wprowadzania danych, lub ze skryptem v-erreurs2.php, jeśli chcemy wyświetlić stronę błędów. Kod skryptu v-formulaire2.php jest następujący:

<form method="post" action="main.php?action=calculerimpot">
    <table>
      <tr>
        <td class="libelle">Etes-vous marié(e)</td>
      <td class="valeur">
          <input type="radio" name="optmarie" <?php echo $dReponse['optoui'] ?> value="oui">oui
          <input type="radio" name="optmarie" <?php echo $dReponse['optnon'] ?> value="non">non        
      </td>
    </tr>
    <tr>
        <td class="libelle">Nombre d'enfants</td>
      <td class="valeur">
          <input type="text" class="text" name="txtenfants" size="3" value="<?php echo $dReponse['enfants'] ?>"        
      </td>
    </tr>
    <tr>
        <td class="libelle">Salaire annuel</td>
      <td class="valeur">
          <input type="text" class="text" name="txtsalaire" size="10" value="<?php echo $dReponse['salaire'] ?>"        
      </td>
    </tr>
    <tr>
        <td colspan="2" align="center"><input type="submit" class="submit" value="Calculer l'impôt"></td>
    </tr>
    </table>
</form>

Dynamiczne elementy tego widoku, które muszą zostać zdefiniowane przez generator widoku, są powiązane z następującymi kluczami słownika $dReponse:

  • optoui: stan przycisku opcji o nazwie optoui
  • optnon: stan przycisku opcji o nazwie optnon
  • dzieci: liczba dzieci, którą należy umieścić w polu txtenfants
  • salaire: roczne wynagrodzenie, które ma zostać umieszczone w polu txtsalaire

Skrypt generujący stronę błędów nosi nazwę v-erreurs2.php. Jego kod jest następujący:

Les erreurs suivantes se sont produites :
<ul>
    <?php
        for($i=0;$i<count($dReponse["erreurs"]);$i++){
            echo "<li class='erreur'>".$dReponse["erreurs"][$i]."</li>\n";
        }//dla
    ?>
</ul>
<div class="info"><?php echo $dReponse["info"] ?></div>

Dynamiczne elementy tego widoku, które należy zdefiniować za pomocą generatora widoków, są powiązane z następującymi kluczami słownika $dReponse:

  • błędy: tablica komunikatów o błędach
  • info: komunikat informacyjny

4.16.4. Arkusz stylów

Nie uległ zmianie. Nadal jest to style1.css.

4.16.5. Nowy plik konfiguracyjny

Aby wprowadzić te nowe widoki, musimy zmodyfikować niektóre wiersze w pliku konfiguracyjnym.

<?php

     // konfigurację PHP
  ini_set("register_globals","off");
  ini_set("display_errors","off");  
  ini_set("expose_php","off");

  // lista modułów do dołączenia
  $dConfig['includes']=array('c-impots-data.php','c-impots-calcul.php');

  // kontroler aplikacji
  $dConfig['webapp']=array('titre'=>"Calculez votre impôt");

  // konfiguracja widoków aplikacji
  $dConfig['vuesReponse']['modele1']=array('url'=>'m-reponse.php');
  $dConfig['vuesReponse']['modele2']=array('url'=>'m-reponse2.php');  
  $dConfig['vues']['formulaire']=array('url'=>'v-formulaire.php');
  $dConfig['vues']['erreurs']=array('url'=>'v-erreurs.php');
  $dConfig['vues']['formulaire2']=array('url'=>'v-formulaire2.php');
  $dConfig['vues']['erreurs2']=array('url'=>'v-erreurs2.php');
  $dConfig['vues']['bandeau']=array('url'=>'v-bandeau.php');
  $dConfig['vues']['menu']=array('url'=>'v-menu.php');     
  $dConfig['style']['url']='style1.css';  

  // konfiguracja akcji aplikacji
  $dConfig['actions']['get:init']=array('url'=>'a-init.php');  
  $dConfig['actions']['post:calculerimpot']=array('url'=>'a-calculimpot.php');
  $dConfig['actions']['get:retourformulaire']=array('url'=>'a-retourformulaire.php');
  $dConfig['actions']['post:effacerformulaire']=array('url'=>'a-init.php');
  $dConfig['actions']['enchainementinvalide']=array('url'=>'a-enchainementinvalide.php');
  $dConfig['actions']['actionInvalide']=array('url'=>'a-actioninvalide.php');          

  // konfiguracja stanów aplikacji
  $dConfig['etats']['e-formulaire']=array(
       'actionsautorisees'=>array('post:calculerimpot','get:init','post:effacerformulaire'),
    'vue'=>'e-formulaire2.php');
  $dConfig['etats']['e-erreurs']=array(
      'actionsautorisees'=>array('get:retourformulaire','get:init'),
      'vue'=>'e-erreurs2.php');
  $dConfig['etats']['sansetat']=array('actionsautorisees'=>array('get:init'));

  // konfiguracja modelu aplikacji
    $dConfig["DSN"]=array(
        "sgbd"=>"mysql",
        "user"=>"seldbimpots",
        "mdp"=>"mdpseldbimpots",
        "host"=>"localhost",
        "database"=>"dbimpots"
    );
?>

Najważniejsza zmiana polega na zastąpieniu generatorów widoków powiązanych z raportami [e-formulaire] i [e-erreurs]. Po dokonaniu tej zmiany nowe generatory widoków będą odpowiedzialne za generowanie nowych stron odpowiedzi.

4.16.6. Generator widoku powiązany z raportem [e-formulaire]

W pliku konfiguracyjnym raport [e-formulaire] jest teraz powiązany z generatorem widoku e-formulaire2.php. Kod tego skryptu jest następujący:

<?php
  // przygotowywanie odpowiedzi formularza
  $dReponse['titre']=$dConfig['webapp']['titre'];
    $dReponse['vuereponse']='modele2';
  $dReponse['vue1']=$dConfig['vues']['bandeau']['url'];
  $dReponse['vue2']=$dConfig['vues']['menu']['url'];
  $dReponse['vue3']=$dConfig['vues']['formulaire2']['url'];
  $dReponse['urlstyle']=$dConfig['style']['url'];
  $dReponse['titre']=$dConfig['webapp']['titre'];
  $dReponse['liens']=array(
      array('texte'=>'Réinitialiser le formulaire', 'url'=>'main.php?action=init')
  );              

  // konfiguracja w zależności od typu generowanego formularza
    $type=$dSession['etat']['secondaire'];
  if($type=='init'){
      // pusty formularz
    $dReponse['optnon']='checked';
  }//if
  if($type=='calculimpot'){
      // należy ponownie wyświetlić parametry wprowadzania danych zapisane w zapytaniu
    $dReponse['optoui']=$_POST['optmarie']=='oui' ? 'checked' : '';
    $dReponse['optnon']=$dReponse['optoui'] ? '' : 'checked';
    $dReponse['enfants']=$_POST['txtenfants'];
    $dReponse['salaire']=$_POST['txtsalaire'];
    $dReponse['resultat']='Impôt à payer : '.$dReponse['impot'].' F';
  }//if
  if($type=='retourformulaire'){
      // należy ponownie wyświetlić parametry wprowadzania danych zapisane w sesji
    $dReponse['optoui']=$dSession['requete']['optmarie']=='oui' ? 'checked' : '';
    $dReponse['optnon']=$dReponse['optoui']=='' ? 'checked' : '';  
    $dReponse['enfants']=$dSession['requete']['txtenfants'];
    $dReponse['salaire']=$dSession['requete']['txtsalaire'];
  }//if
  // wysyłamy odpowiedź
  finSession($dConfig,$dReponse,$dSession);
?>  

Główne zmiany są następujące:

  • generator widoku wskazuje, że chce użyć szablonu odpowiedzi modele2
  • z tego powodu wypełnia elementy dynamiczne $dReponse['vue1'], $dReponse['vue2'], $dReponse['vue3'] – wszystkie trzy niezbędne dla szablonu odpowiedzi modele2.
  • generator wypełnia również element dynamiczny $dReponse['liens'], który określa linki do wyświetlenia w obszarze [vue2] odpowiedzi.

4.16.7. Generator widoku powiązany z raportem [e-erreurs]

W pliku konfiguracyjnym raport [e-erreurs] jest teraz powiązany z generatorem widoku e-erreurs2.php. Kod tego skryptu jest następujący:

<?php

  // przygotowujemy odpowiedź na błędy
  $dReponse['titre']=$dConfig['webapp']['titre'];
    $dReponse['vuereponse']='modele2';
  $dReponse['vue1']=$dConfig['vues']['bandeau']['url'];
  $dReponse['vue2']=$dConfig['vues']['menu']['url'];  
  $dReponse['vue3']=$dConfig['vues']['erreurs2']['url'];  
  $dReponse['urlstyle']=$dConfig['style']['url'];
  $dReponse['titre']=$dConfig['webapp']['titre'];
  $dReponse['liens']=array(
      array('texte'=>'Retour au formulaire de saisie', 'url'=>'main.php?action=retourformulaire')
  );              

  // dodatkowe informacje
  $type=$dSession['etat']['secondaire'];
  if($type=='database'){
      $dReponse['info']="Veuillez avertir l'administrateur de l'application";
  }

  // wysyłamy odpowiedź
  finSession($dConfig,$dReponse,$dSession);
?>  

Wprowadzone zmiany są identyczne z tymi wprowadzonymi w generatorze widoku e-formulaire2.php.

4.17. Wnioski

Na przykładzie udało nam się wykazać zalety naszego generycznego kontrolera. Nie musieliśmy go tworzyć od podstaw. Ogranicziliśmy się do napisania skryptów akcji, generatorów widoków oraz samych widoków aplikacji. Ponadto pokazaliśmy korzyści płynące z oddzielenia akcji od widoków. Dzięki temu mogliśmy zmienić wygląd odpowiedzi bez modyfikowania ani jednej linii kodu w skryptach akcji. Zmodyfikowano jedynie skrypty związane z generowaniem widoków. Aby było to możliwe, skrypt akcji nie może opierać się na żadnych założeniach dotyczących widoku, który wyświetli obliczone przez niego informacje. Musi on jedynie przekazać te informacje do kontrolera, który z kolei przekazuje je do generatora widoku, który nadaje im odpowiednią formę. Jest to zasada bezwzględna: akcja musi być całkowicie oddzielona od widoków.

W niniejszym rozdziale zapoznaliśmy się z filozofią Struts, dobrze znaną programistom Java. Projekt „open source” o nazwie php.mvc umożliwia tworzenie aplikacji internetowych w języku PHP zgodnie z filozofią Struts. Więcej informacji można znaleźć na stronie http://www.phpmvc.net/.