4. Przykłady
4.1. Aplikacja „Podatki”: Wprowadzenie
W tym miejscu przedstawiamy aplikację IMPOTS, z której będziemy wielokrotnie korzystać w dalszej części. Jest to aplikacja służąca do obliczania podatku podatnika. Rozważamy uproszczony przypadek podatnika, który ma do zadeklarowania wyłącznie swoje wynagrodzenie:
- liczba udziałów pracownika wynosi nbParts = nbEnfants/2 + 1, jeśli jest on osobą stanu wolnego, nbEnfants/2+2, jeśli jest żonaty, gdzie nbEnfants to liczba jego dzieci.
- jeśli ma co najmniej troje dzieci, przysługuje mu dodatkowa połowa udziału
- oblicza się jego dochód podlegający opodatkowaniu R = 0,72 * S, gdzie S to jego roczne wynagrodzenie
- oblicza się jego współczynnik rodzinny QF = R / nbParts
- oblicza się jego podatek I. Rozważmy następującą tabelę:
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 | 49062 |
Każdy wiersz zawiera 3 pola. Aby obliczyć podatek I, należy znaleźć pierwszy wiersz, w którym QF <= pole1. Na przykład, jeśli QF = 23000, znajdziemy wiersz
Podatek I wynosi wówczas 0,15*R – 2072,5*nbParts. Jeśli QF jest takie, że relacja QF <= pole1 nigdy nie jest spełniona, wówczas stosuje się współczynniki z ostatniego wiersza. W tym przypadku:
co daje podatek I = 0,65*R – 49062*nbParts.
Dane określające poszczególne progi podatkowe znajdują się w bazie danych ODBC-MySQL. MySQL to wersja SGBD należąca do domeny publicznej, którą można wykorzystywać na różnych platformach, w tym Windows i Linux. Za pomocą tego pliku SGBD utworzono bazę danych o nazwie dbimpots, zawierającą jedną tabelę o nazwie impots. Dostęp do bazy jest kontrolowany za pomocą loginu i hasła, w tym przypadku admimpots/mdpimpots. Poniższy zrzut ekranu pokazuje, jak korzystać z bazy dbimpots przy użyciu pliku MySQL:
C:\Program Files\EasyPHP\mysql\bin>mysql -u admimpots -p
Enter password: *********
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 18 to server version: 3.23.49-max-nt
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql> use dbimpots;
Database changed
mysql> show tables;
+--------------------+
| Tables_in_dbimpots |
+--------------------+
| impots |
+--------------------+
1 row in set (0.00 sec)
mysql> describe impots;
+---------+--------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------+------+-----+---------+-------+
| limites | double | YES | | NULL | |
| coeffR | double | YES | | NULL | |
| coeffN | double | YES | | NULL | |
+---------+--------+------+-----+---------+-------+
3 rows in set (0.02 sec)
mysql> select * from impots;
+---------+--------+---------+
| limites | coeffR | coeffN |
+---------+--------+---------+
| 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 |
+---------+--------+---------+
14 rows in set (0.00 sec)
mysql>quit
Baza danych dbimpots jest przekształcana w źródło danych ODBC w następujący sposób:
- uruchamia się 32-bitowy menedżer źródeł danych ODBC

- używa się przycisku [Add], aby dodać nowe źródło danych ODBC

