3. Wprowadzenie do programowania internetowego w PHP
3.1. Programowanie w języku PHP
Warto tutaj przypomnieć, że PHP jest pełnoprawnym językiem programowania i choć stosuje się go głównie przy tworzeniu aplikacji internetowych, można go wykorzystywać również w innych kontekstach. Dokument „PHP na przykładach”, dostępny pod adresem http://shiva.istia.univ-angers.fr/~tahe/pub/php/php.pdf, przedstawia podstawy tego języka. Zakładamy tutaj, że wiedza ta została już przyswojona. Na prostym przykładzie pokażemy procedurę uruchamiania programu PHP w systemie Windows. Poniższy kod został zapisany pod nazwą coucou.php.
Program ten uruchamia się w oknie DOS systemu Windows:
dos>"e:\program files\easyphp\php\php.exe" coucou.php
X-Powered-By: PHP/4.3.0-dev
Content-type: text/html
coucou
Warto zauważyć, że interpreter PHP domyślnie wysyła:
- interpreter PHP to php.exe i zazwyczaj znajduje się w katalogu <php> instalacji oprogramowania.
- nagłówki HTTP X-Powered-By oraz Content-type:
- pusty wiersz oddzielający nagłówki HTTP od reszty dokumentu
- dokument utworzony tutaj z tekstu wygenerowanego przez funkcję echo
3.2. Plik konfiguracyjny interpretera PHP
Działanie interpretera PHP jest konfigurowane za pomocą pliku konfiguracyjnego o nazwie php.ini, który w systemie Windows znajduje się w samym katalogu systemu Windows. Jest to plik o znacznej wielkości, ponieważ w systemie Windows i dla wersji 4.2 programu PHP liczy on prawie 1000 wierszy, z których na szczęście trzy czwarte stanowią komentarze. Przyjrzyjmy się niektórym atrybutom konfiguracyjnym programu PHP:
umożliwia umieszczanie instrukcji między tagami <? >. W przypadku off należy je umieścić między tagami <?php ... > | |
w on umożliwia stosowanie składni <% =zmienna %> używanej w technologii ASP (Active Server Pages) | |
umożliwia wysłanie nagłówka HTTP X-Powered-By: PHP/4.3.0-dev. W off nagłówek ten jest usuwany. | |
określa zakres monitorowania błędów. W tym przypadku zgłaszane będą wszystkie błędy (E_ALL) z wyjątkiem ostrzeżeń pojawiających się podczas wykonywania (~E_NOTICE) | |
przy wartości on umieszcza błędy w strumieniu HTML wysyłanym do klienta. Są one zatem wyświetlane w przeglądarce. Zaleca się ustawienie tej opcji na wartość off. | |
błędy zostaną zapisane w pliku | |
zapisuje ostatni wystąpiły błąd w zmiennej $php_errormsg | |
ustawia plik do zapisywania błędów (jeśli log_errors=on) | |
w on pewna liczba zmiennych staje się globalna. Uznane za lukę w zabezpieczeniach. | |
domyślnie generuje nagłówek HTTP: Content-type: text/html | |
lista katalogów, które zostaną przeszukane w poszukiwaniu plików wymaganych przez dyrektywy include lub require | |
katalog, w którym będą zapisywane pliki przechowujące informacje o różnych bieżących sesjach. Dotyczy to dysku, na którym zainstalowano PHP. W tym przypadku /temp odnosi się do e:\temp |
Ten plik konfiguracyjny wpływa na przenośność napisanego programu w języku PHP. W rzeczywistości, jeśli aplikacja internetowa ma pobrać wartość pola C z formularza internetowego, może to zrobić na różne sposoby, w zależności od tego, czy zmienna konfiguracyjna register_globals ma wartość on czy off:
- off: wartość zostanie pobrana przez $HTTP_GET_VARS["C"] lub _GET["C"] lub $HTTP_POST_VARS["C"] lub $_POST["C"], w zależności od metody (GET/POST) zastosowanej przez klienta do wysłania wartości formularza
- : tak samo jak powyżej, plus $C, ponieważ wartość pola C została przypisana do zmiennej globalnej o tej samej nazwie co pole
Jeśli programista napisze program, używając notacji $C, ponieważ serwer WWW/PHP, z którego korzysta, przypisuje zmiennej o nazwie register_globals wartość on, program ten przestanie działać, jeśli zostanie przeniesiony na serwer WWW/PHP, na którym ta sama zmienna ma wartość off. Należy zatem starać się pisać programy, unikając korzystania z funkcji zależnych od konfiguracji serwera WWW/PHP.
3.3. Konfiguracja PHP podczas wykonywania
Aby poprawić przenośność programu PHP, można samodzielnie ustawić niektóre zmienne konfiguracyjne PHP. Zmiany te są wprowadzane na czas wykonywania programu i dotyczą wyłącznie tego konkretnego programu. W tym procesie przydatne są dwie funkcje:
zwraca wartość zmiennej konfiguracyjnej confVariable | |
ustawia wartość zmiennej konfiguracyjnej confVariable |
Oto przykład, w którym ustala się wartość zmiennej konfiguracyjnej track_errors:
<?php
// wartość zmiennej konfiguracyjnej track_errors
echo "track_errors=".ini_get("track_errors")."\n";
// zmiana tej wartości
ini_set("track_errors","off");
// weryfikacja
echo "track_errors=".ini_get("track_errors")."\n";
?>
Po uruchomieniu otrzymujemy następujące wyniki:
E:\data\serge\web\php\poly\intro>"E:\Program Files\EasyPHP\php\php.exe" conf1.php
Content-type: text/html
track_errors=1
track_errors=off
Wartość zmiennej konfiguracyjnej track_errors wynosiła początkowo 1 (~on). Zmieniono ją na off. Należy pamiętać, że jeśli nasza aplikacja ma opierać się na określonych wartościach zmiennych konfiguracyjnych, rozsądnie jest zainicjować je w samym programie.
3.4. Kontekst wykonywania przykładów
Przykłady zawarte w niniejszym skryptie będą wykonywane przy następującej konfiguracji:
- PC w systemie Windows 2000
- serwer Apache 1.3
- PHP 4.3
Konfiguracja serwera Apache jest zawarta w pliku httpd.conf. Poniższe wiersze nakazują serwerowi Apache załadowanie pliku PHP jako modułu zintegrowanego z serwerem Apache oraz przekazywanie do interpretera PHP wszystkich żądań dotyczących dokumentów o określonych rozszerzeniach, w tym .php. Jest to domyślny rozszerzenie, którego będziemy używać w naszych programach PHP.
LoadModule php4_module "E:/Program Files/EasyPHP/php/php4apache.dll"
AddModule mod_php4.c
AddType application/x-httpd-php .phtml .pwml .php3 .php4 .php .php2 .inc
Ponadto zdefiniowaliśmy dla serwera Apache alias „poly”:
Alias "/poly/" "e:/data/serge/web/php/poly/"
<Directory "e:/data/serge/web/php/poly">
Options Indexes FollowSymLinks Includes
AllowOverride All
#Polecenie allow,deny
Allow from all
</Directory>
Nazwijmy ścieżkę <poly> ścieżką e:/data/serge/web/php/poly. Jeśli chcemy za pomocą przeglądarki zażądać od serwera Apache dokumentu o nazwie doc.php, użyjemy adresu http://localhost/poly/doc.php. Serwer Apache rozpozna w URL alias poly, a następnie powiąże ścieżkę URL /poly/doc.php z dokumentem <poly>\doc.php.
3.5. Pierwszy przykład
Napiszmy pierwszą aplikację internetową w PHP. Poniższy tekst jest zapisany w pliku heure.php:
<html>
<head>
<title>Une page php dynamique</title>
</head>
<body>
<center>
<h1>Une page PHP générée dynamiquement</h1>
<h2>
<?php
$maintenant=time();
echo date("j/m/y, h:i:s",$maintenant);
?>
</h2>
<br>
A chaque fois que vous rafraîchissez la page, l'heure change.
</body>
</html>
Jeśli wywołamy tę stronę w przeglądarce, otrzymamy następujący wynik:

Dynamiczna część strony została wygenerowana przez kod PHP:
Co dokładnie się stało? Przeglądarka wysłała żądanie dotyczące pliku URL http://localhost/poly/intro/heure.php. Serwer WWW (w tym przykładzie Apache) odebrał to żądanie i na podstawie rozszerzenia .php żądanego dokumentu stwierdził, że musi przekazać to żądanie do interpretera PHP. Następnie interpreter ten analizuje dokument heure.php i wykonuje wszystkie fragmenty kodu znajdujące się pomiędzy tagami <?php > i zastępuje każdą z nich wierszami napisanymi przez instrukcje PHP, echo lub print. W ten sposób interpreter PHP wykona powyższy fragment kodu i zastąpi go wierszem utworzonym przez instrukcję echo:
Po wykonaniu wszystkich fragmentów kodu PHP dokument PHP stał się zwykłym dokumentem HTML, który jest następnie wysyłany do klienta.
Należy w miarę możliwości unikać mieszania kodu PHP z kodem HTML. W tym celu poprzednią aplikację można przepisać w następujący sposób:
<!-- kod PHP -->
<?php
// pobieramy aktualny czas
$maintenant=time();
$maintenant=date("j/m/y, h:i:s",$maintenant);
?>
<!-- kod HTML -->
<html>
<head>
<title>Une page php dynamique</title>
</head>
<body>
<center>
<h1>Une page PHP générée dynamiquement</h1>
<h2>
<?php echo $maintenant ?>
</h2>
<br>
A chaque fois que vous rafraîchissez la page, l'heure change.
</body>
</html>
Wynik wyświetlany w przeglądarce jest identyczny:

Druga wersja jest lepsza od pierwszej, ponieważ w kodzie PHP jest mniej kodu niż w kodzie HTML. Dzięki temu struktura strony jest bardziej przejrzysta. Można pójść o krok dalej, umieszczając kod PHP i kod HTML w dwóch różnych plikach. Kod PHP jest zapisany w pliku heure3.php:
<!-- kod PHP -->
<?php
// pobieramy aktualny czas
$maintenant=time();
$maintenant=date("j/m/y, h:i:s",$maintenant);
// wyświetla się odpowiedź
include "heure3-page1.php";
?>
Kod HTML jest natomiast zapisany w pliku heure3-page1.php:
<!-- kod HTML -->
<html>
<head>
<title>Une page php dynamique</title>
</head>
<body>
<center>
<h1>Une page PHP générée dynamiquement</h1>
<h2>
<?php echo $maintenant ?>
</h2>
<br>
A chaque fois que vous rafraîchissez la page, l'heure change.
</body>
</html>
Gdy przeglądarka zażąda dokumentu heure3.php, zostanie on załadowany i przeanalizowany przez interpreter PHP. Po napotkaniu wiersza
Interpreter włączy plik heure3-page1.php do kodu źródłowego pliku heure3.php i wykona go. W ten sposób wszystko przebiega tak, jakbyśmy mieli następujący kod PHP:
<!-- kod PHP -->
<?php
// pobieramy aktualny czas
$maintenant=time();
$maintenant=date("j/m/y, h:i:s",$maintenant);
?>
<!-- kod HTML -->
<html>
<head>
<title>Une page php dynamique</title>
</head>
<body>
<center>
<h1>Une page PHP générée dynamiquement</h1>
<h2>
<?php echo $maintenant ?>
</h2>
<br>
A chaque fois que vous rafraîchissez la page, l'heure change.
</body>
</html>
Uzyskany wynik jest taki sam jak poprzednio:

Rozwiązanie polegające na umieszczeniu kodów PHP i HTML w oddzielnych plikach zostanie przyjęte w dalszej części. Ma ono szereg zalet:
- struktura stron wysyłanych do klienta nie jest zagubiona w kodzie PHP. Dzięki temu mogą być one utrzymywane przez „projektanta stron internetowych” posiadającego umiejętności graficzne, ale niewielką wiedzę na temat PHP.
- Kod PHP pełni rolę „interfejsu” dla żądań klientów. Jego zadaniem jest obliczenie danych niezbędnych do wygenerowania strony, która zostanie zwrócona klientowi w odpowiedzi.
Rozwiązanie to ma jednak wadę: zamiast wymagać załadowania jednego dokumentu, wymaga załadowania kilku dokumentów, co może skutkować spadkiem wydajności.
3.6. Pobieranie parametrów przesłanych przez klienta internetowego
3.6.1. za pomocą POST
Rozważmy następujący formularz, w którym użytkownik musi podać dwie informacje: imię i wiek.

Gdy użytkownik wypełni pola Nom i Age, naciska następnie przycisk Envoyer, który jest typu submit. Wartości z formularza są następnie wysyłane na serwer. Serwer zwraca formularz wraz z tabelą zawierającą listę otrzymanych wartości:

Przeglądarka wysyła żądanie dotyczące formularza do kolejnej aplikacji o nazwie nomage.php:
<?php
// czy mamy oczekiwane parametry?
$post=isset($_POST["txtNom"]) && isset($_POST["txtAge"]);
if($post){
// pobieramy parametry txtNom i txtAge „wysłane” przez klienta
$nom=$_POST["txtNom"];
$age=$_POST["txtAge"];
} else {
$nom="";
$age="";
}//if
// wyświetlenie strony
include "nomage-p1.php";
?>
Kilka wyjaśnień:
- pole formularza o nazwie HTML może zostać przesłane na serwer za pomocą metody GET lub metody POST. Jeśli zostanie przesłane za pomocą metody GET, serwer może je pobrać ze zmiennej $_GET["champ"] oraz ze zmiennej $_POST["champ"], jeślijeśli jest wysyłany metodą POST.
- Istnienie danych można sprawdzić za pomocą funkcji isset(dane), która zwraca true, jeśli dane istnieją, a false w przeciwnym razie.
- Aplikacja nomage.php tworzy trzy zmienne: $nom dla nazwy formularza, $age dla wieku oraz $post, aby wskazać, czy wartości zostały „przesłane”, czy nie. Te trzy zmienne są przekazywane do strony nomage-p1.php. Należy zauważyć, że chociaż strona ta bierze udział w tworzeniu odpowiedzi dla klienta, klient nie ma o tym wiedzy. Z jego punktu widzenia to aplikacja nomage.php udziela mu odpowiedzi.
- Gdy klient po raz pierwszy zwraca się do aplikacji nomage.php, otrzymujemy fałszywy wynik $post. W rzeczywistości podczas tego pierwszego wywołania żadne wartości z formularza nie są przekazywane do serwera.
Strona nomage-p1.php wygląda następująco:
<html>
<head>
<title>Formulaire web</title>
</head>
<body>
<center>
<h3>Un formulaire Web</h3>
<h4>Récupération des valeurs des champs d'un formulaire</h4>
<hr>
<form name="frmPersonne" method="post">
<table>
<tr>
<td>Nom</td>
<td><input type="text" value="<?php echo $nom ?>" name="txtNom" size="20"></td>
<td>Age</td>
<td><input type="text" value="<?php echo $age ?>" name="txtAge" size="3"></td>
<tr>
</table>
<input type="submit" name="cmdEffacer" value="Envoyer">
</form>
</center>
<hr>
<?php
// czy przesłano jakieś wartości?
if ($post) {
?>
<h4>Valeurs récupérées</h4>
<table border="1">
<tr>
<td>Nom</td><td><?php echo $nom ?></td>
<td width="10"></td>
<td>Age</td><td><?php echo $age ?></td>
<tr>
</table>
<?php } ?>
</body>
</html>
Aplikacja nomage-p1.php zawiera formularz frmPersonne. Jest on zdefiniowany przez tag:
Ponieważ atrybut action tego tagu nie jest zdefiniowany, przeglądarka prześle dane formularza do URL, do którego zwróciła się, aby je uzyskać, czyli do c.a.d. aplikacji nomage.php.
Rozróżnijmy dwa przypadki wywołania aplikacji nomage.php:
- Użytkownik wywołuje ją po raz pierwszy. Aplikacja nomage.php wywołuje zatem aplikację nomage-p1.php, przekazując jej wartości ($nom,$age,$post)=("","",false). Aplikacja nomage-p1.php wyświetla wówczas pusty formularz.
- Użytkownik wypełnia formularz i korzysta z przycisku Envoyer (typu submit). Wartości formularza (txtNom, txtAge) są następnie „przesyłane” (method="post" w <form>) do aplikacji nomage.php (atrybut action nie jest zdefiniowany w <form>). Aplikacja nomage.php oblicza ($nom, $age,$post) = (txtNom, txtAge, true) i przekazuje je do aplikacji nomage-p1.php, która następnie wyświetla już wypełniony formularz wraz z tabelą pobranych wartości.
3.6.2. przez GET
W przypadku, gdy wartości z formularza są przesyłane na serwer za pomocą GET, aplikacja nomage.php staje się kolejną aplikacją nomage2.php:
<?php
// czy otrzymano oczekiwane parametry?
$get=isset($_GET["txtNom"]) && isset($_GET["txtAge"]);
if($get){
// klient pobiera parametry txtNom i txtAge „GETTés”
$nom=$_GET["txtNom"];
$age=$_GET["txtAge"];
} else {
$nom="";
$age="";
}//jeśli
// wyświetlenie strony
include "nomage-p2.php";
?>
Aplikacja nomage-p2.php jest identyczna z aplikacją nomage-p1.php, z wyjątkiem następujących szczegółów:
- tag form został zmodyfikowany:
- aplikacja pobiera teraz zmienną $get zamiast $post:
Podczas działania, gdy wartości są wprowadzane w formularzu i wysyłane na serwer, przeglądarka odzwierciedla w polu URL fakt, że wartości zostały wysłane metodą GET:
![]()
3.7. Pobieranie nagłówków HTTP wysłanych przez klienta internetowego
Kiedy przeglądarka wysyła żądanie do serwera internetowego, przekazuje mu pewną liczbę nagłówków HTTP. Czasami warto mieć do nich dostęp. Na początek można skorzystać z tablicy asocjacyjnej $_SERVER. Zawiera on różne informacje przekazane przez serwer internetowy, w tym między innymi nagłówki HTTP dostarczone przez klienta. Rozważmy następujący program, który wyświetla wszystkie wartości tablicy $_SERVER:
<?php
// wyświetla zmienne związane z serwerem WWW
// wysyłanie zwykłego tekstu
header("Content-type: text/plain");
// przeglądanie tablicy asocjacyjnej $_SERVER
reset($_SERVER);
while (list($clé,$valeur)=each($_SERVER)){
echo "$clé : $valeur\n";
}//while
?>
Zapiszmy ten kod w pliku headers.php i wyświetlmy plik URL w przeglądarce:

Otrzymujemy pewną ilość informacji, w tym nagłówki HTTP wysłane przez przeglądarkę. Są to wartości powiązane z kluczami zaczynającymi się od HTTP. Przyjrzyjmy się bliżej niektórym z uzyskanych powyżej informacji:
typy dokumentów akceptowanych przez klienta internetowego | |
typy znaków akceptowanych w dokumentach | |
akceptowane typy kodowań dokumentów | |
akceptowane typy języków w dokumentach | |
typ połączenia z serwerem. Keep-Alive: serwer musi utrzymać otwarte połączenie po wysłaniu odpowiedzi | |
? maksymalny czas trwania otwartego połączenia | |
komputer hostowy, do którego klient wysłał zapytanie | |
tożsamość klienta | |
adres klienta IP | |
port komunikacyjny używany przez klienta | |
protokół HTTP używany przez serwer | |
metoda zapytania używana przez klienta (GET lub POST) | |
żądanie ?param1=val1¶m2=val2&... umieszczone za żądanym URL (metoda GET) |
Nieznacznie modyfikując kod poprzedniego programu, możemy pobrać wyłącznie nagłówki HTTP:
<?php
// wyświetla zmienne związane z serwerem WWW
// wysyłamy zwykły tekst
header("Content-type: text/plain");
// przeglądanie tablicy asocjacyjnej $_SERVER
reset($_SERVER);
while (list($clé,$valeur)=each($_SERVER)){
// nagłówek HTTP?
if(strtolower(substr($clé,0,4))=="http")
echo substr($clé,5)." : $valeur\n";
}//while
?>
W przeglądarce wyświetla się następujący wynik:

Jeśli chcemy uzyskać konkretny nagłówek HTTP, należy wpisać na przykład $_SERVER["HTTP_ACCEPT"].
3.8. Pobieranie informacji o środowisku
Serwer WWW/PHP działa w środowisku, o którym można uzyskać informacje za pomocą tablicy $_ENV, która przechowuje różne cechy środowiska uruchomieniowego. Rozważmy następującą aplikację env1.php:
<?php
// wyświetla zmienne powiązane z serwerem WWW
// wysyłamy zwykły tekst
header("Content-type: text/plain");
// przeglądanie tablicy asocjacyjnej $_ENV
reset($_ENV);
while (list($clé,$valeur)=each($_ENV)){
echo "$clé : $valeur\n";
}//while
?>
W przeglądarce wyświetla się następujący wynik (fragment):

Jak widać na powyższym przykładzie, serwer WWW/PHP działa pod kontrolą systemu Windows OS NT.
3.9. Przykłady
3.9.1. Dynamiczne generowanie formularza – 1
Jako przykład weźmy generowanie formularza zawierającego tylko jeden element sterujący: listę rozwijaną. Zawartość tej listy jest tworzona dynamicznie na podstawie wartości pobranych z tablicy. W rzeczywistości wartości te są często pobierane z bazy danych. Formularz wygląda następująco:

Jeśli w powyższym przykładzie wprowadzimy Envoyer, otrzymamy następującą odpowiedź:

Kod HTML z pierwotnego formularza, po jego wygenerowaniu, wygląda następująco:
<html>
<head>
<title>Génération de formulaire</title>
</head>
<body>
<h2>Choisissez un nombre</h2>
<hr>
<form name="frmvaleurs" method="post" action="valeurs.php">
<select name="cmbValeurs" size="1">
<option>un</option>
<option>deux</option>
<option>trois</option>
<option>quatre</option>
<option>cinq</option>
<option>six</option>
<option>sept</option>
<option>huit</option>
<option>neuf</option>
<option>dix</option>
</select>
<input type="submit" value="Envoyer" name="cmdEnvoyer">
</form>
</body>
</html>
Aplikacja PHP składa się ze strony głównej valeurs.php, która jest wywoływana zarówno w celu pobrania formularza początkowego (listy wartości), jak i przetworzenia zawartych w nim wartości oraz dostarczenia odpowiedzi (wybranej wartości). Aplikacja generuje dwie różne strony:
- stronę z formularzem początkowym, która zostanie wygenerowana przez program valeurs-p1.php
- stronę z odpowiedzią dla użytkownika, która zostanie wygenerowana przez program valeurs-p2.php
Aplikacja valeurs.php wygląda następująco:
<?php
// tablica wartości
$valeurs=array("un","deux","trois","quatre","cinq","six","sept","huit","neuf","dix");
// czy mamy oczekiwane parametry
$requêteVide=! isset($_POST["cmbValeurs"]);
// pobieramy wybór użytkownika
if ($requêteVide){
// pierwotne zapytanie
include "valeurs-p1.php";
}else{
// odpowiedź na POST
$choix=$_POST["cmbValeurs"];
include "valeurs-p2.php";
}
?>
Definiuje on tabelę wartości i wywołuje program valeurs-p1.php w celu wygenerowania formularza początkowego, jeśli zapytanie klienta było puste, lub program valeurs-p2.php w celu wygenerowania odpowiedzi, jeśli zapytanie było prawidłowe. Program valeurs1-php wygląda następująco:
<html>
<head>
<title>Génération de formulaire</title>
</head>
<body>
<h2>Choisissez un nombre</h2>
<hr>
<form name="frmvaleurs" method="post" action="valeurs.php">
<select name="cmbValeurs" size="1">
<?php
for($i=0;$i<count($valeurs);$i++){
echo "<option>$valeurs[$i]</option>\n";
}//dla
?>
</select>
<input type="submit" value="Envoyer" name="cmdEnvoyer">
</form>
</body>
</html>
Lista wartości z listy rozwijanej jest generowana dynamicznie na podstawie tablicy $valeurs przesłanej przez valeurs.php. Program valeurs-p2.php generuje odpowiedź:
<html>
<head>
<title>réponse</title>
</head>
<body>
<h2>Vous avez choisi le nombre <?php echo $choix ?></h2>
</body>
</html>
W tym miejscu wyświetlana jest jedynie wartość zmiennej $choix, przekazanej również przez valeurs.php.
3.9.2. Dynamiczne generowanie formularza – 2
Wracamy do poprzedniego przykładu, modyfikując go w następujący sposób. Proponowany formularz pozostaje ten sam:

Czy odpowiedź jest inna:

W odpowiedzi zwracany jest formularz, a wybrana przez użytkownika liczba jest podana poniżej. Ponadto liczba ta jest tą, która pojawia się jako wybrana na liście wyświetlanej w odpowiedzi.
Kod dla valeurs.php jest następujący:
<?php
// konfiguracja
ini_set("register_globals","off");
// tabela wartości
$valeurs=array("un","deux","trois","quatre","cinq","six","sept","huit","neuf","dix");
// pobieramy ewentualny wybór użytkownika
$choix=$_POST["cmbValeurs"];
// wyświetla się odpowiedź
include "valeurs-p1.php";
?>
Warto zauważyć, że w tym przypadku zadbano o skonfigurowanie PHP tak, aby nie zawierało zmiennych globalnych. Jest to zazwyczaj rozsądny środek ostrożności, ponieważ zmienne globalne stwarzają problemy związane z bezpieczeństwem. Alternatywą jest zainicjowanie wszystkich używanych zmiennych. Spowoduje to „nadpisanie” ewentualnej zmiennej globalnej o tej samej nazwie.
Strona formularza jest wyświetlana przez valeurs-p1.php:
<html>
<head>
<title>Génération de formulaire</title>
</head>
<body>
<h2>Choisissez un nombre</h2>
<hr>
<form name="frmvaleurs" method="post" action="valeurs.php">
<select name="cmbValeurs" size="1">
<?php
for($i=0;$i<count($valeurs);$i++){
// jeśli bieżąca opcja jest zgodna z wyborem, wybieramy ją
if (isset($choix) && $choix==$valeurs[$i])
echo "<option selected>$valeurs[$i]</option>\n";
else echo "<option>$valeurs[$i]</option>\n";
}//for
?>
</select>
<input type="submit" value="Envoyer" name="cmdEnvoyer">
</form>
<?php
// kontynuacja strony
if(isset($choix)){
echo "<hr>\n";
echo "<h3>Vous avez choisi le nombre $choix</h3>\n";
}
?>
</body>
</html>
Program generujący stronę opiera się na zmiennej $choix przekazanej przez program valeurs.php. Warto tutaj zauważyć, że struktura strony HTML zaczyna być poważnie „zanieczyszczona” kodem PHP. Interfejs użytkownika valeurs.php mógłby wykonać więcej pracy, jak pokazuje poniższa nowa wersja:
<?php
// konfiguracja
ini_set("register_globals","off");
// tabela wartości
$valeurs=array("un","deux","trois","quatre","cinq","six","sept","huit","neuf","dix");
// pobieramy ewentualny wybór użytkownika
$choix=$_POST["cmbValeurs"];
// obliczamy listę wartości do wyświetlenia
$HTMLvaleurs="";
for($i=0;$i<count($valeurs);$i++){
// jeśli bieżąca opcja jest równa wyborowi użytkownika, wybieramy ją
if (isset($choix) && $choix==$valeurs[$i])
$HTMLvaleurs.="<option selected>$valeurs[$i]</option>\n";
else $HTMLvaleurs.="<option>$valeurs[$i]</option>\n";
}//for
// obliczamy drugą część strony
$HTMLpart2="";
if(isset($choix)){
$HTMLpart2="<hr>\n";
$HTMLpart2.="<h3>Vous avez choisi le nombre $choix</h3>\n";
}//if
// wyświetla się odpowiedź
include "valeurs-p2.php";
?>
Strona została wygenerowana przez program valeurs-p2.php w następujący sposób:
<html>
<head>
<title>Génération de formulaire</title>
</head>
<body>
<h2>Choisissez un nombre</h2>
<hr>
<form name="frmvaleurs" method="post" action="valeurs.php">
<select name="cmbValeurs" size="1">
<?php
// wyświetlanie listy wartości
echo $HTMLvaleurs;
?>
</select>
<input type="submit" value="Envoyer" name="cmdEnvoyer">
</form>
<?php
// wyświetlanie części 2
echo $HTMLpart2;
?>
</body>
</html>
Kod HTML został teraz w znacznej mierze oczyszczony z kodu PHP. Przypomnijmy jednak cel podziału na program front-end, który analizuje i przetwarza zapytanie klienta, oraz programy odpowiedzialne wyłącznie za wyświetlanie stron skonfigurowanych na podstawie danych przekazanych przez front-end: chodzi o oddzielenie pracy programisty PHP od pracy grafika. Programista PHP pracuje nad interfejsem użytkownika, a grafik nad stronami internetowymi. W naszej nowej wersji grafik nie może już na przykład pracować nad częścią 2 strony, ponieważ nie ma już dostępu do jej kodu HTML. W pierwszej wersji miał taką możliwość. Żadna z tych dwóch metod nie jest więc idealna.
3.9.3. Dynamiczne generowanie formularza – 3
Powracamy do tego samego problemu, co poprzednio, ale tym razem wartości pobierane są z bazy danych. W naszym przykładzie jest to baza o nazwie MySQL:
- baza nosi nazwę dbValeurs
- jej właścicielem jest admDbValeurs, a hasło to mdpDbValeurs
- baza zawiera jedną tabelę o nazwie tvaleurs
- tabela ta zawiera tylko jedno pole typu całkowitego o nazwie „valeur”
dos> mysql --database=dbValeurs --user=admDbValeurs --password=mdpDbValeurs
mysql> show tables;
+---------------------+
| Tables_in_dbValeurs |
+---------------------+
| tvaleurs |
+---------------------+
1 row in set (0.00 sec)
mysql> describe tvaleurs;
+--------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+---------+------+-----+---------+-------+
| valeur | int(11) | | | 0 | |
+--------+---------+------+-----+---------+-------+
mysql> select * from tvaleurs;
+--------+
| valeur |
+--------+
| 0 |
| 1 |
| 2 |
| 3 |
| 4 |
| 6 |
| 5 |
| 7 |
| 8 |
| 9 |
+--------+
10 rows in set (0.00 sec)
W aplikacji korzystającej z bazy danych zazwyczaj występują następujące etapy:
- Połączenie z SGBD
- Wysyłanie zapytań SQL do bazy danych SGBD
- Przetwarzanie wyników tych zapytań
- Zamknięcie połączenia z SGBD
Kroki 2 i 3 są wykonywane wielokrotnie, a zamknięcie połączenia następuje dopiero po zakończeniu pracy z bazą danych. Jest to schemat stosunkowo typowy dla każdego, kto kiedykolwiek korzystał z bazy danych w trybie interaktywnym. Poniższa tabela zawiera instrukcje PHP dotyczące wykonywania tych różnych operacji za pomocą SGBD i MySQL:
$connexion=mysql_pconnect($hote,$user,$pwd) $connexion=mysql_connect($hote,$user,$pwd) $hote: nazwa internetowa komputera, na którym uruchomione są programy SGBD i MySQL. W rzeczywistości możliwe jest korzystanie z programów SGBD i MySQL zdalnie. $user: nazwa znanego użytkownika programu SGBD MySQL $pwd: jego hasło $connexion: utworzone połączenie mysql_pconnect tworzy trwałe połączenie z SGBD i MySQL. Takie połączenie nie jest zamykane po zakończeniu działania skryptu. Pozostaje otwarte. Dzięki temu, gdy zajdzie potrzeba otwarcia nowego połączenia z SGBD, PHP wyszuka istniejące połączenie należące do tego samego użytkownika. Jeśli je znajdzie, wykorzysta je. Pozwala to zaoszczędzić czas. mysql_connect tworzy połączenie nietrwałe, które jest zamykane po zakończeniu pracy z SGBD i MySQL. | |
$résultats=mysql_db_query($base,$requête,$connexion) $base: baza danych MySQL, z którą będziemy pracować $requête: zapytanie dotyczące SQL (insert, delete, update, select, ...) $connexion: połączenie z SGBD MySQL $résultats: wyniki zapytania – różnią się w zależności od tego, czy zapytanie jest zapytaniem typu select, czy operacją aktualizacji (insert, update, delete, ...) | |
$résultats=mysql_db_query($base, „select …”, $connexion) Wynikiem zapytania SELECT jest tabela, a więc zbiór wierszy i kolumn. Dostęp do tej tabeli można uzyskać za pomocą $résultats. $ligne=mysql_fetch_row($résultats) odczytuje wiersz z tabeli i umieszcza go w $ligne w postaci tablicy. Zatem $ligne[i] stanowi kolumnę i pobranego wiersza. Funkcję mysql_fetch_row można wywoływać wielokrotnie. Za każdym razem odczytuje ona nowy wiersz z tabeli $résultats. Po osiągnięciu końca tabeli funkcja zwraca wartość false. W ten sposób tabela $résultats może być wykorzystywana w następujący sposób: while($wiersz = mysql_fetch_row($wyniki)) { // przetwarza bieżący wiersz $wiersz }//while | |
$résultats=mysql_db_query($base, „wstaw ...”, $connexion) Wartość $résultats jest prawdziwa lub fałszywa w zależności od tego, czy operacja zakończyła się powodzeniem, czy nie. W przypadku powodzenia funkcja mysql_affected_rows pozwala ustalić liczbę wierszy zmodyfikowanych w wyniku operacji aktualizacji. | |
mysql_close($połączenie) $connexion: połączenie z SGBD MySQL |
Kod interfejsu użytkownika valeurs.php przyjmuje następującą postać:
<?php
// konfiguracja
ini_set("register_globals","off");
ini_set("display_errors","off");
ini_set("track_errors","on");
// tabela wartości
list($erreur,$valeurs)=getValeurs();
// czy wystąpił błąd?
if($erreur){
// wyświetlanie strony błędu
include "valeurs-err.php";
// koniec
return;
}//if
// pobieramy ewentualny wybór użytkownika
$choix=$_POST["cmbValeurs"];
// obliczamy listę wartości do wyświetlenia
$HTMLvaleurs="";
for($i=0;$i<count($valeurs);$i++){
// jeśli bieżąca opcja jest równa wyborowi, wybieramy ją
if (isset($choix) && $choix==$valeurs[$i])
$HTMLvaleurs.="<option selected>$valeurs[$i]</option>\n";
else $HTMLvaleurs.="<option>$valeurs[$i]</option>\n";
}//for
// obliczamy drugą część strony
$HTMLpart2="";
if(isset($choix)){
$HTMLpart2="<hr>\n";
$HTMLpart2.="<h3>Vous avez choisi le nombre $choix</h3>\n";
}//if
// wyświetla się odpowiedź
include "valeurs-p1.php";
// koniec
return;
// ------------------------------------------------------------------------
function getValeurs(){
// pobiera wartości z bazy danych MySQL
$user="admDbValeurs";
$pwd="mdpDbValeurs";
$db="dbValeurs";
$hote="localhost";
$table="tvaleurs";
$champ="valeur";
// nawiązanie trwałego połączenia z serwerem MySQL
// lub w przeciwnym razie nawiązanie zwykłego połączenia
($connexion=mysql_pconnect($hote,$user,$pwd))
|| ($connexion=mysql_connect($hote,$user,$pwd));
if(! $connexion)
return array("Base de données indisponible(".mysql_error()."). Veuillez recommencer ultérieurement.");
// pobieranie wartości
$selectValeurs=mysql_db_query($db,"select $champ from $table",$connexion);
if(! $selectValeurs)
return array("Base de données indisponible(".mysql_error()."). Veuillez recommencer ultérieurement.");
// wartości są umieszczane w tablicy
$valeurs=array();
while($ligne=mysql_fetch_row($selectValeurs)){
$valeurs[]=$ligne[0];
}//while
// zamknięcie połączenia (jeśli jest trwałe, w rzeczywistości nie zostanie zamknięte)
mysql_close($connexion);
// zwrot wyniku
return array("",$valeurs);
}//getValeurs
?>
Tym razem wartości, które należy umieścić w polu kombi, nie są dostarczane przez tablicę, lecz przez funkcję getValeurs(). Funkcja ta:
- otwiera trwałe połączenie (mysql_pconnect) z serwerem mySQL, przekazując zarejestrowaną nazwę użytkownika i hasło.
- Po nawiązaniu połączenia wysyłane jest zapytanie select w celu pobrania wartości znajdujących się w tabeli tvaleurs z bazy danych dbValeurs.
- Wynik zapytania select jest umieszczany w tablicy $valeurs, która jest zwracana do programu wywołującego.
- Funkcja zwraca w rzeczywistości tablicę zawierającą dwa wyniki ($erreur, $valeurs), gdzie pierwszy element to ewentualny komunikat o błędzie lub, w przeciwnym razie, pusty ciąg znaków.
- Program wywołujący sprawdza, czy wystąpił błąd, a jeśli tak, wyświetla stronę valeurs-err.php. Wygląda ona następująco:
<html>
<head>
<title>Erreur</title>
</head>
<body>
<h3>L'erreur suivante s'est produite</h3>
<font color="red">
<h4><?php echo $erreur ?></h4>
</font>
</body>
</html>
- jeśli nie wystąpił błąd, program wywołujący ma do dyspozycji wartości w tablicy $valeurs. Wracamy więc do poprzedniego problemu.
Oto dwa przykłady wykonania:
- z błędem

- bez błędu

3.9.4. Dynamiczne generowanie formularza – 4
Co by się stało w poprzednim przykładzie, gdybyśmy zmienili SGBD? Na przykład, gdybyśmy przeszli z MySQL na Oracle lub SQL Server? Konieczne byłoby przepisanie funkcji getValeurs(), która dostarcza wartości. Zaletą zebrania kodu niezbędnego do pobrania wartości, które mają być wyświetlone na liście, w jednej funkcji jest to, że kod wymagający modyfikacji jest ściśle określony i nie jest rozproszony po całym programie. Funkcję getValeurs() można przepisać tak, aby była niezależna od używanego modułu SGBD. Wystarczy, aby współpracowała ze sterownikiem ODBC z pakietu SGBD, a nie bezpośrednio z SGBD.
Na rynku dostępnych jest wiele baz danych. Aby ujednolicić dostęp do baz danych w systemie Windows, firma Microsoft opracowała interfejs o nazwie ODBC (Open DataBase Connectivity). Warstwa ta ukrywa specyfikę poszczególnych baz danych pod standardowym interfejsem. W systemie Windows dostępnych jest wiele sterowników ułatwiających dostęp do baz danych. Oto przykładowa lista sterowników zainstalowanych na komputerze z systemem Windows:

Również SGBD i MySQL mają swoje sterowniki ODBC. Aplikacja oparta na sterownikach ODBC może korzystać z dowolnej z powyższych baz danych bez konieczności przepisywania kodu.
![]() |
Udostępnijmy naszą bazę danych MySQL i dbValeurs za pośrednictwem sterownika ODBC. Poniższa procedura dotyczy systemu Windows 2000. W przypadku systemów Win9x procedura jest bardzo podobna. Uruchamiamy menedżera zasobów ODBC:

Za pomocą przycisku [Add] dodajemy nowe źródło danych ODBC:

Wybieramy sterownik ODBC MySQL, wykonujemy [Terminer], a następnie podajemy parametry źródła danych:

nazwa nadana źródłu danych ODBC (odbc-wartości) | |
nazwa komputera, na którym znajduje się SGBD MySQL, zarządzającego źródłem danych (localhost) | |
nazwa bazy danych MySQL, która stanowi źródło danych (dbValeurs) | |
użytkownik posiadający wystarczające uprawnienia dostępu do bazy danych MySQL, którą ma zarządzać (admDbValeurs) | |
swoje hasło (mdpDbValeurs) |
PHP może współpracować ze sterownikami ODBC. Poniższa tabela przedstawia funkcje, o których warto wiedzieć:
$connexion = odbc_pconnect($dsn, $user, $pwd) $connexion=odbc_connect($dsn,$user,$pwd) $dsn: nazwa DSN (Data Source Name) komputera, na którym uruchomiony jest proces SGBD $user: nazwa użytkownika znanego programowi SGBD $pwd: jego hasło $connexion: utworzone połączenie odbc_pconnect tworzy trwałe połączenie z SGBD. Takie połączenie nie jest zamykane po zakończeniu działania skryptu. Pozostaje otwarte. Dzięki temu, gdy zajdzie potrzeba otwarcia nowego połączenia z SGBD, PHP wyszuka istniejące połączenie należące do tego samego użytkownika. Jeśli je znajdzie, wykorzysta je. Pozwala to zaoszczędzić czas. odbc_connect tworzy połączenie nietrwałe, które jest zamykane po zakończeniu pracy z SGBD. | |
$requêtePréparée=odbc_prepare($connexion,$requête) $requête: zapytanie SQL (wstaw, usuń, zaktualizuj, wybierz, ...) $connexion: połączenie z SGBD Analizuje zapytanie $requête i przygotowuje je do wykonania. Tak „przygotowane” zapytanie jest odwołane przez wynik $requêtePréparée. Przygotowanie zapytania do wykonania nie jest obowiązkowe, ale poprawia wydajność, ponieważ analiza zapytania odbywa się tylko raz. Następnie zleca się wykonanie przygotowanego zapytania. Jeśli zleca się wielokrotne wykonanie nieprzygotowanego zapytania, jego analiza jest przeprowadzana za każdym razem, co jest zbędne. Po przygotowaniu zapytanie jest wykonywane przez $res=odbc_execute($requêtePréparée) zwraca wartość true lub false w zależności od tego, czy wykonanie zapytania zakończyło się powodzeniem, czy nie | |
Wynikiem zapytania SELECT jest tabela, a więc zbiór wierszy i kolumn. Dostęp do tej tabeli można uzyskać za pomocą $requêtePréparée. $res=odbc_fetch_row($requêtePréparée) odczytuje wiersz z tabeli wynikowej funkcji select. Zwraca wartość true lub false w zależności od tego, czy wykonanie zapytania zakończyło się powodzeniem, czy nie. Elementy pobranego wiersza są dostępne za pośrednictwem funkcji odbc_result: $val=odbc_result($requêtePréparée,i): kolumna i z właśnie odczytanej linii $val=odbc_result($requêtePréparée,"nomColonne”): kolumna nomColonne z właśnie odczytanej linii Funkcję odbc_fetch_row można wywoływać wielokrotnie. Za każdym razem odczytuje ona nowy wiersz z tabeli wyników. Po osiągnięciu końca tabeli funkcja zwraca wartość false. Dzięki temu z tabeli wyników można korzystać w następujący sposób: | |
odbc_close($połączenie) $connexion: połączenie z SGBD MySQL |
Funkcja getValeurs(), odpowiedzialna za pobieranie wartości z bazy danych ODBC, wygląda następująco:
// ------------------------------------------------------------------------
function getValeurs(){
// pobiera wartości z bazy danych MySQL
$user="admDbValeurs";
$pwd="mdpDbValeurs";
$db="dbValeurs";
$dsn="odbc-valeurs";
$table="tvaleurs";
$champ="valeur";
// otwarcie trwałego połączenia z serwerem MySQL
// lub w przeciwnym razie nawiązanie zwykłego połączenia
($connexion=odbc_pconnect($dsn,$user,$pwd))
|| ($connexion=odbc_connect($dsn,$user,$pwd));
if(! $connexion)
return array("1 - Base de données indisponible(".odbc_error()."). Veuillez recommencer ultérieurement.");
// pobieranie wartości
$selectValeurs=odbc_prepare($connexion,"select $champ from $table");
if(! odbc_execute($selectValeurs))
return array("2 - Base de données indisponible(".odbc_error()."). Veuillez recommencer ultérieurement.");
// wartości są umieszczane w tablicy
$valeurs=array();
while(odbc_fetch_row($selectValeurs)){
$valeurs[]=odbc_result($selectValeurs,$champ);
}//while
// zamknięcie połączenia (jeśli jest trwałe, w rzeczywistości nie zostanie zamknięte)
odbc_close($connexion);
// zwrot wyniku
return array("",$valeurs);
}//getValeurs
?>
Jeśli uruchomimy nową aplikację bez aktywacji bazy danych odbc-valeurs, otrzymamy następujący wynik:

Warto zauważyć, że kod błędu zwracany przez sterownik ODBC (odbc_error()=S1000) nie jest zbyt jednoznaczny. Jeśli baza danych odbc-valeurs zostanie udostępniona, otrzymujemy te same wyniki, co poprzednio.
Podsumowując, można stwierdzić, że rozwiązanie to jest korzystne z punktu widzenia konserwacji aplikacji. Jeśli bowiem baza danych ulegnie zmianie, aplikacja nie będzie wymagała modyfikacji. Administrator systemu po prostu utworzy nowe źródło danych o nazwie ODBC dla nowej bazy. Również z myślą o konserwacji dobrze byłoby ustawić parametry dostępu do bazy ($dsn, $user, $pwd) w oddzielnym pliku, który aplikacja ładowałaby przy uruchomieniu (include).
3.9.5. Pobieranie wartości z formularza
Już kilkakrotnie pobieraliśmy wartości z formularza przesłanego przez klienta internetowego. Poniższy przykład przedstawia formularz zawierający najczęściej spotykane komponenty HTML i służy do pobierania parametrów przesłanych przez przeglądarkę klienta. Formularz wygląda następująco:

Formularz jest wstępnie wypełniony. Następnie użytkownik może wprowadzić w nim zmiany:

Jeśli kliknie przycisk [Envoyer], serwer zwróci mu listę wartości z formularza:

Formularz jest statyczną stroną HTML balises.html:
<html>
<head>
<title>balises</title>
<script language="JavaScript">
function effacer(){
alert("Vous avez cliqué sur le bouton Effacer");
}//usunięcie
</script>
</head>
<body background="/images/standard.jpg">
<form method="POST" action="parameters.php">
<table border="0">
<tr>
<td>Etes-vous marié(e)</td>
<td>
<input type="radio" value="oui" name="R1">Oui
<input type="radio" name="R1" value="non" checked>Non
</td>
</tr>
<tr>
<td>Cases à cocher</td>
<td>
<input type="checkbox" name="C1" value="un">1
<input type="checkbox" name="C2" value="deux" checked>2
<input type="checkbox" name="C3" value="trois">3
</td>
</tr>
<tr>
<td>Champ de saisie</td>
<td>
<input type="text" name="txtSaisie" size="20" value="qqs mots">
</td>
</tr>
<tr>
<td>Mot de passe</td>
<td>
<input type="password" name="txtMdp" size="20" value="unMotDePasse">
</td>
</tr>
<tr>
<td>Boîte de saisie</td>
<td>
<textarea rows="2" name="areaSaisie" cols="20">
ligne1
ligne2
ligne3
</textarea>
</td>
</tr>
<tr>
<td>combo</td>
<td>
<select size="1" name="cmbValeurs">
<option>choix1</option>
<option selected>choix2</option>
<option>choix3</option>
</select>
</td>
</tr>
<tr>
<td>liste à choix simple</td>
<td>
<select size="3" name="lst1">
<option selected>liste1</option>
<option>liste2</option>
<option>liste3</option>
<option>liste4</option>
<option>liste5</option>
</select>
</td>
</tr>
<tr>
<td>liste à choix multiple</td>
<td>
<select size="3" name="lst2[]" multiple>
<option selected>liste1</option>
<option>liste2</option>
<option selected>liste3</option>
<option>liste4</option>
<option>liste5</option>
</select>
</td>
</tr>
<tr>
<td>bouton</td>
<td>
<input type="button" value="Effacer" name="cmdEffacer" onclick="effacer()">
</td>
</tr>
<tr>
<td>envoyer</td>
<td>
<input type="submit" value="Envoyer" name="cmdRenvoyer">
</td>
</tr>
<tr>
<td>rétablir</td>
<td>
<input type="reset" value="Rétablir" name="cmdRétablir">
</td>
</tr>
</table>
<input type="hidden" name="secret" value="uneValeur">
</form>
</body>
</html>
Poniższa tabela przedstawia rolę poszczególnych znaczników w tym dokumencie oraz wartość pobieraną przez PHP dla różnych typów elementów formularza. Wartość pola o nazwie HTML C może zostać przesłana przez POST lub GET. W pierwszym przypadku zostanie ona pobrana do zmiennej $_GET["C"], a w drugim przypadku do zmiennej $_POST["C"]. Poniższa tabela zakłada użycie zmiennej POST.
Kontrola | znacznika HTML | wartość pobrana przez PHP |
<form method="POST" > | ||
<input type="text" name="txtSaisie" size="20" value="kilka słów"> | $_POST["txtSaisie"]: wartość zawarta w polu txtSaisie formularza | |
<input type="password" name="txtMdp" size="20" value="unMotDePasse"> | $_POST["txtmdp"]: wartość zawarta w polu txtMdp formularza | |
<textarea rows="2" name="areaSaisie" cols="20"> wiersz1 wiersz 2 wiersz 3 </textarea> | $_POST["areaSaisie"]: wiersze zawarte w polu areaSaisie w postaci pojedynczego ciągu znaków: "ligne1\r\nligne2\r\nligne3". Wiersze są oddzielone od siebie sekwencją „\r\n”. | |
<input type="radio" value="Tak" name="R1">Tak <input type="radio" name="R1" value="nie" checked>Nie | $_POST["R1"]: wartość (=value) przycisku opcji zaznaczonego jako „tak” lub „nie”, w zależności od sytuacji. | |
<input type="checkbox" name="C1" value="jeden">1 <input type="checkbox" name="C2" value="dwa" checked>2 <input type="checkbox" name="C3" value="trzy">3 | $_POST["C1"]: wartość (=value) pola wyboru, jeśli jest zaznaczone; w przeciwnym razie zmienna nie istnieje. Zatem jeśli zaznaczono pole wyboru C1, $_POST["C1"] ma wartość „jeden”, w przeciwnym razie $_POST["C1"] nie istnieje. | |
<select size="1" name="cmbValeurs"> <option>opcja1</option> <option selected>opcja2</option> <option>opcja3</option> </select> | $_POST["cmbValeurs"]: opcja wybrana z listy, na przykład „opcja3”. | |
<select size="3" name="lst1"> <option selected>lista1</option> <option>lista2</option> <option>lista3</option> <option>lista4</option> <option>lista5</option> </select> | $_POST["lst1"]: opcja wybrana z listy, na przykład „liste5”. | |
<select size="3" name="lst2[]" multiple> <option>lista1</option> <option>lista2</option> <option selected>lista3</option> <option>lista4</option> <option>lista5</option> </select> | $_POST["lst2"]: tabela opcji wybranych z listy, na przykład ["liste3,"liste5"]. Należy zwrócić uwagę na szczególną składnię tagu HTML w tym konkretnym przypadku: lst2[]. | |
<input type="hidden" name="secret" value="uneValeur"> | $_POST["secret"]: wartość (=value) pola, w tym przypadku „uneValeur”. |
W naszym przykładzie wartości z formularza są przekazywane do programu parameters.php:
Kod tego ostatniego jest następujący:
<?php
// konfiguracja
ini_set("register_globals","off");
ini_set("display_errors","off");
// metoda wywołania
$méthode=$_SERVER["REQUEST_METHOD"];
// pobieranie parametrów
// zależy to od metody ich wysyłania
if($méthode=="GET")
$param=$_GET;
else $param=$_POST;
$R1=$param["R1"];
$C1=$param["C1"];
$C2=$param["C2"];
$C3=$param["C3"];
$txtSaisie=$param["txtSaisie"];
$txtMdp=$param["txtMdp"];
$areaSaisie=implode("<br>",explode("\r\n",$param["areaSaisie"]));
$cmbValeurs=$param["cmbValeurs"];
$lst1=$param["lst1"];
$lst2=implode("<br>",$param["lst2"]);
$secret=$param["secret"];
// czy żądanie jest prawidłowe?
$requêteValide=isset($R1) && (isset($C1) || isset($C2) || isset($C3))
&& isset($txtSaisie) && isset($txtMdp) && isset($areaSaisie)
&& isset($cmbValeurs) && isset($lst1) && isset($lst2)
&& isset($secret);
// wyświetlanie strony
if ($requêteValide)
include "parameters-p1.php";
else include "balises.html";
?>
Omówmy kilka aspektów tego programu:
- aplikacja nie zajmuje się sposobem przekazywania wartości z formularza do serwera. W obu możliwych przypadkach (GET i POST) słownik przekazywanych wartości jest określany przez $param.
- na podstawie zawartości pola areaSaisie „wiersz1\r\nwiersz2\r\n...” tworzy się tablicę ciągów znaków za pomocą funkcji explode("\r\n", $param["areaSaisie"]). Otrzymujemy zatem tablicę [ligne1,ligne2,...]. Na jej podstawie tworzymy ciąg znaków "ligne1<br>ligne2<br>..." za pomocą funkcji implode.
- Wartość listy wielokrotnego wyboru lst2 jest tablicą, na przykład ["option3","option5"]. Na jej podstawie tworzy się ciąg znaków „option3<br>option5” za pomocą funkcji implode.
- Aplikacja sprawdza, czy wszystkie parametry zostały ustawione. Należy pamiętać, że dowolna funkcja URL może zostać wywołana ręcznie lub programowo i niekoniecznie są dostępne oczekiwane parametry. Jeśli brakuje parametrów, wyświetlana jest strona balises.html, w przeciwnym razie strona parameters-p1.php. Ta ostatnia wyświetla wartości pobrane i obliczone w funkcji parameters.php w tabeli:
<html>
<head>
<title>Récupération des paramètres d'un formulaire</title>
</head>
<body>
<table border="1">
<tr>
<td>R1</td>
<td><?php echo $R1 ?></td>
</tr>
<tr>
<td>C1</td>
<td><?php echo $C1 ?></td>
</tr>
<tr>
<td>C2</td>
<td><?php echo $C2 ?></td>
</tr>
<tr>
<td>C3</td>
<td><?php echo $C3 ?></td>
</tr>
<tr>
<td>txtSaisie</td>
<td><?php echo $txtSaisie ?></td>
</tr>
<tr>
<td>txtMdp</td>
<td><?php echo $txtMdp ?></td>
</tr>
<tr>
<td>areaSaisie</td>
<td><?php echo $areaSaisie ?></td>
</tr>
<tr>
<td>cmbValeurs</td>
<td><?php echo $cmbValeurs ?></td>
</tr>
<tr>
<td>lst1</td>
<td><?php echo $lst1 ?></td>
</tr>
<tr>
<td>lst2</td>
<td><?php echo $lst2 ?></td>
</tr>
<tr>
<td>secret</td>
<td><?php echo $secret ?></td>
</tr>
</table>
</body>
</html>
3.10. Śledzenie sesji
3.10.1. Problem
Aplikacja internetowa może składać się z kilku wymian formularzy między serwerem a klientem. Działa to w następujący sposób:
krok 1
- klient C1 nawiązuje połączenie z serwerem i wysyła swoje pierwsze żądanie.
- serwer wysyła formularz F1 do klienta C1 i zamyka połączenie nawiązane w kroku 1.
Krok 2
- Klient C1 wypełnia formularz i odsyła go do serwera. W tym celu przeglądarka nawiązuje nowe połączenie z serwerem.
- Serwer przetwarza dane z formularza 1, oblicza na ich podstawie informacje I1, wysyła formularz F2 do klienta C1 i zamyka połączenie otwarte w kroku 3.
Krok 3
- Cykl etapów 3 i 4 powtarza się w etapach 5 i 6. Po zakończeniu etapu 6 serwer otrzyma dwa formularze F1 i F2 i na ich podstawie obliczy informacje I1 oraz I2.
Pojawia się następujący problem: w jaki sposób serwer zachowuje informacje I1 i I2 powiązane z klientem C1? Problem ten nazywamy śledzeniem sesji klienta C1. Aby zrozumieć jego źródło, przyjrzyjmy się schematowi aplikacji serwerowej TCP-IP obsługującej jednocześnie wielu klientów:
![]() |
W klasycznej aplikacji klient-serwer TCP-IP:
- klient nawiązuje połączenie z serwerem
- przez to połączenie wymienia dane z serwerem
- połączenie zostaje zamknięte przez jedną ze stron
Dwa istotne elementy tego mechanizmu to:
- dla każdego klienta tworzone jest jedno połączenie
- połączenie to jest wykorzystywane przez cały czas trwania komunikacji serwera z klientem
To, co pozwala serwerowi w danym momencie rozpoznać, z którym klientem współpracuje, to połączenie, czyli inaczej mówiąc „kanał”, który łączy go z klientem. Ponieważ kanał ten jest przypisany do konkretnego klienta, wszystko, co do niego dociera, pochodzi od tego klienta, a wszystko, co jest wysyłane tym kanałem, dociera do tego samego klienta.
Mechanizm klient-serwer HTTP działa zgodnie z powyższym schematem, z tą jednak różnicą, że komunikacja klient-serwer ogranicza się do pojedynczej wymiany danych między klientem a serwerem:
- klient nawiązuje połączenie z serwerem i wysyła żądanie
- serwer udziela odpowiedzi i zamyka połączenie
Jeśli w momencie T1 klient C wysyła żądanie do serwera, otrzymuje połączenie C1, które posłuży do jednorazowej wymiany żądania i odpowiedzi. Jeśli w momencie T2 ten sam klient wyśle drugie żądanie do serwera, otrzyma połączenie C2, które różni się od połączenia C1. Z punktu widzenia serwera nie ma zatem żadnej różnicy między tym drugim żądaniem użytkownika C a jego żądaniem początkowym: w obu przypadkach serwer traktuje klienta jako nowego klienta. Aby istniał związek między różnymi połączeniami klienta C z serwerem, klient C musi zostać „rozpoznany” przez serwer jako „stały użytkownik”, a serwer musi pobrać informacje, które posiada na temat tego stałego użytkownika.
Wyobraźmy sobie system zarządzania, który działałby w następujący sposób:
- Istnieje jedna kolejka
- Istnieje kilka okienek. Dzięki temu kilku klientów może być obsługiwanych jednocześnie. Gdy okienko się zwalnia, klient opuszcza kolejkę, aby zostać obsłużonym przy tym okienku
- Jeśli klient pojawia się po raz pierwszy, osoba przy okienku wręcza mu żeton z numerem. Klient może zadać tylko jedno pytanie. Po uzyskaniu odpowiedzi musi opuścić okienko i przejść na koniec kolejki. Pracownik okienka zapisuje informacje o tym kliencie w teczce oznaczonej jego numerem.
- Kiedy znów nadejdzie jego kolej, klient może zostać obsłużony przez innego pracownika niż poprzednio. Ten prosi go o żeton i pobiera kartotekę o numerze odpowiadającym numerowi żetonu. Klient ponownie zgłasza prośbę, otrzymuje odpowiedź, a informacje są dodawane do jego kartoteki.
- i tak dalej... Z biegiem czasu klient otrzyma odpowiedzi na wszystkie swoje zapytania. Śledzenie kolejnych zapytań odbywa się za pomocą żetonu i powiązanej z nim teczki.
Mechanizm śledzenia sesji w aplikacji internetowej typu klient-serwer działa analogicznie do powyższego przykładu:
- przy pierwszym żądaniu klient otrzymuje token od serwera internetowego
- będzie on przedstawiał ten token przy każdym kolejnym żądaniu w celu identyfikacji
Token może przybierać różne formy:
- pola ukrytego w formularzu
- klient wysyła pierwsze żądanie (serwer rozpoznaje go po tym, że klient nie posiada tokenu)
- serwer generuje odpowiedź (formularz) i umieszcza token w ukrytym polu tego formularza. W tym momencie połączenie zostaje zamknięte (klient opuszcza okienko z tokenem). Serwer zadbał ewentualnie o powiązanie informacji z tym tokenem.
- Klient wysyła drugie żądanie, odsyłając formularz. Serwer pobiera z niego token. Dzięki tokenowi może wówczas przetworzyć drugie żądanie klienta, mając dostęp do informacji obliczonych podczas pierwszego żądania. Do pliku powiązanego z tokenem dodawane są nowe informacje, do klienta wysyłana jest druga odpowiedź, a połączenie zostaje zamknięte po raz drugi. Token został ponownie umieszczony w formularzu odpowiedzi, aby użytkownik mógł go przedstawić podczas kolejnego żądania.
- i tak dalej...
Główną wadą tej techniki jest to, że token musi zostać umieszczony w formularzu. Jeśli odpowiedź serwera nie jest formularzem, metoda ukrytego pola nie ma już zastosowania.
- metoda pliku cookie
- klient wysyła pierwsze żądanie (serwer rozpoznaje go po tym, że klient nie posiada tokenu)
- serwer wysyła odpowiedź, dodając plik cookie do nagłówków HTTP tej odpowiedzi. Odbywa się to za pomocą polecenia HTTP Set-Cookie:
Set-Cookie: param1=wartość1;param2=wartość2;....
gdzie parami to nazwy parametrów, a valeursi to ich wartości. Wśród parametrów znajdzie się token. Bardzo często w pliku cookie znajduje się wyłącznie token, a pozostałe informacje są zapisywane przez serwer w folderze powiązanym z tokenem. Przeglądarka, która otrzymuje plik cookie, zapisuje go w pliku na dysku. Po otrzymaniu odpowiedzi od serwera połączenie zostaje zamknięte (klient opuszcza okno z tokenem).
- (ciąg dalszy)
- klient wysyła drugie żądanie do serwera. Za każdym razem, gdy wysyłane jest żądanie do serwera, przeglądarka sprawdza wśród wszystkich posiadanych plików cookie, czy znajduje się wśród nich plik pochodzący z serwera, do którego skierowano żądanie. Jeśli tak, wysyła je do serwera zawsze w postaci polecenia HTTP, czyli polecenia „Cookie”, którego składnia jest analogiczna do składni polecenia Set-Cookie używanego przez serwer:
Cookie: param1=wartość1;param2=wartość2;....
Wśród nagłówków HTTP wysyłanych przez przeglądarkę serwer odnajdzie token, który pozwoli mu rozpoznać klienta i odzyskać powiązane z nim informacje.
Jest to najczęściej stosowana forma tokenu. Ma ona jednak wadę: użytkownik może skonfigurować swoją przeglądarkę tak, aby nie akceptowała plików cookie. Taki użytkownik nie ma wówczas dostępu do aplikacji internetowych wykorzystujących pliki cookie.
- przeróbka URL
- klient wysyła pierwsze żądanie (serwer rozpoznaje go po tym, że klient nie posiada tokenu)
- serwer wysyła odpowiedź. Zawiera ona linki, z których użytkownik musi skorzystać, aby kontynuować korzystanie z aplikacji. W adresie URL każdego z tych linków serwer dodaje token w postaci URL;token=wartość.
- gdy użytkownik kliknie jeden z linków, aby kontynuować korzystanie z aplikacji, przeglądarka wysyła żądanie do serwera WWW, umieszczając w nagłówkach HTTP żądany token w postaci URL URL;token=wartość. Serwer jest wówczas w stanie odzyskać token.
3.10.2. API i PHP do śledzenia sesji
Poniżej przedstawiamy główne metody przydatne do śledzenia sesji:
rozpoczyna sesję, do której należy bieżące żądanie. Jeśli żądanie to nie należało jeszcze do żadnej sesji, sesja ta zostaje utworzona. | |
identyfikator bieżącej sesji | |
słownik przechowujący dane sesji. Dostępny do odczytu i zapisu | |
usuwa dane zawarte w bieżącej sesji. Dane te pozostają dostępne dla bieżącej wymiany danych między klientem a serwerem, ale nie będą dostępne podczas następnej wymiany. |
3.10.3. Przykład 1
Przedstawiamy przykład zaczerpnięty z książki „Programowanie z wykorzystaniem J2EE”, wydanej przez wydawnictwo Wrox i dystrybuowanej przez Eyrolles. Przykład ten pozwala zapoznać się z działaniem sesji PHP. Strona główna wygląda następująco:

Znajdują się na niej następujące elementy:
- identyfikator sesji ID uzyskany za pomocą funkcji session_id(). Ten identyfikator ID, wygenerowany przez przeglądarkę, jest wysyłany do klienta za pomocą pliku cookie, który przeglądarka odsyła, gdy żąda identyfikatora URL z tej samej struktury drzewa. To właśnie pozwala na utrzymanie sesji.
- licznik, który jest zwiększany wraz z kolejnymi żądaniami przeglądarki i który wskazuje, że sesja jest prawidłowo utrzymywana.
- link umożliwiający usunięcie danych powiązanych z bieżącą sesją. Odbywa się to za pomocą funkcji session_destroy()
- link do ponownego załadowania strony
Kod aplikacji cycledevie.php wygląda następująco:
<?php
//cycledevie.php
// konfiguracja
ini_set("register_globals","off");
ini_set("display_errors","off");
// rozpoczynamy sesję
session_start();
// czy należy ją unieważnić?
$action=$_GET["action"];
if($action=="invalider"){
// koniec sesji
session_destroy();
}//if
// zarządzanie licznikiem
if(! isset($_SESSION["compteur"]))
// licznik nie istnieje – tworzymy go
$_SESSION["compteur"]=0;
// licznik istnieje – zwiększamy jego wartość
else $_SESSION["compteur"]++;
// pobieramy ID z bieżącej sesji
$idSession=session_id();
// pobieramy licznik
$compteur=$_SESSION["compteur"];
// przekazujemy kontrolę do strony wyświetlania
include "cycledevie-p1.php";
?>
Należy zwrócić uwagę na następujące kwestie:
- od momentu uruchomienia aplikacji rozpoczyna się sesja. Jeśli klient przesłał token sesji, wznawiana jest sesja o tym identyfikatorze, a wszystkie powiązane z nią dane są umieszczane w słowniku $_SESSION. W przeciwnym razie tworzony jest nowy token sesji.
- jeśli klient przesłał parametr action o wartości „invalider”, dane sesji są oznaczane jako „do usunięcia” na potrzeby następnej wymiany. W przeciwieństwie do normalnej wymiany, nie zostaną one zapisane na serwerze po zakończeniu wymiany.
- Z słownika $_SESSION pobierany jest licznik powiązany z sesją, a także identyfikator sesji ID (session_id()).
- Strona, która ma zostać wysłana do klienta, jest generowana przez program cycledevie-p1.php
Strona cycledevie-p1.php wyświetla stronę wysłaną do klienta:
<html>
<head>
<title>Gestion de sessions</title>
</head>
<body>
<h3>Cycle de vie d'une session PHP</h3>
<hr>
<br>ID session : <?php echo $idSession ?>
<br>compteur : <?php echo $compteur ?>
<br><a href="cycledevie.php?action=invalider">Invalider la session</a>
<br><a href="cycledevie.php">Recharger la page</a>
</body>
</html>
Warto zwrócić uwagę na kod URL dołączony do każdego z tych dwóch linków:
- cycledevie.php służy do ponownego załadowania strony
- cycledevie.php?action=invalider – w celu unieważnienia sesji. W tym przypadku do URL dołączony jest parametr action=invalider. Zostanie on pobrany przez program serwerowy cycledevie.php za pomocą instrukcji $action=$_GET["action"].
Odświeżmy stronę dwa razy z rzędu:

Licznik został prawidłowo zwiększony. Identyfikator sesji ID nie uległ zmianie. Teraz unieważnijmy sesję:

Widać, że utraciliśmy identyfikator sesji ID, ale licznik został ponownie zwiększony. Odświeżmy stronę:

Widać, że zaczynamy od nowa z tym samym identyfikatorem sesji ID, co poprzednio. Licznik natomiast wraca do zera. Funkcja session_destroy() nie ma zatem natychmiastowego efektu. Dane bieżącej sesji są usuwane jedynie na potrzeby wymiany danych między klientem a serwerem następującej po tej, w której dokonano usunięcia. Identyfikator sesji ID nie uległ zmianie, co sugeruje, że funkcja session_destroy() nie uruchamia nowej sesji poprzez utworzenie nowego identyfikatora ID. Plik cookie z tokenem sesji został odesłany przez przeglądarkę klienta, a funkcja PHP odzyskała sesję na podstawie tego tokenu – sesję, która nie zawierała już żadnych danych.
Poprzednie testy przeprowadzono przy użyciu przeglądarki Netscape skonfigurowanej do korzystania z plików cookie. Skonfigurujmy ją teraz tak, aby z nich nie korzystała. Oznacza to, że nie będzie ona ani przechowywać, ani odsyłać plików cookie wysyłanych przez serwer. Można się zatem spodziewać, że sesje przestaną działać. Spróbujmy przeprowadzić pierwszą wymianę danych:

Otrzymaliśmy identyfikator sesji ID, wygenerowany przez funkcję session_start(). Odświeżmy stronę, korzystając z linku:

Co zaskakujące, powyższe wyniki pokazują, że sesja jest aktywna, a licznik działa poprawnie. Jak to możliwe, skoro pliki cookie zostały wyłączone i nie ma już wymiany tokenów między serwerem a przeglądarką? Odpowiedź daje nam identyfikator URL z powyższego zrzutu ekranu:
To jest URL z linku „Odśwież stronę”. Sprawdźmy kod źródłowy strony wyświetlanej przez przeglądarkę:
<a href="cycledevie.php?action=invalider&PHPSESSID=587ce26f943a288d8f41212e30fed13c">Invalider la session</a>
<a href="cycledevie.php?PHPSESSID=587ce26f943a288d8f41212e30fed13c">Recharger la page</a>
Przypomnijmy, że początkowy kod obu linków na stronie cycledevie-p1.php wygląda następująco:
<a href="cycledevie.php?action=invalider">Invalider la session</a>
<a href="cycledevie.php">Recharger la page</a>
Interpreter PHP samodzielnie przepisał więc kody URL obu linków, dodając do nich token sesji. Dzięki temu token ten jest prawidłowo przekazywany przez przeglądarkę po kliknięciu linków. To wyjaśnia, dlaczego nawet przy wyłączonych plikach cookie sesja jest nadal prawidłowo zarządzana.
3.10.4. Przykład 3
Zamierzamy napisać aplikację PHP, która będzie klientem poprzedniej aplikacji compteur. Będzie ona wywoływać ją N razy z rzędu, gdzie N będzie przekazywane jako parametr. Naszym celem jest zaprezentowanie zaprogramowanego klienta internetowego oraz sposobu zarządzania tokenem sesji. Naszym punktem wyjścia będzie ogólny klient internetowy wywoływany w następujący sposób:
clientweb URL GET/HEAD
- URL: żądany adres URL
- GET/HEAD: GET, aby uzyskać kod HTML ze strony, HEAD, aby ograniczyć się wyłącznie do nagłówków HTTP
Oto przykład z kodem URL http://localhost/poly/sessions/2/cycledevie.php. Jest to ten sam program, co opisany wcześniej, z niewielką różnicą:
// ustalamy ścieżkę pliku cookie
session_set_cookie_params(0,"/poly/sessions/2");
// rozpoczynamy sesję
session_start();
Funkcja session_set_cookie_params pozwala ustawić pewne parametry pliku cookie, który będzie zawierał token sesji. Pierwszym parametrem jest czas życia pliku cookie. Czas życia równy zero oznacza, że plik cookie zostanie usunięty przez przeglądarkę, która go otrzymała, po jej zamknięciu. Drugim parametrem jest ścieżka do serwera URL, na który przeglądarka ma odesłać plik cookie. W powyższym przykładzie, jeśli przeglądarka otrzymała plik cookie z serwera o nazwie localhost, odeśle ten plik cookie do dowolnego serwera o nazwie URL znajdującego się w drzewie serwerów http://localhost/poly/sessions/2/.
dos>e:\php43\php.exe clientweb.php http://localhost/poly/sessions/2/cycledevie.php GET
HTTP/1.1 200 OK
Date: Wed, 09 Oct 2002 13:58:16 GMT
Server: Apache/1.3.24 (Win32)
Set-Cookie: PHPSESSID=48d5aaa0e99850b17c33a6e22d38e5c4; path=/poly/sessions/2
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Transfer-Encoding: chunked
Content-Type: text/html
<html>
<head>
<title>Gestion de sessions</title>
</head>
<body>
<h3>Cycle de vie d'une session PHP</h3>
<hr>
<br>ID session : 48d5aaa0e99850b17c33a6e22d38e5c4 <br>compteur : 0
<br><a href="cycledevie.php?action=invalider&PHPSESSID=48d5aaa0e99850b17c33a6e22d38e5c4">Invalid
er la session</a>
<br><a href="cycledevie.php?PHPSESSID=48d5aaa0e99850b17c33a6e22d38e5c4">Recharger la page</a>
</body>
</html>
Program clientweb wyświetla wszystko, co otrzymuje z serwera. Powyżej widoczne jest polecenie HTTP Set-cookie, za pomocą którego serwer wysyła plik cookie do swojego klienta. W tym przypadku plik cookie zawiera dwie informacje:
- PHPSESSID, czyli token sesji
- path, który określa adres URL, do którego należy plik cookie. path=/poly/sessions/2 informuje przeglądarkę, że będzie musiała odesłać plik cookie do serwera za każdym razem, gdy zażąda adresu URL zaczynającego się od /poly/sessions/2 z komputera, który wysłał jej ten plik cookie.
- Plik cookie może również określać okres ważności. W tym przypadku informacja ta nie została podana. Plik cookie zostanie zatem usunięty po zamknięciu przeglądarki. Plik cookie może mieć na przykład okres ważności wynoszący N dni. Dopóki plik jest ważny, przeglądarka będzie go odsyłać za każdym razem, gdy zostanie wywołana jedna z wartości URL z jego domeny (Path). Weźmy na przykład witrynę sklepu internetowego o nazwie CD. Witryna ta może śledzić ścieżkę nawigacji klienta w swoim katalogu i stopniowo określać jego preferencje: na przykład muzykę klasyczną. Preferencje te mogą zostać zapisane w pliku cookie o okresie ważności wynoszącym 3 miesiące. Jeśli ten sam klient powróci na stronę po upływie miesiąca, przeglądarka prześle plik cookie do aplikacji serwerowej. Aplikacja ta, na podstawie informacji zawartych w pliku cookie, będzie mogła dostosować generowane strony do preferencji klienta.
Poniżej znajduje się kod klienta internetowego.
<?php
// konfiguracja
dl("php_curl.dll"); // biblioteka CURL
// składnia: $0 URL GET
// wymagane są trzy argumenty
if($argc != 3){
// komunikat o błędzie
fputs(STDERR,"Syntaxe : $argv[0] URL GET/HEAD");
// zatrzymanie
exit(1);
}//if
// trzeci argument musi mieć postać GET lub HEAD
$header=strtolower($argv[2]);
if($header!="get" && $header!="head"){
// komunikat o błędzie
fputs(STDERR,"Syntaxe : $argv[0] URL GET/HEAD");
// zatrzymanie
exit(2);
}//if
// pierwszym argumentem jest URL
$URL=strtolower($argv[1]);
// przygotowywanie połączenia
$connexion=curl_init($URL);
// konfiguracja połączenia
curl_setopt($connexion,CURLOPT_HEADER,1);
if($header=="head") curl_setopt($connexion,CURLOPT_NOBODY,1);
// realizacja połączenia
curl_exec($connexion);
// zamknięcie połączenia
curl_close($connexion);
// koniec
exit(0);
?>
Poprzedni program korzysta z biblioteki CURL:
inicjuje obiekt CURL z docelowym obiektem URL | |
ustawia wartości niektórych opcji połączenia. Oto dwie użyte w programie: CURLOPT_HEADER=1: umożliwia uzyskanie nagłówków HTTP wysyłanych przez serwer CURLOPT_NOBODY=1: pozwala zignorować dokument wysyłany przez serwer za nagłówkami HTTP | |
nawiązuje połączenie z $URL z żądanymi opcjami. Wyświetla na ekranie wszystko, co wysyła serwer | |
zamyka połączenie |
Poprzedni program jest dość prosty. Jednak biblioteka CURL nie pozwala na precyzyjną obsługę odpowiedzi serwera, na przykład na analizowanie jej wiersz po wierszu. Poniższy program wykonuje to samo co poprzedni, ale z wykorzystaniem podstawowych funkcji sieciowych biblioteki PHP. Będzie on punktem wyjścia do napisania klienta dla naszej aplikacji cycledevie.php.
<?php
// składnia: $0 URL GET/HEAD
// wymagane są trzy argumenty
if($argc != 3){
// komunikat o błędzie
fputs(STDERR,"Syntaxe : $argv[0] URL GET/HEAD");
// zatrzymanie
exit(1);
}//if
// połączenie i wyświetlenie wyniku
$résultats=getURL($argv[1],$argv[2]);
if(isset($résultats->erreur)){
// błąd
echo "L'erreur suivante s'est produite : $résultats->erreur\n";
}else{
// wyświetlenie odpowiedzi serwera
echo $résultats->réponse;
}//if
// koniec
exit(0);
//-----------------------------------------------------------------------
function getURL($URL,$header){
// łączy się z $URL
// tworzy GET lub HEAD w zależności od wartości nagłówka
// odpowiedź serwera stanowi wynik funkcji
// analiza URL
$url=parse_url($URL);
// protokół
if(strtolower($url["scheme"])!="http"){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//if
// urządzenie
$hote=$url["host"];
if(! isset($hote)){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//if
// port
$port=$url["port"];
if(! isset($port)) $port=80;
// ścieżka
$chemin=$url["path"];
// żądanie
if(isset($url["query"])){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//if
// analiza $header
$header=strtoupper($header);
if($header!="GET" && $header!="HEAD"){
// komunikat o błędzie
$résultats->erreur="méthode [$header] doit être GET ou HEAD";
// zatrzymanie
return $résultats;
}//if
// nawiązanie połączenia na porcie $port z $hote
$connexion=fsockopen($hote,$port,&$errno,&$erreur);
// powrót w przypadku błędu
if(! $connexion){
$résultats->erreur="Echec de la connexion au site ($hote,$port) : $erreur";
return $résultats;
}//if
// $connexion reprezentuje dwukierunkowy strumień komunikacji
// między klientem (tym programem) a serwerem WWW, z którym nawiązano połączenie
// kanał ten służy do wymiany poleceń i informacji
// protokołem komunikacji jest HTTP
// klient wysyła polecenie GET, aby zażądać pliku URL /
// składnia polecenia GET: URL HTTP/1.0
// nagłówki (headers) protokołu HTTP muszą kończyć się pustym wierszem
fputs($connexion, "$header $chemin HTTP/1.0\n\n");
// serwer odpowie teraz na kanale $connexion. Wyśle wszystkie
// te dane, a następnie zamknie kanał. Klient odczytuje zatem wszystko, co nadchodzi z $connexion
// aż do zamknięcia kanału
$résultats->réponse="";
while($ligne=fgets($connexion,10000))
$résultats->réponse.=$ligne;
// klient z kolei zamyka połączenie
fclose($connexion);
// powrót
return $résultats;
}//getURL
?>
Omówmy kilka aspektów tego programu:
- program przyjmuje dwa parametry:
- adres HTTP w formacie URL, którego zawartość ma zostać wyświetlona na ekranie.
- metodę GET lub HEAD, którą należy zastosować w zależności od tego, czy chcemy wyświetlić tylko nagłówki HTTP (HEAD), czy też również treść dokumentu powiązaną z URL (GET).
- Oba parametry są przekazywane do funkcji getURL. Funkcja ta zwraca obiekt $résultats. Obiekt ten zawiera pole erreur w przypadku wystąpienia błędu, a w przeciwnym razie pole réponse. Pole erreur służy do przechowywania ewentualnego komunikatu o błędzie. Pole réponse zawiera odpowiedź serwera WWW, z którym nawiązano połączenie.
- Funkcja getURL analizuje URL i $URL za pomocą funkcji parse_url. Instrukcja $url=parse_url($URL) utworzy tablicę asocjacyjną $url z następującymi ewentualnymi kluczami:
- scheme: protokół URL (http, ftp, ...)
- host: komputer, na którym znajduje się URL
- port: port URL
- path: ścieżka do serwera URL
- querystring: parametry URL
Adres URL będzie poprawny, jeśli ma postać http://machine[:port][/chemin].
- Sprawdzany jest również parametr $header
- po sprawdzeniu i potwierdzeniu poprawności parametrów tworzone jest połączenie TCP na serwerze ($hote, $port), następnie wysyłane jest polecenie HTTP, GET lub HEAD, w zależności od parametru $header.
- następnie odczytuje się odpowiedź serwera i umieszcza ją w zmiennej $résultats->odpowiedź.
Wykonanie programu daje następujące wyniki:
dos>"e:\php43\php.exe" geturl.php http://localhost/poly/sessions/2/cycledevie.php get
HTTP/1.1 200 OK
Date: Wed, 09 Oct 2002 14:56:55 GMT
Server: Apache/1.3.24 (Win32)
Set-Cookie: PHPSESSID=ea0d2673811ed069e7289d86933a4c0a; path=/poly/sessions/2
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Connection: close
Content-Type: text/html
<html>
<head>
<title>Gestion de sessions</title>
</head>
<body>
<h3>Cycle de vie d'une session PHP</h3>
<hr>
<br>ID session : ea0d2673811ed069e7289d86933a4c0a <br>compteur : 0
<br><a href="cycledevie.php?action=invalider&PHPSESSID=ea0d2673811ed069e7289d86933a4c0a">Invalid
er la session</a>
<br><a href="cycledevie.php?PHPSESSID=ea0d2673811ed069e7289d86933a4c0a">Recharger la page</a>
</body>
</html>
Uważny czytelnik zauważył zapewne, że odpowiedź serwera różni się w zależności od używanego programu klienckiego. W pierwszym przypadku serwer wysłał nagłówek HTTP: Transfer-Encoding: chunked, który nie został wysłany w drugim przypadku. Wynika to z faktu, że drugi klient wysłał nagłówek HTTP: get URL HTTP/1.0, który żąda URL i wskazuje, że korzysta z protokołu HTTP w wersji 1.0, co zobowiązuje serwer do udzielenia odpowiedzi przy użyciu tego samego protokołu. Natomiast nagłówek HTTP „Transfer-Encoding: chunked” należy do protokołu HTTP w wersji 1.1. Dlatego serwer nie wykorzystał go w swojej odpowiedzi. To pokazuje nam, że pierwszy klient wysłał swoje żądanie, wskazując, że korzysta z protokołu HTTP w wersji 1.1.
Teraz tworzymy program o nazwie clientCompteur, wywoływany w następujący sposób:
clientCompteur URL N [JSESSIONID]
- URL: adres URL aplikacji cycledevie
- N: liczba wywołań tej aplikacji
- PHPSESSID: parametr opcjonalny – token sesji
Celem programu jest wywołanie aplikacji cycledevie.php N razy, zarządzając plikiem cookie sesji i wyświetlając za każdym razem wartość licznika zwróconą przez serwer. Po zakończeniu N wywołań wartość licznika musi wynosić N-1. Oto pierwszy przykład działania:
dos>"e:\php43\php.exe" clientCompteur2.php http://localhost/poly/sessions/2/cycledevie.php 3
--> GET /poly/sessions/2/cycledevie.php HTTP/1.1
--> Host: localhost:80
--> Connection: close
-->
<-- HTTP/1.1 200 OK
<-- Date: Thu, 10 Oct 2002 06:27:48 GMT
<-- Server: Apache/1.3.24 (Win32)
<-- Set-Cookie: PHPSESSID=2425e00d1d65c2bdcbafc1ce6244f7ea; path=/poly/sessions/2
<-- Expires: Thu, 19 Nov 1981 08:52:00 GMT
<-- Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
<-- Pragma: no-cache
<-- Connection: close
<-- Transfer-Encoding: chunked
<-- Content-Type: text/html
<--
[Le compteur est égal à 0]
--> GET /poly/sessions/2/cycledevie.php HTTP/1.1
--> Host: localhost:80
--> Connection: close
--> Cookie: PHPSESSID=2425e00d1d65c2bdcbafc1ce6244f7ea
-->
<-- HTTP/1.1 200 OK
<-- Date: Thu, 10 Oct 2002 06:27:48 GMT
<-- Server: Apache/1.3.24 (Win32)
<-- Expires: Thu, 19 Nov 1981 08:52:00 GMT
<-- Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
<-- Pragma: no-cache
<-- Connection: close
<-- Transfer-Encoding: chunked
<-- Content-Type: text/html
<--
[Le compteur est égal à 1]
--> GET /poly/sessions/2/cycledevie.php HTTP/1.1
--> Host: localhost:80
--> Connection: close
--> Cookie: PHPSESSID=2425e00d1d65c2bdcbafc1ce6244f7ea
-->
<-- HTTP/1.1 200 OK
<-- Date: Thu, 10 Oct 2002 06:27:48 GMT
<-- Server: Apache/1.3.24 (Win32)
<-- Expires: Thu, 19 Nov 1981 08:52:00 GMT
<-- Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
<-- Pragma: no-cache
<-- Connection: close
<-- Transfer-Encoding: chunked
<-- Content-Type: text/html
<--
[Le compteur est égal à 2]
Program wyświetla:
- nagłówki HTTP, które wysyła do serwera w postaci --> entêteEnvoyé
- nagłówki HTTP, które otrzymuje w postaci <-- entêteReçu
- wartość licznika po każdym wywołaniu
Widać, że podczas pierwszego wywołania:
- klient nie wysyła pliku cookie
- serwer wysyła plik cookie (Set-Cookie:)
W przypadku kolejnych wywołań:
- klient zawsze odsyła plik cookie, który otrzymał od serwera podczas pierwszego wywołania. To właśnie pozwala serwerowi rozpoznać go i zwiększyć wartość licznika.
- serwer natomiast nie wysyła już pliku cookie
Uruchamiamy ponownie poprzedni program, przekazując powyższy token jako trzeci parametr:
dos>"e:\php43\php.exe" clientCompteur2.php http://localhost/poly/sessions/2/cycledevie.php 1 2425e00d1d65c2bdcbafc1ce6244f7ea
--> GET /poly/sessions/2/cycledevie.php HTTP/1.1
--> Host: localhost:80
--> Connection: close
--> Cookie: PHPSESSID=2425e00d1d65c2bdcbafc1ce6244f7ea
-->
<-- HTTP/1.1 200 OK
<-- Date: Thu, 10 Oct 2002 06:32:03 GMT
<-- Server: Apache/1.3.24 (Win32)
<-- Expires: Thu, 19 Nov 1981 08:52:00 GMT
<-- Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
<-- Pragma: no-cache
<-- Connection: close
<-- Transfer-Encoding: chunked
<-- Content-Type: text/html
<--
[Le compteur est égal à 3]
Widać tutaj, że już przy pierwszym wywołaniu ze strony klienta serwer otrzymuje ważny plik cookie sesji. Być może wskazuje to na potencjalną lukę w zabezpieczeniach. Jeśli uda mi się przechwycić w sieci token sesji, będę w stanie podszyć się pod osobę, która ją zainicjowała. W naszym przykładzie pierwsze wywołanie (bez tokenu sesji) reprezentuje osobę inicjującą sesję (być może za pomocą loginu i hasła, które uprawniają ją do otrzymania tokenu), a drugie wywołanie (z tokenem sesji) reprezentuje osobę, która „przejęła” token sesji z pierwszego wywołania. Jeśli bieżąca operacja dotyczy bankowości, może to stać się kłopotliwe...
Kod klienta wygląda następująco:
<?php
// składnia: $0 URL N [PHPSESSID]
// potrzebne są trzy argumenty
if($argc!=3 && $argc!=4){
// komunikat o błędzie
fputs(STDERR,"Syntaxe : $argv[0] URL N [PHPSESSID]");
// zatrzymanie
exit(1);
}//if
// pobieranie parametrów
$URL=$argv[1];
$N=$argv[2];
$PHPSESSID=$argv[3];
// połączenie i wyświetlenie wyniku
$résultats=getURL($URL,$N,$PHPSESSID);
if(isset($résultats->erreur)){
// błąd
echo "L'erreur suivante s'est produite : $résultats->erreur\n";
}
// koniec
exit(0);
//-----------------------------------------------------------------------
function getURL($URL,$N,$PHPSESSID){
// łączy się z URL
// tworzy GET lub HEAD w zależności od wartości nagłówka
// odpowiedź serwera stanowi wynik funkcji
// analiza URL
$url=parse_url($URL);
// protokół
if(strtolower($url["scheme"])!="http"){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//if
// urządzenie
$hote=$url["host"];
if(! isset($hote)){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//if
// port
$port=$url["port"];
if(! isset($port)) $port=80;
// ścieżka
$chemin=$url["path"];
// zapytanie
if(isset($url["query"])){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//if
// weryfikacja $N
if (! preg_match("/^\d+$/",$N)){
// błąd
$résultats->erreur="nombre [$N] erroné";
// koniec
return $résultats;
}//if
// wykonuje się wywołania $N do $URL
for($i=0;$i<$N;$i++){
// nawiązanie połączenia na porcie $port serwera $hote
$connexion=fsockopen($hote,$port,&$errno,&$erreur);
// powrót w przypadku błędu
if(! $connexion){
$résultats->erreur="Echec de la connexion au site ($hote,$port) : $erreur";
return $résultats;
}//if
// $connexion reprezentuje dwukierunkowy strumień komunikacji
// między klientem (tym programem) a serwerem WWW, z którym nawiązano połączenie
// kanał ten służy do wymiany poleceń i informacji
// protokołem komunikacji jest HTTP
// klient wysyła nagłówki HTTP
// get URL HTTP/1.1
envoie($connexion, "GET $chemin HTTP/1.1\n");
// host: host:port
envoie($connexion, "Host: $hote:$port\n");
// Połączenie: zamknięte
envoie($connexion, "Connection: close\n");
// Cookie: $PHPSESSID
if($PHPSESSID) envoie($connexion, "Cookie: PHPSESSID=$PHPSESSID\n");
// pusta linia
envoie($connexion,"\n");
// serwer będzie teraz odpowiadał na kanale $connexion. Wyśle wszystkie
// swoje dane, a następnie zamknie kanał.
// Klient zaczyna od odczytania nagłówków HTTP zakończonych pustą linią
$CHUNKED=0;
while(($ligne=fgets($connexion,10000)) && (($ligne=rtrim($ligne))!="")){
// echo linii
echo "<-- $ligne\n";
// wyszukiwanie tokenu, jeśli jeszcze go nie znaleziono
if(! $PHPSESSID){
// wyszukiwanie wiersza „set-cookie”
if(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i",$ligne,$champs)){
// znaleziono token – zapisujemy go
$PHPSESSID=$champs[1];
}//if
}//if
// wyszukiwanie trybu przesyłania dokumentu
if(! $CHUNKED){
// wyszukiwanie wiersza Transfer-Encoding: chunked
if(preg_match("/^Transfer-Encoding: chunked/i",$ligne,$champs)){
// przesyłanie fragmentami
$CHUNKED=1;
}//if
}//if
}//następny wiersz
// wyświetlenie wiersza
echo "<-- $ligne\n";
// odczyt dokumentu zależy od sposobu, w jaki został wysłany
if($CHUNKED) $document=getChunkedDoc($connexion);
else $document=getDoc($connexion);
// wyszukiwanie licznika w dokumencie
if(preg_match("/<br>compteur : (\d+)/i",$document,$champs)){
// znaleziono licznik – wyświetlamy go
echo "\n[Le compteur est égal à $champs[1]]\n\n";
}//if
// klient zamyka połączenie
fclose($connexion);
}//for i
}//getURL
//--------------------------
function getDoc($connexion){
// odczyt dokumentu na $connexion
$doc="";
while($ligne=fread($connexion,10000))
$doc.=$ligne;
// koniec
return $doc;
}//getDoc
//--------------------------
function getChunkedDoc($connexion){
// odczyt dokumentu na $connexion
// dokument ten jest wysyłany fragmentami w postaci
// liczba znaków w fragmencie w systemie szesnastkowym
// kontynuacja fragmentu
// pusta linia
// odczytuje się rozmiar fragmentu z pierwszego wiersza
$taille=hexdec(rtrim(fgets($connexion,10000)));
// odczytuje się następujący dokument
$doc="";
while($taille!=0){
// odczyt fragmentu o długości $taille znaków
$doc.=fread($connexion,$taille);
// pusty wiersz
fgets($connexion,10000);
// kolejny fragment
// odczyt rozmiaru fragmentu
$taille=hexdec(rtrim(fgets($connexion,10000)));
}//while
// koniec
return $doc;
}// getChunkedDoc
//--------------------------
function envoie($flux,$msg){
// wysyła $msg na $flux
fwrite($flux,$msg);
// wyświetl na ekranie
echo "--> $msg";
}//wysyła
?>
Przeanalizujmy najważniejsze elementy tego programu:
- musimy wykonać N wymian danych między klientem a serwerem. Dlatego też są one umieszczone w pętli
- przy każdej wymianie klient nawiązuje połączenie TCP-IP z serwerem. Po nawiązaniu połączenia wysyła do serwera nagłówki HTTP swojego żądania:
<?php
...
// klient wysyła nagłówki HTTP
// get URL HTTP/1.1
envoie($connexion, "GET $chemin HTTP/1.1\n");
// host: host:port
envoie($connexion, "Host: $hote:$port\n");
// Połączenie: zamknięte
envoie($connexion, "Connection: close\n");
// Cookie: $PHPSESSID
if($PHPSESSID) envoie($connexion, "Cookie: PHPSESSID=$PHPSESSID\n");
// pusta linia
envoie($connexion,"\n");
Jeśli token PHPSESSID jest dostępny, jest wysyłany w postaci pliku cookie, w przeciwnym razie nie jest wysyłany. Należy zauważyć, że klient wskazał, iż korzysta z protokołu HTTP/1.1. To wyjaśnia, dlaczego później serwer wyśle mu nagłówek HTTP: Transfer-Encoding: chunked, który należy do protokołu HTTP/1.1, ale nie do protokołu HTTP/1.0.
- Po wysłaniu żądania klient oczekuje na odpowiedź serwera. Najpierw analizuje nagłówki HTTP zawarte w tej odpowiedzi. Szuka w niej dwóch wierszy:
Wiersz „Cookie:” to nagłówek HTTP, który zawiera token sesji PHPSESSID. Klient musi go pobrać, aby odesłać go do serwera podczas kolejnej wymiany danych. Wiersz „Transfer-Encoding: chunked”, jeśli występuje, oznacza, że serwer wyśle dokument w fragmentach. Każdy fragment jest wówczas wysyłany do klienta w następującej postaci:
Jeśli wiersz Transfer-Encoding: chunked nie występuje, dokument jest wysyłany jednorazowo po pustym wierszu nagłówków HTTP. Zatem w zależności od obecności lub braku tego wiersza sposób odbioru dokumentu będzie się różnił. Kod interpretacji nagłówków HTTP jest następujący:
<?php
...
// Klient zaczyna od odczytania nagłówków HTTP zakończonych pustym wierszem
$CHUNKED=0;
while(($ligne=fgets($connexion,10000)) && (($ligne=rtrim($ligne))!="")){
// echo linii
echo "<-- $ligne\n";
// wyszukiwanie tokenu, jeśli jeszcze go nie znaleziono
if(! $PHPSESSID){
// wyszukiwanie wiersza „set-cookie”
if(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i",$ligne,$champs)){
// znaleziono token – zapisujemy go
$PHPSESSID=$champs[1];
}//if
}//if
// wyszukiwanie trybu przesyłania dokumentu
if(! $CHUNKED){
// wyszukiwanie wiersza Transfer-Encoding: chunked
if(preg_match("/^Transfer-Encoding: chunked/i",$ligne,$champs)){
// przesyłanie fragmentami
$CHUNKED=1;
}//if
}//if
}//następny wiersz
- gdy token zostanie znaleziony po raz pierwszy, nie będzie już wyszukiwany podczas kolejnych wywołań serwera. Po przetworzeniu nagłówków odpowiedzi HTTP przechodzi się do dokumentu następującego po nagłówkach HTTP. Dokument ten jest odczytywany w różny sposób w zależności od trybu transferu:
<?php
...
// odczyt dokumentu zależy od sposobu, w jaki został wysłany
if($CHUNKED) $document=getChunkedDoc($connexion);
else $document=getDoc($connexion);
- W otrzymanym dokumencie $document szukamy wiersza zawierającego wartość licznika. Wyszukiwanie to również odbywa się za pomocą wyrażenia regularnego:
<?php
...
// wyszukiwanie licznika w dokumencie
if(preg_match("/<br>compteur : (\d+)/i",$document,$champs)){
// znaleziono licznik – wyświetlamy go
echo "\n[Le compteur est égal à $champs[1]]\n\n";
}//if
- W przypadku, gdy serwer wysyła dokument w całości, jego odbiór jest prosty:
<?php
...
//--------------------------
function getDoc($connexion){
// odczyt dokumentu o numerze $connexion
$doc="";
while($ligne=fgets($connexion,10000))
$doc.=$ligne;
// koniec
return $doc;
}//getDoc
- W przypadku, gdy serwer wysyła dokument w kilku częściach, jego odczytanie jest bardziej skomplikowane:
<?php
...
function getChunkedDoc($connexion){
// odczyt dokumentu na $connexion
// dokument ten jest wysyłany fragmentami w postaci
// liczba znaków w fragmencie w systemie szesnastkowym
// kontynuacja fragmentu
// w pierwszym wierszu odczytuje się rozmiar fragmentu
$taille=hexdec(rtrim(fgets($connexion,10000)));
// odczytuje się następujący dokument
$doc="";
while($taille!=0){
// odczyt fragmentu o długości $taille znaków
$doc.=fread($connexion,$taille);
// pusty wiersz
fgets($connexion,10000);
// kolejny fragment
// odczyt rozmiaru fragmentu
$taille=hexdec(rtrim(fgets($connexion,10000)));
}//while
// koniec
return $doc;
}// getChunkedDoc
Przypomnijmy, że fragment dokumentu jest wysyłany w postaci
Zaczynamy więc od odczytania rozmiaru dokumentu. Znając już ten rozmiar, wywołujemy funkcję fread, aby odczytała $taille znaków ze strumienia $connexion, a następnie następujący po nim pusty wiersz. Powtarzamy tę czynność, dopóki serwer nie poinformuje, że wyśle dokument o rozmiarze 0.
3.10.5. Przykład 4
W poprzednim przykładzie klient internetowy zwraca token w postaci pliku cookie. Widzieliśmy, że może on również zwrócić token w samym żądaniu URL w postaci URL;PHPSESSID=xxx. Sprawdźmy to. Program clientCompteur.php zostaje przekształcony w clientCompteur2.php i zmodyfikowany w następujący sposób:
<?php
...
....
// klient wysyła nagłówki HTTP
// get URL HTTP/1.1
if($PHPSESSID)
envoie($connexion, "GET $chemin?PHPSESSID=$PHPSESSID HTTP/1.1\n");
else envoie($connexion, "GET $chemin HTTP/1.1\n");
// host: host:port
envoie($connexion, "Host: $hote:$port\n");
// Połączenie: zamknięte
envoie($connexion, "Connection: close\n");
// pusta linia
envoie($connexion,"\n");
....
Klient żąda zatem odczytu licznika o numerze URL za pomocą GET i URL;PHPSESSID=xx HTTP/1.1 i nie wysyła już pliku cookie. To jedyna zmiana. Oto wyniki pierwszego wywołania:
dos>"e:\php43\php.exe" clientCompteur2.php http://localhost/poly/sessions/2/cycledevie.php 2
--> GET /poly/sessions/2/cycledevie.php HTTP/1.1
--> Host: localhost:80
--> Connection: close
-->
<-- HTTP/1.1 200 OK
<-- Date: Thu, 10 Oct 2002 07:21:19 GMT
<-- Server: Apache/1.3.24 (Win32)
<-- Set-Cookie: PHPSESSID=573212ba82303d7903caf8944ee7a86f; path=/poly/sessions/2
<-- Expires: Thu, 19 Nov 1981 08:52:00 GMT
<-- Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
<-- Pragma: no-cache
<-- Connection: close
<-- Transfer-Encoding: chunked
<-- Content-Type: text/html
<--
[Le compteur est égal à 0]
--> GET /poly/sessions/2/cycledevie.php?PHPSESSID=573212ba82303d7903caf8944ee7a86f HTTP/1.1
--> Host: localhost:80
--> Connection: close
-->
<-- HTTP/1.1 200 OK
<-- Date: Thu, 10 Oct 2002 07:21:19 GMT
<-- Server: Apache/1.3.24 (Win32)
<-- Expires: Thu, 19 Nov 1981 08:52:00 GMT
<-- Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
<-- Pragma: no-cache
<-- Connection: close
<-- Transfer-Encoding: chunked
<-- Content-Type: text/html
<--
[Le compteur est égal à 1]
Podczas pierwszego wywołania klient żąda zasobu URL bez tokenu sesji. Serwer odpowiada, wysyłając mu token. Następnie klient ponownie żąda tego samego zasobu URL, dołączając do niego otrzymany token. Widać, że licznik został prawidłowo zwiększony, co świadczy o tym, że serwer prawidłowo rozpoznał, iż chodzi o tę samą sesję.
3.10.6. Przykład 5
Ten przykład przedstawia aplikację składającą się z trzech stron, które nazwiemy page1, page2 i page3. Użytkownik musi je wyświetlić w następującej kolejności:
- strona1 to formularz wymagający podania informacji: imienia
- strona2 to formularz wyświetlany w odpowiedzi na wysłanie formularza ze strony1. Wymaga on podania drugiej informacji: wieku
- strona3 to dokument o nazwie HTML, który wyświetla imię uzyskane na stronie1 oraz wiek uzyskany na stronie2.
Mają miejsce trzy wymiany danych między klientem a serwerem:
- podczas pierwszej wymiany klient żąda formularza ze strony 1, a serwer go wysyła
- podczas drugiej wymiany klient wysyła formularz ze strony 1 (imię) do serwera. W odpowiedzi otrzymuje formularz ze strony 2 lub ponownie formularz ze strony 1, jeśli był on błędny.
- podczas trzeciej wymiany klient wysyła formularz ze strony 2 (wiek) do serwera. W odpowiedzi otrzymuje formularz ze strony 3 lub ponownie formularz ze strony 2, jeśli był on błędny. Dokument ze strony 3 wyświetla imię i wiek. Imię zostało uzyskane przez serwer podczas drugiej wymiany danych i od tego momentu zostało „zapomniane”. Wykorzystuje się sesję do zapisania imienia podczas wymiany nr 2, aby było ono dostępne podczas wymiany nr 3.
Strona „page1” uzyskana podczas pierwszej wymiany wygląda następująco:

Wypełniamy pole imienia:

Klikamy przycisk [Suite] i otrzymujemy następującą stronę page2:

Wpisujemy wiek w polu:

Klikamy przycisk [Suite], po czym wyświetla się następująca strona page3:

Po przesłaniu strony page1 do serwera, serwer może zwrócić ją z kodem błędu, jeśli pole „imię” jest puste:

Po przesłaniu strony page2 do serwera może on zwrócić ją z kodem błędu, jeśli wiek jest nieprawidłowy:

Aplikacja składa się z sześciu programów:
wykorzystuje page1.php | |
wyświetla stronę1. Formularz ze strony1 jest przetwarzany przez etape2.php. | |
przetwarza wartości z formularza strony1. Jeśli wystąpią błędy, strona1 jest ponownie wyświetlana przez page1.php, w przeciwnym razie strona2 jest wyświetlana przez page2.php. | |
wyświetla stronę 2. Formularz ze strony 2 jest przetwarzany przez etape3.php. | |
przetwarza wartości z formularza na stronie 2. Jeśli wystąpią błędy, strona 2 jest ponownie wyświetlana przez page2.php, w przeciwnym razie strona 3 jest wyświetlana przez page3.php. | |
wyświetla stronę 3. |
Krok 1 aplikacji jest obsługiwany przez następujący program etape1.php:
<?php
// etape1.php
// konfiguracja
ini_set("register_globals","off");
ini_set("display_errors","off");
// rozpoczęcie sesji
session_start();
$_SESSION["session"]=""; //zresetowanie zmiennej sesji
// przygotowanie strony 1
$requête->nom="";
$requête->erreurs=array();
// wyświetlenie strony 1
include "page1.php";
// koniec
exit(0);
?>
Należy zwrócić uwagę na następujące kwestie:
- aplikacja wymaga śledzenia sesji. Dlatego każdy etap sesji rozpoczyna nową sesję.
- Informacje dotyczące sesji, które należy zachować, będą przechowywane w obiekcie $session.
- Informacje niezbędne do wyświetlenia poszczególnych (trzech) stron aplikacji zostaną umieszczone w obiekcie $requête.
Program page1.php wyświetla informacje zawarte w obiekcie $requête:
<? // page1.php ?>
<html>
<head>
<title>page 1</title>
</head>
<body>
<h3>Page 1/3</h3>
<form name="frmNom" method="POST" action="etape2.php">
<table>
<tr>
<td>Votre nom</td>
<td><input type="text" name="nom" value="<? echo $requête->nom ?>"></td>
</tr>
</table>
<input type="submit" value="Suite">
</form>
<? // błędy?
if (count($requête->erreurs)!=0){
?>
<hr>
<font color="red">
Les erreurs suivantes se sont produites
<ul>
<? for($i=0;$i<count($requête->erreurs);$i++){ ?>
<li><? echo $requête->erreurs[$i] ?>
<? }//for ?>
</ul>
<? }//if ?>
</body>
</html>
- Strona otrzymuje obiekt $requête zawierający dwa pola: nom i erreurs. Wyświetla wartości tych dwóch pól.
- Ponadto zawiera formularz. Jego wartości (nazwa) są przesyłane metodą POST do programu etape2.php:
Aplikacja etape2.php ma za zadanie przetworzyć wartości z formularza na stronie 1 i ponownie wyświetlić stronę 1 w przypadku wystąpienia błędów (błędne imię), a w przeciwnym razie wyświetlić stronę 2 w celu uzyskania wieku.
<?php
// etape2.php
// konfiguracja
ini_set("register_globals","off");
ini_set("display_errors","off");
// rozpoczęcie sesji
session_start();
// zazwyczaj powinien występować parametr „nazwa”
// zapisany w zapytaniu
$requête->nom=$_POST["nom"];
// jeśli nie ma parametrów, wysyłamy stronę 1 bez błędów
if (! isset($requête->nom)){
$requête->nom="";
$requête->erreurs=array();
include "page1.php";
exit(0);
}//if
// jeśli parametr „nazwa” rzeczywiście występuje – sprawdzamy jego poprawność
$page=calculerPage($requête);
// czy wystąpiły błędy?
if(count($page->erreurs)!=0){
// strona 1 z błędami
$requête->erreurs=$page->erreurs;
include "page1.php";
exit(0);
}//if
// brak błędów – zapisujemy nazwę w sesji
unset($session);
$session->nom=$requête->nom;
$_SESSION["session"]=$session;
// wyświetlanie strony 2
$requête->age="";
$requête->erreurs=array();
include "page2.php";
// koniec
exit(0);
// ---------calculerPage
function calculerPage($requête){
// sprawdza poprawność zapytania $requête
// zwraca tablicę błędów w $page->błędy
// na początku nie ma błędów
$page->erreurs=array();
// nazwa nie może być pusta
if (preg_match("/^\s*$/",$requête->nom)){
$page->erreurs[]="Vous n'avez pas indiqué de nom";
}
// powrót do strony
return $page;
}//calculerPage
?>
- etape2 najpierw sprawdza, czy posiada oczekiwany parametr nom. Jeśli tak nie jest, powoduje ponowne wyświetlenie pustej strony1. Taka sytuacja może wystąpić, jeśli funkcja etape2 jest wywoływana bezpośrednio przez klienta, który nie przekazuje jej żadnych parametrów.
- Jeśli parametr nom jest obecny, sprawdzana jest jego poprawność. Odbywa się to za pośrednictwem procedury o nazwie calculerPage, której zadaniem jest utworzenie obiektu $page z polem erreurs, które jest tablicą błędów. Możliwy jest tylko jeden błąd, ale chcieliśmy pokazać, że można obsługiwać listę błędów.
- Jeśli wystąpią błędy, strona page1 jest wyświetlana ponownie wraz z listą błędów.
- Jeśli nie ma błędów, nazwa jest zapisywana w obiekcie $session, który przechowuje dane związane z bieżącą sesją. Następnie wyświetlana jest strona page2.
Program page2.php wyświetla stronę 2:
<? // page2.php ?>
<html>
<head>
<title>page 2</title>
</head>
<body>
<h3>Page 2/3</h3>
<form name="frmAge" method="POST" action="etape3.php">
<table>
<tr>
<td>Nom</td>
<td><font color="green"><? echo $requête->nom ?></font></td>
</tr>
<tr>
<td>Votre âge</td>
<td><input type="text" name="age" size="3" value="<? echo $requête->age ?>"></td>
</tr>
</table>
<input type="submit" value="Suite">
</form>
<? // błędy?
if (count($requête->erreurs)!=0){
?>
<hr>
<font color="red">
Les erreurs suivantes se sont produites
<ul>
<? for($i=0;$i<count($requête->erreurs);$i++){
echo "<li>".$requête->erreurs[$i];
}//dla
?>
</ul>
</font>
<? } ?>
</body>
</html>
Zasada działania tej strony jest bardzo podobna do tej opisanej w przypadku page2.php. Wyświetla zawartość obiektu $requête zawierającego pola nom, age i erreurs. Wyświetla formularz, którego wartości będą przetwarzane przez etape3.php.
Program etape3.php przetwarza zatem wartości z formularza na stronie 2, ograniczone w tym przypadku do wieku:
<?php
// etape3.php
// konfiguracja
ini_set("register_globals","off");
ini_set("display_errors","off");
// rozpoczęcie sesji
session_start();
// pobieramy parametry „nazwa” i „wiek”
$requête->age=$_POST["age"];
$session=$_SESSION["session"];
$requête->nom=$session->nom;
// zazwyczaj powinny być podane imię i wiek
if (! isset($requête->age) || ! isset($requête->nom)){
// jeśli wywołanie jest nieprawidłowe, wysyłamy stronę 1
$_SESSION["session"]=""; // na wszelki wypadek
$requête->nom="";
$requête->erreurs=array();
include "page1.php";
exit(0);
}//if
// parametr „age” jest obecny – sprawdzamy jego poprawność
$page=calculerPage($requête);
// czy wystąpiły błędy?
if(count($page->erreurs)!=0){
// strona 2 z błędami
$requête->erreurs=$page->erreurs;
include "page2.php";
exit(0);
}//if
// brak błędu – zapisanie wieku w sesji
$session->age=$requête->age;
$_SESSION["session"]=$session;
// wyświetlenie strony 3
include "page3.php";
// koniec
exit(0);
// ---------calculerPage
function calculerPage($requête){
// sprawdza poprawność zapytania $requête
// zwraca tablicę błędów w $page->błędy
// na początku nie ma błędów
$page->erreurs=array();
// wiek musi mieć prawidłowy format
if (! preg_match("/^\s*\d{1,3}\s*$/",$requête->age)){
$page->erreurs[]="âge incorrect";
}
// powrót do strony
return $page;
}//calculerPage
?>
- Program rozpoczyna się od pobrania nazwy z sesji (pochodzącej ze strony 1) oraz wieku z formularza na stronie 2. Jeśli brakuje którejkolwiek z tych informacji, wyświetlana jest strona 1.
- Następnie sprawdzana jest poprawność wieku. Jeśli wiek jest nieprawidłowy, ponownie wyświetlana jest strona 2 wraz z listą błędów. Jeśli wiek jest prawidłowy, wyświetlana jest strona 3. Strona ta wyświetla jedynie dwie wartości (imię, wiek) uzyskane z obu formularzy (strona 1, strona 2).