- wybieramy sterownik MySQL i wykonujemy [Terminer]
![]() |
- sterownik MySQL wymaga podania pewnych informacji:
1 | nazwa DSN, jaką należy nadać źródłu danych ODBC – może być dowolna |
2 | komputer, na którym działa SGBD MySQL – w tym przypadku localhost. Warto zauważyć, że bazą danych może być baza zdalna. Lokalne aplikacje korzystające ze źródła danych ODBC nie zauważyłyby tego. Dotyczyłoby to w szczególności naszej aplikacji PHP. |
3 | baza danych MySQL, z której należy korzystać. MySQL to SGBD, który zarządza relacyjnymi bazami danych, czyli zbiorami tabel połączonych ze sobą relacjami. W tym miejscu podaje się nazwę zarządzanej bazy danych. |
4 | nazwa użytkownika posiadającego uprawnienia dostępu do tej bazy |
5 | jego hasło |
4.2. Aplikacja podatkowa: klasa ImpotsDSN
Nasza aplikacja będzie oparta na klasie PHP ImpotsDSN, która będzie posiadała następujące atrybuty, konstruktor i metodę:
<?php
...
// atrybuty
var $limites; // tabela limitów
var $coeffR; // tabela coeffR
var $coeffN; // tabela coeffN
var $erreurs; // tabela błędów
- W opisie problemu widzieliśmy, że potrzebujemy trzech zestawów danych
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 | 49062 |
Pierwsza kolumna zostanie umieszczona w atrybucie $limites klasy ImotsDSN, druga w atrybucie $coeffR, a trzecia w $coeffN. Te trzy atrybuty są inicjowane przez konstruktor klasy. Konstruktor ten pobiera dane ze źródła danych ODBC. Podczas tworzenia obiektu mogą wystąpić różne błędy. Błędy te są zgłaszane w atrybucie $erreurs w postaci tablicy komunikatów o błędach.
- Konstruktor otrzymuje jako parametr słownik $impots zawierający następujące pola:
// dsn: nazwa DSN źródła danych ODBC zawierającego wartości tabel limitów, coeffR, coeffN
// user: nazwa użytkownika posiadającego uprawnienia do odczytu źródła ODBC
// pwd: jego hasło
// tabela: nazwa tabeli zawierającej wartości (wartości graniczne, coeffR, coeffN)
// limity: nazwa kolumny zawierającej wartości graniczne
// coeffR: nazwa kolumny zawierającej wartości coeffR
// coeffN: nazwa kolumny zawierającej wartości coeffN
Parametr konstruktora dostarcza wszystkich informacji niezbędnych do odczytania danych ze źródła danych ODBC
- Po utworzeniu obiektu ImpotsDSN użytkownicy klasy będą mogli wywołać metodę calculerImpots tego obiektu. Metoda ta przyjmuje jako parametr słownik $personne:
// $osoba["marié"]: tak, nie
// $osoba["enfants"]: liczba dzieci
// $osoba["salaire"]: roczne wynagrodzenie
Pełna klasa wygląda następująco:
<?php
// definicja klasy objImpots
class ImpotsDSN{
// atrybuty: 3 tabele danych
var $limites; // tabela limitów
var $coeffR; // tabela coeffR
var $coeffN; // tabela coeffN
var $erreurs; // tabela błędów
// producent
function ImpotsDSN($impots){
// $impots: słownik zawierający następujące pola
// dsn: nazwa DSN źródła danych ODBC zawierającego wartości tabel limitów, coeffR, coeffN
// user: nazwa użytkownika posiadającego uprawnienia do odczytu źródła ODBC
// pwd: jego hasło
// tabela: nazwa tabeli zawierającej wartości (wartości graniczne, coeffR, coeffN)
// limity: nazwa kolumny zawierającej wartości graniczne
// coeffR: nazwa kolumny zawierającej wartości coeffR
// coeffN: nazwa kolumny zawierającej wartości coeffN
// początkowo brak błędów
$this->erreurs=array();
// weryfikacja wywołania
if (! isset($impots[dsn]) || ! isset($impots[user]) ||! isset($impots[pwd]) ||! isset($impots[table]) ||
! isset($impots[limites]) ||! isset($impots[coeffR]) ||! isset($impots[coeffN])){
// błąd
$this->erreurs[]="Appel incorrect";
// koniec
return;
}//if
// otwarcie bazy danych DSN
$connexion=odbc_connect($impots[dsn],$impots[user],$impots[pwd]);
// błąd?
if(! $connexion){
// błąd
$this->erreurs[]="Impossible d'ouvrir la base DSN [$impots[dsn]] (".odbc_error().")";
// koniec
return;
}//if
// wysłanie zapytania do bazy danych
$requête=odbc_prepare($connexion,"select $impots[limites],$impots[coeffR],$impots[coeffN] from $impots[table]");
if(! odbc_execute($requête)){
// błąd
$this->erreurs[]="Impossible d'expoiter la base DSN [$impots[dsn]] (".odbc_error().")";
// koniec
odbc_close($connexion);
return;
}//if
// przetwarzanie wyników zapytania
$this->limites=array();
$this->coeffR=array();
$this->coeffN=array();
while(odbc_fetch_row($requête)){
// kolejny wiersz
$this->limites[]=odbc_result($requête,$impots[limites]);
$this->coeffR[]=odbc_result($requête,$impots[coeffR]);
$this->coeffN[]=odbc_result($requête,$impots[coeffN]);
}//while
// zamknięcie bazy
odbc_close($connexion);
}//konstruktor
// --------------------------------------------------------------------------
function calculer($personne){
// $osoba["marié"]: tak, nie
// $osoba["enfants"]: liczba dzieci
// $osoba["salaire"]: roczne wynagrodzenie
// czy obiekt jest w prawidłowym stanie?
if (! is_array($this->erreurs) || count($this->erreurs)!=0) return -1;
// weryfikacja wywołania
if (! isset($personne[marié]) || ! isset($personne[enfants]) ||! isset($personne[salaire])){
// błąd
$this->erreurs[]="Appel incorrect";
// koniec
return -1;
}//if
// czy parametry są poprawne?
$personne[marié]=strtolower($personne[marié]);
if($personne[marié]!=oui && $personne[marié]!=non){
// błąd
$this->erreurs[]="Statut marital [$personne[marié]] incorrect";
}//if
if(! preg_match("/^\s*\d{1,3}\s*$/",$personne[enfants])){
// błąd
$this->erreurs[]="Nombre d'enfants [$personne[enfants]] incorrect";
}//if
if(! preg_match("/^\s*\d+\s*$/",$personne[salaire])){
// błąd
$this->erreurs[]="salaire [$personne[salaire]] incorrect";
}//if
// błędy?
if(count($this->erreurs)!=0) return -1;
// liczba porcji
if($personne[marié]==oui) $nbParts=$personne[enfants]/2+2;
else $nbParts=$personne[enfants]/2+1;
// o pół udziału więcej, jeśli jest co najmniej troje dzieci
if($personne[enfants]>=3) $nbParts+=0.5;
// dochód podlegający opodatkowaniu
$revenuImposable=0.72*$personne[salaire];
// współczynnik rodzinny
$quotient=$revenuImposable/$nbParts;
// jest umieszczany na końcu tabeli limitów w celu zatrzymania następującej po niej pętli
$this->limites[$this->nbLimites]=$quotient;
// obliczenie podatku
$i=0;
while($quotient>$this->limites[$i]) $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->coeffR[$i]-$nbParts*$this->coeffN[$i]);
}//calculImpots
}//klasa podatków
?>
Program testowy mógłby wyglądać następująco:
<?php
// biblioteki
include "ImpotsDSN.php";
// tworzenie obiektu podatkowego
$conf=array(dsn=>"mysql-dbimpots",user=>admimpots,pwd=>mdpimpots,
table=>impots,limites=>limites,coeffR=>coeffR,coeffN=>coeffN);
$objImpots=new ImpotsDSN($conf);
// błędy?
if(count($objImpots->erreurs)!=0){
// komunikat
echo "Les erreurs suivantes se sont produites :\n";
$erreurs=$objImpots->erreurs;
for($i=0;$i<count($erreurs);$i++){
echo "$erreurs[$i]\n";
}//for
// koniec
exit(1);
}//if
// próby
calculerImpots($objImpots,oui,2,200000);
calculerImpots($objImpots,non,2,200000);
calculerImpots($objImpots,oui,3,200000);
calculerImpots($objImpots,non,3,200000);
calculerImpots($objImpots,array(),array(),array());
// koniec
exit(0);
// -----------------------------------------------------------
function calculerImpots($objImpots,$marié,$enfants,$salaire){
// echo
echo "impots($marié,$enfants,$salaire)\n";
// obliczenie podatku
$personne=array(marié=>$marié,enfants=>$enfants,salaire=>$salaire);
$montant=$objImpots->calculer($personne);
// błędy?
if(count($objImpots->erreurs)!=0){
// komunikat
echo "Les erreurs suivantes se sont produites :\n";
$erreurs=$objImpots->erreurs;
for($i=0;$i<count($erreurs);$i++){
echo "$erreurs[$i]\n";
}//for
}else echo "montant=$montant\n";
}//calculerImp
- program testowy „dołącza” plik zawierający klasę ImpotsDSN
- następnie tworzy obiekt objImpots klasy ImpotsDSN, przekazując konstruktorowi obiektu niezbędne informacje
- po utworzeniu tego obiektu jego metoda calculer zostanie wywołana pięć razy
Otrzymane wyniki są następujące:
dos>e:\php43\php.exe test.php
impots(oui,2,200000)
montant=22504
impots(non,2,200000)
montant=33388
impots(oui,3,200000)
montant=16400
impots(non,3,200000)
montant=22504
impots(Array,Array,Array)
Les erreurs suivantes se sont produites :
Statut marital [array] incorrect
Nombre d'enfants [Array] incorrect
salaire [Array] incorrect
Od tej pory będziemy korzystać z klasy ImpotsDSN bez ponownego podawania jej definicji.
4.3. Aplikacja Podatki: wersja 1
Przedstawiamy teraz wersję 1 aplikacji IMPOTS. Rozważmy sytuację, w której aplikacja internetowa wyświetla użytkownikowi interfejs HTML w celu uzyskania trzech parametrów niezbędnych do obliczenia podatku:
- stan cywilny (żonaty/zamężna lub nie)
- liczba dzieci
- roczne wynagrodzenie

Formularz jest wyświetlany przez następującą stronę PHP:
<?php //formularz podatkowy ?>
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
function effacer(){
// w formularzu
with(document.frmImpots){
optMarie[0].checked=false;
optMarie[1].checked=true;
txtEnfants.value="";
txtSalaire.value="";
txtImpots.value="";
}//z
}//usunąć
</script>
</head>
<body background="/poly/impots/images/standard.jpg">
<center>
Calcul d'impôts
<hr>
<form name="frmImpots" method="POST">
<table>
<tr>
<td>Etes-vous marié(e)</td>
<td>
<input type="radio" name="optMarie" value="oui" <?php echo $requête->chkoui ?>>oui
<input type="radio" name="optMarie" value="non" <?php echo $requête->chknon ?>>non
</td>
</tr>
<tr>
<td>Nombre d'enfants</td>
<td><input type="text" size="5" name="txtEnfants" value="<?php echo $requête->enfants ?>"></td>
</tr>
<tr>
<td>Salaire annuel</td>
<td><input type="text" size="10" name="txtSalaire" value="<?php echo $requête->salaire ?>"></td>
</tr>
<tr>
<td><font color="green">Impôt</font></td>
<td><input type="text" size="10" name="txtImpots" value="<?php echo $requête->impots ?>" readonly></td>
</tr>
<tr></tr>
<tr>
<td><input type="submit" value="Calculer"></td>
<td><input type="button" value="Effacer" onclick="effacer()"></td>
</tr>
</table>
</form>
</center>
<?php
// czy są jakieś błędy?
if(count($requête->erreurs)!=0){
// wyświetlanie błędów
echo "<hr>\n<font color=\"red\">\n";
echo "Les erreurs suivantes se sont produites<br>";
echo "<ul>";
for($i=0;$i<count($requête->erreurs);$i++){
echo "<li>".$requête->erreurs[$i]."</li>\n";
}
echo "</ul>\n</font>\n";
}//if
?>
</body>
</html>
Strona PHP ogranicza się do wyświetlania informacji przekazanych jej przez główny program aplikacji w zmiennej $requête, która jest obiektem zawierającym następujące pola:
atrybuty przycisków opcji „tak” i „nie” – mogą przyjmować wartości "checked" lub "", aby włączyć lub wyłączyć odpowiedni przycisk opcji | |
liczba dzieci podatnika | |
jego roczne wynagrodzenie | |
kwota podatku do zapłaty | |
ewentualna lista błędów – może być pusta. |
Strona wysłana do klienta zawiera skrypt JavaScript z funkcją effacer powiązaną z przyciskiem „Effacer”, której zadaniem jest przywrócenie formularza do stanu początkowego: przycisk niezaznaczony, pola wprowadzania danych puste. Należy zauważyć, że tego rezultatu nie można było uzyskać przy użyciu przycisku HTML typu „reset”. W rzeczywistości, gdy stosowany jest ten typ przycisku, przeglądarka przywraca formularz do stanu, w jakim go otrzymała. Natomiast w naszej aplikacji przeglądarka otrzymuje formularze, które mogą nie być puste.
Aplikacja przetwarzająca powyższy formularz wygląda następująco:
<?php
// przetwarza formularz podatkowy
// biblioteki
include "ImpotsDSN.php";
// konfiguracja aplikacji
ini_set("register_globals","off");
ini_set("display_errors","off");
$formulaireImpots="impots_form.php";
$erreursImpots="impots_erreurs.php";
$bdImpots=array(dsn=>"mysql-dbimpots",user=>admimpots,pwd=>mdpimpots,
table=>impots,limites=>limites,coeffR=>coeffR,coeffN=>coeffN);
// pobieranie parametrów
$requête->marié=$_POST["optMarie"];
$requête->enfants=$_POST["txtEnfants"];
$requête->salaire=$_POST["txtSalaire"];
// czy mamy wszystkie parametry?
if(! isset($requête->marié) || ! isset($requête->enfants) || ! isset($requête->salaire)){
// przygotowanie pustego formularza
$requête->chkoui="";
$requête->chknon="checked";
$requête->enfants="";
$requête->salaire="";
$requête->impots="";
$requête->erreurs=array();
// wyświetlanie formularza
include $formulaireImpots;
// koniec
exit(0);
}//if
// sprawdzanie parametrów
$requête=vérifier($requête);
// czy są błędy?
if(count($requête->erreurs)!=0){
// wyświetlanie formularza
include "$formulaireImpots";
// koniec
exit(0);
}//if
// obliczanie odpowiedzi
$requête=calculerImpots($bdImpots,$requête);
// czy są jakieś błędy?
if(count($requête->erreurs)!=0){
// wyświetlanie formularza błędów
include "$erreursImpots";
// koniec
exit(0);
}//if
// wyświetlanie formularza
include "$formulaireImpots";
// koniec
exit(0);
// --------------------------------------------------------
function vérifier($requête){
// sprawdza poprawność parametrów zapytania
// na początku brak błędów
$requête->erreurs=array();
// status prawidłowy?
$requête->marié=strtolower($requête->marié);
if($requête->marié!="oui" && $requête->marié!="non"){
// błąd
$requête->erreurs[]="Statut marital [$requête->marié] incorrect";
}
// czy liczba elementów podrzędnych jest prawidłowa?
if(! preg_match("/^\s*\d+\s*$/",$requête->enfants)){
// błąd
$requête->erreurs[]="Nombre d'enfants [$requête->enfants] incorrect";
}
// płaca prawidłowa?
if(! preg_match("/^\s*\d+\s*$/",$requête->salaire)){
// błąd
$requête->erreurs[]="Salaire [$requête->salaire] incorrect";
}
// stan przycisków opcji
if($requête->marié=="oui"){
$requête->chkoui="checked";
$requête->chknon="";
}else{
$requête->chknon="checked";
$requête->chkoui="";
}
// wysyłamy zapytanie
return $requête;
}//sprawdź
// --------------------------------------------------------
function calculerImpots($bdImpots,$requête){
// obliczanie kwoty podatku
// $bdImpots: słownik zawierający informacje niezbędne do odczytania źródła danych ODBC
// $requête: zapytanie zawierające informacje o podatniku
// tworzy się obiekt ImpotsDSN
$objImpots=new ImpotsDSN($bdImpots);
// czy są jakieś błędy?
if(count($objImpots->erreurs)!=0){
// błędy umieszczamy w zapytaniu
$requête->erreurs=$objImpots->erreurs;
// gotowe
return $requête;
}//if
// obliczenie podatku
$personne=array(marié=>"$requête->marié",enfants=>"$requête->enfants",salaire=>"$requête->salaire");
$requête->impots=$objImpots->calculer($personne);
// zwracamy wynik
return $requête;
}//calculerImpots
Uwagi:
- po pierwsze, dołączono klasę ImpotsDSN. To właśnie ona stanowi podstawę obliczeń podatkowych i zasłania nam dostęp do bazy danych
- wykonuje się pewną liczbę operacji inicjalizacyjnych mających na celu ułatwienie konserwacji aplikacji. Jeśli parametry ulegną zmianie, należy zmienić ich wartości w tej sekcji. Zazwyczaj te wartości konfiguracyjne są raczej przechowywane w pliku lub bazie danych.
- Pobieramy trzy parametry formularza odpowiadające polom HTML, optMarie, txtEnfants oraz txtSalaire.
- Parametry te mogą być całkowicie lub częściowo nieobecne. Dzieje się tak zwłaszcza przy pierwszym wywołaniu formularza. W takim przypadku wysyłany jest po prostu pusty formularz.
- Następnie sprawdzana jest poprawność trzech pobranych parametrów. Jeśli okażą się nieprawidłowe, formularz jest odsyłany w postaci, w jakiej został wprowadzony, ale z dołączoną listą błędów.
- Po sprawdzeniu poprawności trzech parametrów można przystąpić do obliczenia podatku. Jest on obliczany przez funkcję calculerImpots. Funkcja ta tworzy obiekt typu ImpotsDSN i wykorzystuje metodę calculer tego obiektu.
- Utworzenie obiektu ImpotsDSN może się nie powieść, jeśli baza danych jest niedostępna. W takim przypadku aplikacja wyświetla specjalną stronę błędu wskazującą, jakie błędy wystąpiły.
- Jeśli wszystko przebiegło pomyślnie, formularz jest zwracany w postaci, w jakiej został wypełniony, ale z dodaną kwotą podatku do zapłaty.
Strona błędów wygląda następująco:
<?php // strona błędu ?>
<html>
<head>
<title>Application impôts indisponible</title>
</head>
<body background="/poly/impots/images/standard.jpg">
<h3>Calcul d'impôts</h3>
<hr>
Application indisponible. Recommencez ultérieurement.<br><br>
<font color="red">
Les erreurs suivantes se sont produites<br>
<ul>
<?php
// wyświetlanie błędów
for($i=0;$i<count($requête->erreurs);$i++){
echo "<li>".$requête->erreurs[$i]."</li>\n";
}
?>
</ul>
</font>
</body>
</html>
Oto kilka przykładów błędów. Po pierwsze, podano nieprawidłowe hasło:

W formularzu podano nieprawidłowe dane:

Wreszcie podano prawidłowe wartości:

4.4. Aplikacja Podatki: wersja 2
W poprzednim przykładzie poprawność parametrów txtEnfants i txtSalaire formularza jest sprawdzana przez serwer. W tym przypadku proponujemy sprawdzenie ich za pomocą skryptu JavaScript umieszczonego na stronie formularza. Wówczas to przeglądarka dokonuje weryfikacji parametrów. Serwer jest angażowany tylko wtedy, gdy parametry są prawidłowe. Pozwala to zaoszczędzić „przepustowość”. Strona wyświetlająca impots-form.php wygląda następująco:
<?php //formularz podatkowy ?>
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
function effacer(){
........
}//wyczyść
//------------------------------
function calculer(){
// sprawdzanie parametrów przed wysłaniem ich na serwer
with(document.frmImpots){
//liczba dzieci
champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
if(champs==null){
// szablon nie został zweryfikowany
alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
nbEnfants.focus();
return;
}//if
//wynagrodzenie
champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
if(champs==null){
// szablon nie został zweryfikowany
alert("Le salaire n'a pas été donné ou est incorrect");
salaire.focus();
return;
}//jeśli
// w porządku – wysyłamy formularz na serwer
submit();
}//with
}//obliczyć
</script>
</head>
<body background="/poly/impots/images/standard.jpg">
............
<td><input type="button" value="Calculer" onclick="calculer()"></td>
<td><input type="button" value="Effacer" onclick="effacer()"></td>
........
</body>
</html>
Należy zwrócić uwagę na następujące zmiany:
- przycisk Calculer nie jest już typu submit, lecz typu button powiązanego z funkcją o nazwie calculer. To właśnie ta funkcja przeprowadzi analizę pól txtEnfants i txTsalaire. Jeśli są one poprawne, wartości z formularza zostaną wysłane na serwer (submit), w przeciwnym razie wyświetlony zostanie komunikat o błędzie.
Oto przykładowy komunikat wyświetlany w przypadku błędu:

4.5. Aplikacja podatkowa: wersja 3
Nieznacznie modyfikujemy aplikację, aby wprowadzić do niej pojęcie sesji. Zakładamy teraz, że aplikacja służy do symulacji obliczeń podatkowych. Użytkownik może w ten sposób symulować różne „konfiguracje” podatnika i sprawdzić, jaka byłaby kwota podatku do zapłaty w każdym z tych przypadków. Poniższa strona internetowa przedstawia przykładowy wynik:

Kod wyświetlający powyższą stronę wygląda następująco:
<?php //formularz podatkowy ?>
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
function effacer(){
...
}//usunąć
//------------------------------
function calculer(){
...
}//obliczyć
</script>
</head>
<body background="/poly/impots/images/standard.jpg">
<center>
Calcul d'impôts
<hr>
<form name="frmImpots" method="POST">
<table>
<tr>
<td>Etes-vous marié(e)</td>
<td>
<input type="radio" name="optMarie" value="oui" <?php echo $requête[chkoui] ?>>oui
<input type="radio" name="optMarie" value="non" <?php echo $requête[chknon] ?>>non
</td>
</tr>
<tr>
<td>Nombre d'enfants</td>
<td><input type="text" size="5" name="txtEnfants" value="<?php echo $requête[enfants] ?>"></td>
</tr>
<tr>
<td>Salaire annuel</td>
<td><input type="text" size="10" name="txtSalaire" value="<?php echo $requête[salaire] ?>"></td>
</tr>
<tr>
<td><font color="green">Impôt</font></td>
<td><input type="text" size="10" name="txtImpots" value="<?php echo $requête[impots] ?>" readonly></td>
</tr>
<tr></tr>
<tr>
<td><input type="button" value="Calculer" onclick="calculer()"></td>
<td><input type="button" value="Effacer" onclick="effacer()"></td>
</tr>
</table>
</form>
</center>
<?php
// czy są jakieś błędy?
if(count($requête[erreurs])!=0){
// wyświetlanie błędów
echo "<hr>\n<font color=\"red\">\n";
echo "Les erreurs suivantes se sont produites<br>";
echo "<ul>";
for($i=0;$i<count($requête[erreurs]);$i++){
echo "<li>".$requête[erreurs][$i]."</li>\n";
}
echo "</ul>\n</font>\n";
}//if
// czy są symulacje?
else if(count($requête[simulations])!=0){
// wyświetlanie symulacji
echo "<hr>\n<h2>Résultats des simulations</h2>\n";
echo "<table border=\"1\">\n";
echo "<tr><td>Marié</td><td>Enfants</td><td>Salaire annuel (F)</td><td>Impôts à payer (F)</td></tr>\n";
for($i=0;$i<count($requête[simulations]);$i++){
echo "<tr>".
"<td>".$requête[simulations][$i][0]."</td>".
"<td>".$requête[simulations][$i][1]."</td>".
"<td>".$requête[simulations][$i][2]."</td>".
"<td>".$requête[simulations][$i][3]."</td>".
"</tr>\n";
}//for
echo "</table>\n";
}//if
?>
</body>
</html>
Program wyświetlający wykazuje niewielkie różnice w stosunku do poprzedniej wersji:
- słownik otrzymuje słownik $requête zamiast obiektu $requête. Należy zatem wpisać $requête[enfants], a nie $requête->dzieci.
- Słownik ten posiada pole o nazwie simulations, które jest tablicą dwuwymiarową. $requête[simulations][i] to symulacja nr i. Jest to tablica składająca się z czterech ciągów znaków: stan cywilny, liczba dzieci, roczne wynagrodzenie, podatek do zapłaty.
Program główny uległ natomiast bardziej znaczącej zmianie:
<?php
// przetwarza formularz podatkowy
// biblioteki
include "ImpotsDSN.php";
// rozpoczęcie sesji
session_start();
// konfiguracja aplikacji
ini_set("register_globals","off");
ini_set("display_errors","off");
$formulaireImpots="impots_form.php";
$erreursImpots="impots_erreurs.php";
$bdImpots=array(dsn=>"mysql-dbimpots",user=>admimpots,pwd=>mdpimpots,
table=>impots,limites=>limites,coeffR=>coeffR,coeffN=>coeffN);
// pobieranie parametrów sesji
$session=$_SESSION["session"];
// czy sesja jest ważna?
if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
// rozpoczynamy nową sesję
$session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
// czy wystąpiły błędy?
if(count($session[objImpots]->erreurs)!=0){
$requête=array(erreurs=>$session[objImpots]->erreurs);
// wyświetlanie strony błędów
include $erreursImpots;
// koniec
$session=array();
terminerSession($session);
}//if
}//if
// pobieramy parametry bieżącej wymiany
$requête[marié]=$_POST["optMarie"];
$requête[enfants]=$_POST["txtEnfants"];
$requête[salaire]=$_POST["txtSalaire"];
// czy mamy wszystkie parametry?
if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
// wyświetlenie pustego formularza
$requête=array(chkoui=>"",chknon=>"checked",enfants=>"",salaire=>"",impots=>"",
erreurs=>array(),simulations=>array());
include $formulaireImpots;
// koniec
terminerSession($session);
}//if
// sprawdzanie parametrów
$requête=vérifier($requête);
// czy są jakieś błędy?
if(count($requête[erreurs])!=0){
// wyświetlenie formularza
include "$formulaireImpots";
// koniec
terminerSession($session);
}//if
// obliczenie podatku do zapłaty
$requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
enfants=>$requête[enfants],salaire=>$requête[salaire]));
// kolejna symulacja
$session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
$requête[simulations]=$session[simulations];
// wyświetlenie formularza
include "$formulaireImpots";
// koniec
terminerSession($session);
// --------------------------------------------------------
function vérifier($requête){
// sprawdza poprawność parametrów zapytania
// na początku brak błędów
$requête[erreurs]=array();
// status prawidłowy?
$requête[marié]=strtolower($requête[marié]);
if($requête[marié]!="oui" && $requête[marié]!="non"){
// błąd
$requête[erreurs][]="Statut marital [$requête[marié]] incorrect";
}
// czy liczba elementów podrzędnych jest prawidłowa?
if(! preg_match("/^\s*\d+\s*$/",$requête[enfants])){
// błąd
$requête[erreurs][]="Nombre d'enfants [$requête[enfants]] incorrect";
}
// płaca prawidłowa?
if(! preg_match("/^\s*\d+\s*$/",$requête[salaire])){
// błąd
$requête[erreurs][]="Salaire [$requête[salaire]] incorrect";
}
// stan przycisków opcji
if($requête[marié]=="oui"){
$requête[chkoui]="checked";
$requête[chknon]="";
}else{
$requête[chknon]="checked";
$requête[chkoui]="";
}
// wysyłamy zapytanie
return $requête;
}//sprawdź
// --------------------------------------------------------
function terminerSession($session){
// zapisywanie sesji
$_SESSION[session]=$session;
// koniec skryptu
exit(0);
}//terminerSession
- Po pierwsze, ta aplikacja obsługuje sesję. Sesję uruchamia się więc na początku skryptu:
- Sesja przechowuje tylko jedną zmienną: słownik $session. Słownik ten posiada dwa pola:
- objImpots: obiekt ImpotsDSN. Obiekt ten jest zapisywany w sesji klienta, aby uniknąć niepotrzebnych kolejnych dostępów do bazy danych ODBC.
- symulacje: tablica symulacji służąca do zapamiętywania symulacji poprzednich transakcji między kolejnymi transakcjami.
- Po uruchomieniu sesji pobierana jest zmienna $session. Jeśli zmienna ta jeszcze nie istnieje, oznacza to, że sesja właśnie się rozpoczyna. Wówczas tworzony jest obiekt ImpotsDSN oraz pusta tablica symulacji. W razie potrzeby zgłaszane są błędy.
<?php
...
// pobieramy parametry sesji
$session=$_SESSION["session"];
// czy sesja jest ważna?
if(! isset($session) || ! isset($session[objImpots]) || ! isset($session[simulations])){
// rozpoczynamy nową sesję
$session=array(objImpots=>new ImpotsDSN($bdImpots),simulations=>array());
// czy wystąpiły błędy?
if(count($session[objImpots]->erreurs)!=0){
$requête=array(erreurs=>$session[objImpots]->erreurs);
// wyświetlanie strony błędów
include $erreursImpots;
// koniec
$session=array();
terminerSession($session);
}//if
}//if
- Po pomyślnym zainicjowaniu sesji pobierane są parametry wymiany. Jeśli nie ma wszystkich oczekiwanych parametrów, wysyłany jest pusty formularz.
<?php
...
// pobieramy parametry bieżącej wymiany
$requête[marié]=$_POST["optMarie"];
$requête[enfants]=$_POST["txtEnfants"];
$requête[salaire]=$_POST["txtSalaire"];
// czy mamy wszystkie parametry?
if(! isset($requête[marié]) || ! isset($requête[enfants]) || ! isset($requête[salaire])){
// wyświetlenie pustego formularza
$requête=array(chkoui=>"",chknon=>"checked",enfants=>"",salaire=>"",impots=>"",
erreurs=>array(),simulations=>array());
include $formulaireImpots;
// koniec
terminerSession($session);
}//if
- Jeśli wszystkie parametry są poprawne, zostają one zweryfikowane. W przypadku wystąpienia błędów są one zgłaszane:
<?php
...
// sprawdzanie parametrów
$requête=vérifier($requête);
// czy są jakieś błędy?
if(count($requête[erreurs])!=0){
// wyświetlenie formularza
include "$formulaireImpots";
// koniec
terminerSession($session);
}//if
- jeśli parametry są prawidłowe, podatek jest obliczany:
<?php
...
// obliczenie podatku do zapłaty
$requête[impots]=$session[objImpots]->calculer(array(marié=>$requête[marié],
enfants=>$requête[enfants],salaire=>$requête[salaire]));
- trwająca symulacja zostaje dodana do tabeli symulacji, a ta jest wysyłana do klienta wraz z formularzem:
<?php
...
// kolejna symulacja
$session[simulations][]=array($requête[marié],$requête[enfants],$requête[salaire],$requête[impots]);
$requête[simulations]=$session[simulations];
// wyświetlenie formularza
include "$formulaireImpots";
// koniec
terminerSession($session);
- W każdym przypadku skrypt kończy się zapisaniem zmiennej $session w sesji. Odbywa się to w procedurze terminerSession.
<?php
...
function terminerSession($session){
// zapisywanie sesji
$_SESSION[session]=$session;
// koniec skryptu
exit(0);
}//terminerSession
Na zakończenie przedstawimy program wyświetlający błędy dostępu do bazy danych (impots-erreurs.php):
<?php // strona błędu ?>
<html>
<head>
<title>Application impôts indisponible</title>
</head>
<body background="/poly/impots/images/standard.jpg">
<h3>Calcul d'impôts</h3>
<hr>
Application indisponible. Recommencez ultérieurement.<br><br>
<font color="red">
Les erreurs suivantes se sont produites<br>
<ul>
<?php
// wyświetlanie błędów
for($i=0;$i<count($requête[erreurs]);$i++){
echo "<li>".$requête[erreurs][$i]."</li>\n";
}
?>
</ul>
</font>
</body>
</html>
Również w tym przypadku obiekt $requête został zastąpiony słownikiem $requête.
4.6. Aplikacja podatkowa: wersja 4
Teraz stworzymy samodzielną aplikację, która będzie klientem internetowym poprzedniej aplikacji internetowej impots. Aplikacja będzie aplikacją konsolową uruchamianą z okna DOS:
dos>e:\php43\php.exe cltImpots.php
Syntaxe cltImpots.php urlImpots marié enfants salaire [jeton]
Parametry klienta internetowego cltImpots są następujące:
// składnia $0 urlImpots żonaty dzieci wynagrodzenie token
// klient serwisu podatkowego działającego na urlImpots
// przesyła do tej usługi trzy informacje: stan cywilny, dzieci, wynagrodzenie
// ewentualnie wraz z tokenem sesji, jeśli został przekazany
// wyświetla tabelę symulacji dla bieżącej sesji
Oto kilka przykładów zastosowania. Najpierw pierwszy przykład bez tokenu sesji.
dos>e:\php43\php.exe cltImpots.php http://localhost/poly/impots/6/impots.php tak 2 200000
Jeton de session=[a6297317667bc981c462120987b8dd18]
Simulations :
[Marié,Enfants,Salaire annuel (F),Impôts à payer (F)]
[oui,2,200000,22504]
Prawidłowo pobraliśmy kwotę należnego podatku (22504 f). Pobraliśmy również token sesji. Teraz możemy go wykorzystać do drugiego zapytania:
dos>e:\php43\php.exe cltImpots.php http://localhost/poly/impots/6/impots.php tak 3 200000 a6297317667bc981c462120987b8dd18
Jeton de session=[a6297317667bc981c462120987b8dd18]
Simulations :
[Marié,Enfants,Salaire annuel (F),Impôts à payer (F)]
[oui,2,200000,22504]
[oui,3,200000,16400]
Otrzymujemy tablicę symulacji przesłanych przez serwer WWW. Pobany token pozostaje oczywiście ten sam. Jeśli wywołamy usługę internetową, gdy baza danych nie została uruchomiona, otrzymujemy następujący wynik:
dos>e:\php43\php.exe cltImpots2.php http://localhost/poly/impots/6/impots.php tak 3 2 000 000
Jeton de session=[8369014d5053212bc42f64bbdfb152ee]
Les erreurs suivantes se sont produites :
Impossible d'ouvrir la base DSN [mysql-dbimpots] (S1000)
Podczas tworzenia programowego klienta internetowego konieczna jest dokładna wiedza na temat tego, co serwer wysyła w odpowiedzi na różne możliwe żądania klienta. Serwer wysyła zestaw wierszy HTML zawierający informacje przydatne oraz inne, które służą wyłącznie do formatowania strony HTML. Wyrażenia regularne PHP mogą pomóc nam w odnalezieniu przydatnych informacji w strumieniu wierszy wysyłanych przez serwer. W tym celu musimy znać dokładny format poszczególnych odpowiedzi serwera. Możemy to sprawdzić, wysyłając zapytanie do serwisu internetowego za pomocą przeglądarki i przeglądając wysłany kod źródłowy. Należy zauważyć, że metoda ta nie pozwala na poznanie nagłówków HTTP zawartych w odpowiedzi serwera internetowego. Czasami warto je znać. W takim przypadku można skorzystać z jednego z dwóch ogólnych klientów internetowych omówionych w poprzednim rozdziale.
Jeśli powtórzymy poprzednie przykłady, oto kod HTML otrzymany przez przeglądarkę dla tabeli symulacji:
<h2>Résultats des simulations</h2>
<table border="1">
<tr><td>Marié</td><td>Enfants</td><td>Salaire annuel (F)</td><td>Impôts à payer (F)</td></tr>
<tr><td>oui</td><td>2</td><td>200000</td><td>22504</td></tr>
<tr><td>oui</td><td>3</td><td>200000</td><td>16400</td></tr>
</table>
Jest to tabela o nazwie HTML, jedyna w przesłanym dokumencie. Wyrażenie regularne „|<tr>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*</tr>|” powinno zatem umożliwiać odnalezienie w dokumencie różnych otrzymanych symulacji. Gdy baza danych jest niedostępna, otrzymujemy następujący dokument:
Application indisponible. Recommencez ultérieurement.<br><br>
<font color="red">
Les erreurs suivantes se sont produites<br>
<ul>
<li>Impossible d'ouvrir la base DSN [mysql-dbimpots] (S1000)</li>
</ul>
</font>
Aby odzyskać błąd, klient internetowy będzie musiał wyszukać w odpowiedzi serwera internetowego wiersze odpowiadające wzorcowi: „|<li>(.+?)</li>|”.
Mamy teraz wytyczne dotyczące tego, co należy zrobić, gdy użytkownik zażąda obliczenia podatku z poziomu poprzedniego klienta konsolowego:
- sprawdzić, czy wszystkie parametry są prawidłowe, i w razie potrzeby zgłosić błędy.
- nawiązać połączenie z URL podanym jako pierwszy parametr. W tym celu należy postępować zgodnie z wzorem ogólnego klienta internetowego, który został już przedstawiony i omówiony
- w strumieniu odpowiedzi serwera, użyjmy wyrażeń regularnych, aby:
- znalezienie komunikatów o błędach
- znaleźć wyniki symulacji
Kod klienta cltImpots.php jest następujący:
<?php
// składnia $0 urlImpots żonaty dzieci wynagrodzenie token
// klient serwisu podatkowego działającego na platformie urlImpots
// przesyła do tej usługi trzy informacje: stan cywilny, dzieci, wynagrodzenie
// ewentualnie wraz z tokenem sesji, jeśli został przekazany
// wyświetla tabelę symulacji dla bieżącej sesji
// sprawdza liczbę argumentów
if(count($argv)!=5 && count($argv)!=6){
// błąd
fwrite(STDERR,"Syntaxe $argv[0] urlImpots marié enfants salaire [jeton]\n");
// koniec
exit(1);
}//if
// odnotowuje się URL
$urlImpots=analyseURL($argv[1]);
if(isset($urlImpots[erreur])){
// błąd
fwrite(STDERR,"$urlImpots[erreur]\n");
// koniec
exit(1);
}//if
// analiza żądania użytkownika
$demande=analyseDemande("$argv[2],$argv[3],$argv[4]");
// błędy?
if($demande[erreur]){
// komunikat
echo "$demande[erreur]\n";
// koniec
exit(1);
}//if
// można wysłać żądanie – używamy tokenu sesji
$impots=getImpots($urlImpots,$demande,$argv[5]);
// wyświetlamy token sesji
echo "Jeton de session=[$impots[jeton]]\n";
// czy są jakieś błędy?
if(count($impots[erreurs])!=0){
//komunikaty o błędach
echo "Les erreurs suivantes se sont produites :\n";
for($i=0;$i<count($impots[erreurs]);$i++){
echo $impots[erreurs][$i]."\n";
}//dla
}else{
// brak błędów – wyświetlanie symulacji
echo "Simulations :\n";
for($i=0;$i<count($impots[simulations]);$i++){
echo "[".implode(",",$impots[simulations][$i])."]\n";
}//for
}//if
// koniec programu
exit(0);
// --------------------------------------------------------------
function analyseURL($URL){
// analiza poprawności pliku URL $URL
$url=parse_url($URL);
// protokół
if(strtolower($url[scheme])!="http"){
$url[erreur]="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $url;
}//if
// urządzenie
if(! isset($url[host])){
$url[erreur]="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $url;
}//if
// port
if(! isset($url[port])) $url[port]=80;
// żądanie
if(isset($url["query"])){
$url[erreur]="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $url;
}//if
// powrót
return $url;
}//analyseURL
// -----------------------------------------------------------
function analyseDemande($demande){
// $demande: ciąg do analizy
// musi mieć postać: żonaty, dzieci, wynagrodzenie
// poprawny format
if(! preg_match("/^\s*(oui|non)\s*,\s*(\d{1,3})\s*,\s*(\d+)\s*$/i",$demande,$champs)){
// nieprawidłowy format
return(array(erreur=>"Format (marié, enfants, salaire) invalide."));
}
// w porządku
$marié=strtolower($champs[1]);
return array(marié=>$marié,enfants=>$champs[2],salaire=>$champs[3]);
}//analyseDemande
// --------------------------------------------------------------
function getImpots($urlImpots,$demande,$jeton){
// $urlImpots: należy sprawdzić URL
// $demande: słownik zawierający pola „mąż”, „dzieci”, „wynagrodzenie”
// $jeton: ewentualny token sesji
// nawiązanie połączenia na porcie $urlImpots[port] z $urlImpots[host]
$connexion=fsockopen($urlImpots[host],$urlImpots[port],&$errno,&$erreur);
// powrót w przypadku błędu
if(! $connexion){
return array(erreurs=>array("Echec de la connexion au site ($urlImpots[host],$urlImpots[port]) : $erreur"));
}//if
// wysyłamy nagłówki HTTP do serwera
POST($connexion,$urlImpots,$jeton,
array(optMarie=>$demande[marié],txtEnfants=>$demande[enfants],txtSalaire=>$demande[salaire]));
// odczytujemy odpowiedź serwera – najpierw nagłówki HTTP
// pierwszy wiersz
$ligne=fgets($connexion,10000);
// URL znaleziono?
if(! preg_match("/^(.+?) 200 OK\s*$/",$ligne)){
// URL niedostępne – zwracany jest błąd
return array(erreurs=>array("L'URL $urlImpots[path] n'a pu être trouvée"));
}//if
// odczytujemy pozostałe nagłówki HTTP
while(($ligne=fgets($connexion,10000)) && (($ligne=rtrim($ligne))!="")){
// wyszukiwanie tokenu, jeśli jeszcze go nie znaleziono
if(! $jeton){
// wyszukiwanie wiersza „set-cookie”
if(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i",$ligne,$champs)){
// znaleziono token – zapisujemy go
$jeton=$champs[1];
}//if
}//if
}//następna linia
// odczytujemy następujący dokument
$document="";
while($ligne=fread($connexion,10000)){
$document.=$ligne;
}//while
// zamyka się połączenie
fclose($connexion);
// analizujemy dokument
$impots=getInfos($document);
// zwracamy wynik
return array(jeton=>$jeton,erreurs=>$impots[erreurs],simulations=>$impots[simulations]);
}//getImpots
// --------------------------------------------------------------
function POST($connexion,$url,$jeton,$paramètres){
// $connexion: połączenie z serwerem WWW
// $url: URL do zapytania
// $paramètres: słownik parametrów do wysłania
// przygotowujemy plik POST
$post="";
while(list($paramètre,$valeur)=each($paramètres)){
$post.=$paramètre."=".urlencode($valeur)."&";
}//while
// usuwamy ostatni znak
$post=substr($post,0,-1);
// przygotowujemy żądanie HTTP w formacie HTTP/1.0
$HTTP="POST $url[path] HTTP/1.0\n";
$HTTP.="Content-type: application/x-www-form-urlencoded\n";
$HTTP.="Content-length: ".strlen($post)."\n";
$HTTP.="Connection: close\n";
if($jeton) $HTTP.="Cookie: PHPSESSID=$jeton\n";
$HTTP.="\n";
$HTTP.=$post;
// wysyła się żądanie HTTP
fwrite($connexion,$HTTP);
}//POST
// --------------------------------------------------------------
function getInfos($document){
// $document: dokument HTML
// szukamy albo listy błędów
// albo tabeli symulacji
// przygotowanie wyniku
$impots[erreurs]=array();
$impots[simulations]=array();
// czy wystąpił błąd?
// szablon wiersza błędu
$modErreur="|<li>(.+?)</li>|";
// wyszukiwanie w dokumencie
if(preg_match_all($modErreur,$document,$champs,PREG_SET_ORDER)){
// pobieranie błędów
for($i=0;$i<count($champs);$i++){
$impots[erreurs][]=$champs[$i][1];
}//dla
// zakończone
return $impots;
}//jeśli
// przykładowy wiersz z tabeli symulacji
// <tr><td>tak</td><td>2</td><td>200000</td><td>22504</td></tr>
$modSimulation="|<tr>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*</tr>|";
// wyszukiwanie w dokumencie
if(preg_match_all($modSimulation,$document,$champs,PREG_SET_ORDER)){
// pobieranie symulacji
for($i=0;$i<count($champs);$i++){
$impots[simulations][]=array($champs[$i][1],$champs[$i][2],$champs[$i][3],$champs[$i][4]);
}//dla
// zwrot wyniku
return $impots;
}//if
// nie jest normalne, że się tu znalazłem
return $impots;
}//getInfos
?>
Uwagi:
- program rozpoczyna się od sprawdzenia poprawności otrzymanych parametrów. Wykorzystuje w tym celu dwie funkcje: analyseURL, która sprawdza poprawność parametru URL, oraz analyseDemande, która sprawdza pozostałe parametry.
- Obliczenie podatku odbywa się za pomocą funkcji getImpots:
- Funkcja getImpots jest zadeklarowana w następujący sposób:
<?php
...
// --------------------------------------------------------------
function getImpots($urlImpots,$demande,$jeton){
// $urlImpots: należy sprawdzić URL
// $demande: słownik zawierający pola „mąż”, „dzieci”, „wynagrodzenie”
// $jeton: ewentualny token sesji
- (ciąg dalszy)
- $urlImpots to słownik zawierający następujące pola:
- host: komputer, na którym znajduje się usługa internetowa
- port: port usługi
- path: ścieżka do żądanego zasobu
- $demande to słownik zawierający następujące pola:
- marié: tak/nie: stan cywilny
- dzieci: liczba dzieci
- wynagrodzenie: roczne wynagrodzenie
- $jeton to token sesji.
- $urlImpots to słownik zawierający następujące pola:
Funkcja zwraca słownik zawierający następujące pola:
- błędy: tablica komunikatów o błędach
- symulacje: tablica symulacji, przy czym każda symulacja jest z kolei tablicą zawierającą cztery elementy (stan cywilny, dzieci, wynagrodzenie, podatek)
- token: token sesji
- Po uzyskaniu wyniku funkcji getImpots program wyświetla wyniki i kończy działanie:
<?php
...
// wyświetla się token sesji
echo "Jeton de session=[$impots[jeton]]\n";
// czy są jakieś błędy?
if(count($impots[erreurs])!=0){
//komunikaty o błędach
echo "Les erreurs suivantes se sont produites :\n";
for($i=0;$i<count($impots[erreurs]);$i++){
echo $impots[erreurs][$i]."\n";
}//dla
}else{
// brak błędów – wyświetlanie symulacji
echo "Simulations :\n";
for($i=0;$i<count($impots[simulations]);$i++){
echo "[".implode(",",$impots[simulations][$i])."]\n";
}//for
}//if
// koniec programu
exit(0);
- Przeanalizujmy teraz funkcję getImpots($urlImpots,$demande,$jeton), która ma
- utworzyć połączenie TCP-IP na porcie $urlImpots[port] maszyny $urlImpots[host]
- wysłać do serwera WWW nagłówki HTTP, których oczekuje, w szczególności token sesji, jeśli taki istnieje
- wysłać do serwera WWW żądanie o nagłówku POST wraz z parametrami zawartymi w słowniku $demande
- przeanalizować odpowiedź serwera WWW w celu znalezienia w niej albo listy błędów, albo tabeli symulacji.
- Wysyłanie nagłówków HTTP odbywa się za pomocą funkcji POST:
<?php
...
// wysyłamy nagłówki HTTP na serwer
POST($connexion,$urlImpots,$jeton,
array(optMarie=>$demande[marié],txtEnfants=>$demande[enfants],txtSalaire=>$demande[salaire]));
- Po wysłaniu nagłówków HTTP funkcja getImpots odczytuje całą odpowiedź serwera i umieszcza ją w $document. Odpowiedź jest odczytywana bez analizy, z wyjątkiem pierwszego wiersza, który pozwala nam ustalić, czy serwer WWW znalazł żądany plik URL. Jeśli plik URL został znaleziony, serwer WWW odpowiada HTTP/1.X 200 OK, gdzie X zależy od używanej wersji pliku HTTP.
<?php
...
// znaleziono URL?
if(! preg_match("/^(.+?) 200 OK\s*$/",$ligne)){
<?php
...
// URL niedostępny – powrót z błędem
return array(erreurs=>array("L'URL $urlImpots[path] n'a pu être trouvée"));
}//if
- dokument jest analizowany za pomocą funkcji getInfos:
- Wynikiem jest słownik zawierający dwa pola:
- błędy: lista błędów – może być pusta
- symulacje: lista symulacji – może być pusta
- Po wykonaniu tej operacji funkcja getImpots może zwrócić wynik w postaci słownika.
<?php
...
// zwracamy wynik
return array(jeton=>$jeton,erreurs=>$impots[erreurs],simulations=>$impots[simulations]);
- Przeanalizujmy teraz funkcję POST:
<?php
...
// --------------------------------------------------------------
function POST($connexion,$url,$jeton,$paramètres){
// $connexion: połączenie z serwerem internetowym
// $url: URL, do którego należy wysłać zapytanie
// $paramètres: słownik parametrów do wysłania
// przygotowujemy plik POST
$post="";
while(list($paramètre,$valeur)=each($paramètres)){
$post.=$paramètre."=".urlencode($valeur)."&";
}//while
// usuwamy ostatni znak
$post=substr($post,0,-1);
// przygotowujemy żądanie HTTP w formacie HTTP/1.0
$HTTP="POST $url[path] HTTP/1.0\n";
$HTTP.="Content-type: application/x-www-form-urlencoded\n";
$HTTP.="Content-length: ".strlen($post)."\n";
$HTTP.="Connection: close\n";
if($jeton) $HTTP.="Cookie: PHPSESSID=$jeton\n";
$HTTP.="\n";
$HTTP.=$post;
// wysyłamy żądanie HTTP
fwrite($connexion,$HTTP);
}//POST
Chociaż w ramach programu mieliśmy już okazję zażądać zasobu internetowego za pomocą kodu GET, nie mieliśmy jeszcze okazji zrobić tego za pomocą kodu POST. Kod POST różni się od kodu GET sposobem wysyłania parametrów do serwera. Musi on wysyłać następujące nagłówki HTTP:
wraz z
- chemin: ścieżka do żądanego zasobu internetowego, w tym przypadku $url[path]
- HTTP/1.X: żądany protokół HTTP. W tym przypadku wybrano HTTP/1.0, aby otrzymać odpowiedź w jednym bloku. HTTP/1.1 pozwala na wysłanie odpowiedzi w kilku blokach (chunked).
- N określa liczbę znaków, które klient zamierza wysłać do serwera
N znaków tworzących parametry zapytania jest wysyłanych bezpośrednio po pustej linii, która kończy nagłówki HTTP wysyłane do serwera. Parametry te mają postać param1=val1¶m2=val2&..., gdzie parami to nazwa parametru, a vali to jego wartość. Dopuszczalne wartości mogą zawierać „przeszkadzające” znaki, takie jak spacje, znak &, znak = itp. Znaki te należy zastąpić ciągiem %XX, gdzie XX to ich kod szesnastkowy. Funkcja PHP urlencode wykonuje to zadanie. Odwrotną operację wykonuje funkcja urldecode.
Na koniec należy zauważyć, że jeśli posiadamy token, jest on wysyłany z nagłówkiem HTTP Cookie: PHPSESSID=token.
- Pozostaje nam jeszcze przeanalizować funkcję, która analizuje odpowiedź serwera:
<?php
...
// --------------------------------------------------------------
function getInfos($document){
// $document: dokument HTML
// szukamy albo listy błędów
// albo tabeli symulacji
// przygotowanie wyniku
$impots[erreurs]=array();
$impots[simulations]=array();
- Należy pamiętać, że błędy są wysyłane przez serwer w postaci <li>komunikat o błędzie</li>. Wyrażenie regularne „|<li>(.+?)</li>|” powinno umożliwić pobranie tych informacji. W tym przypadku użyliśmy znaku | do wyodrębnienia wyrażenia regularnego zamiast znaku /, ponieważ ten ostatni występuje również w samym wyrażeniu regularnym. Funkcja preg_match_all ($modèle, $document, $champs, PREG_SET_ORDER) pozwala pobrać wszystkie wystąpienia $modèle znalezione w $document. Są one umieszczane w tablicy $champs. Zatem $champs[i] reprezentuje wystąpienie nr i elementu $modèle w $document. W tabeli $champs[$i][0] umieszczany jest ciąg znaków odpowiadający wzorcowi. Gdyby zawierał on nawiasy, ciąg znaków odpowiadający pierwszemu nawiasowi umieszczany jest w pliku $champs[$i][1], drugi w $champs[$i][2] i tak dalej. Kod służący do pobrania błędów będzie zatem następujący:
<?php
...
// czy wystąpił błąd?
// szablon wiersza błędu
$modErreur="|<li>(.+?)</li>|";
// wyszukiwanie w dokumencie
if(preg_match_all($modErreur,$document,$champs,PREG_SET_ORDER)){
// pobieranie błędów
for($i=0;$i<count($champs);$i++){
$impots[erreurs][]=$champs[$i][1];
}//for
// koniec
return $impots;
}//if
4.7. Aplikacja podatkowa: Podsumowanie
Przedstawiliśmy różne wersje naszej aplikacji klient-serwer do obliczania podatków:
- wersja 1: usługa jest obsługiwana przez zestaw programów PHP, a klientem jest przeglądarka. Przeprowadza ona tylko jedną symulację i nie zapamiętuje poprzednich.
- wersja 2: dodano kilka funkcji po stronie przeglądarki poprzez umieszczenie skryptów JavaScript w dokumencie HTML ładowanym przez przeglądarkę. Skrypt ten sprawdza poprawność parametrów formularza.
- wersja 3: umożliwiono usłudze zapamiętywanie różnych symulacji przeprowadzonych przez klienta poprzez zarządzanie sesją. Interfejs HTML został odpowiednio zmodyfikowany, aby je wyświetlać.
- wersja 4: klient jest teraz samodzielną aplikacją konsolową. Pozwala nam to powrócić do tworzenia programowanych klientów internetowych.
W tym miejscu można sformułować kilka uwag:
- wersje od 1 do 3 obsługują przeglądarki, które nie mają żadnych innych możliwości poza wykonywaniem skryptów JavaScript. Należy zauważyć, że użytkownik zawsze ma możliwość zablokowania wykonywania tych skryptów. Wówczas aplikacja będzie działać tylko częściowo w wersji 1 (opcja „Usuń” nie będzie działać) oraz w ogóle nie będzie działać w wersjach 2 i 3 (opcje „Usuń” i „Oblicz” nie będą działać). Interesujące mogłoby być stworzenie wersji serwisu, która nie wykorzystuje skryptów JavaScript.
Tworząc serwis internetowy, należy zadać sobie pytanie, do jakich typów klientów kierujemy naszą ofertę. Jeśli chcemy dotrzeć do jak największej liczby klientów, stworzymy aplikację, która wysyła do przeglądarek wyłącznie kod HTML (bez JavaScriptu ani apletów). Jeśli pracujemy w intranecie i mamy kontrolę nad konfiguracją stanowisk w nim, możemy pozwolić sobie na większe wymagania wobec klienta.
Wersja 4 to klient internetowy, który wyszukuje potrzebne informacje w strumieniu HTML wysyłanym przez serwer. Bardzo często nie mamy kontroli nad tym strumieniem. Dzieje się tak, gdy piszemy klienta dla istniejącej w sieci usługi internetowej, którą zarządza ktoś inny. Weźmy przykład. Załóżmy, że nasza usługa symulacji obliczeń podatkowych została stworzona przez firmę X. Obecnie usługa ta wysyła symulacje w tabeli o nazwie HTML, a nasz klient wykorzystuje ten fakt, aby je pobrać. W ten sposób porównuje każdy wiersz odpowiedzi serwera z wyrażeniem regularnym:
// szablon wiersza tabeli symulacji
// <tr><td>tak</td><td>2</td><td>200000</td><td>22504</td></tr>
$modSimulation="|<tr>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*</tr>|";
Załóżmy teraz, że twórca aplikacji zmienia wygląd odpowiedzi, umieszczając symulacje nie w tabeli, ale na liście w następującej postaci:
W takim przypadku nasz klient internetowy będzie wymagał przeróbki. To właśnie stanowi stałe zagrożenie dla klientów internetowych aplikacji, nad którymi nie mamy kontroli. XML może stanowić rozwiązanie tego problemu:
- zamiast generować HTML, usługa symulacji wygeneruje XML. W naszym przykładzie mogłoby to wyglądać następująco:
<simulations>
<entetes marie="marié" enfants="enfants" salaire="salaire" impot="impôt"/>
<simulation marie="oui" enfants="2" salaire="200000" impot="22504" />
<simulation marie="non" enfants="2" salaire="200000" impot="33388" />
</simulations>
- do tej odpowiedzi można by przypisać arkusz stylów, wskazujący przeglądarkom, jak ta odpowiedź powinna wyglądać XML
- klienty internetowe ignorowałyby ten arkusz stylów i pobierałyby informacje bezpośrednio ze strumienia odpowiedzi XML
Jeśli twórca serwisu zechce zmienić wygląd dostarczanych wyników, zmieni arkusz stylów, a nie sam kod XML. Dzięki arkuszowi stylów przeglądarki wyświetlą nowy wygląd, a programowe klienty internetowe nie będą wymagały modyfikacji.
