3. PHP ile web programlamaya giriş
3.1. PHP Programlama
Burada şunu hatırlatmak gerekir ki, PHP tam anlamıyla bir programlama dilidir ve her ne kadar çoğunlukla web uygulamaları geliştirme kapsamında kullanılsa da, başka bağlamlarda da kullanılabilir. http://shiva.istia.univ-angers.fr/~tahe/pub/php/php.pdf adresinde bulunan "PHP Örneklerle" başlıklı belge, dilin temellerini açıklamaktadır. Burada bu bilgilerin zaten bilindiği varsayılmaktadır. Basit bir örnekle, Windows ortamında bir PHP programının çalıştırılma sürecini gösterelim. Aşağıdaki kod, coucou.php adıyla kaydedilmiştir.
Bu program, Windows DOS penceresinde çalıştırılır:
dos>"e:\program files\easyphp\php\php.exe" coucou.php
X-Powered-By: PHP/4.3.0-dev
Content-type: text/html
coucou
PHP yorumlayıcısının varsayılan olarak şunu gönderdiği dikkatinizi çekecektir:
- PHP yorumlayıcısı, php.exe'tir ve normalde yazılımın kurulum dizinindeki <php> klasöründe bulunur.
- HTTP X-Powered-By ve Content-type başlıkları:
- HTTP başlıklarını belgenin geri kalanından ayıran boş satır
- burada echo işlevi tarafından yazılan metinden oluşan belge
3.2. PHP yorumlayıcısının yapılandırma dosyası
PHP yorumlayıcısının davranışı, php.ini adlı bir yapılandırma dosyası ile ayarlanır ve Windows'ta Windows dizininde bulunur. Bu dosya oldukça büyüktür; zira Windows ortamında ve PHP'in 4.2 sürümünde, yaklaşık 1000 satırdan oluşur; neyse ki bunların dörtte üçü yorum satırlarından ibarettir. PHP'in yapılandırma özniteliklerinden birkaçını inceleyelim:
, <? > etiketleri arasına komutlar eklemeye izin verir. off'te ise bunları <?php ... > etiketleri arasına eklemek gerekir | |
on'te, ASP (Active Server Pages) teknolojisi tarafından kullanılan <% =değişken %> sözdizimini kullanmaya olanak tanır | |
HTTP X-Powered-By: PHP/4.3.0-dev başlığının gönderilmesini sağlar. off'te bu başlık kaldırılır. | |
hata izleme kapsamını belirler. Burada, çalışma sırasındaki uyarılar (~E_NOTICE) hariç tüm hatalar (E_ALL) rapor edilecektir | |
on'e, hataları müşteriye gönderilen HTML akışına yerleştirir. Bu hatalar tarayıcıda görüntülenir. Bu seçeneği off olarak ayarlamanız önerilir. | |
hatalar bir dosyada kaydedilecektir | |
en son meydana gelen hatayı $php_errormsg değişkeninde saklar | |
hata kaydetme dosyasını belirler (eğer log_errors=on ise) | |
on'te, bazı değişkenler genel hale gelir. Bir güvenlik açığı olarak kabul edilir. | |
varsayılan olarak HTTP başlığını oluşturur: Content-type: text/html | |
include veya require yönergeleri tarafından gerekli görülen dosyaları aramak üzere taranacak dizinlerin listesi | |
devam eden farklı oturumları kaydeden dosyaların kaydedileceği dizin. İlgili disk, PHP'in kurulduğu disktir. Burada /temp, e:\temp'i ifade eder |
Bu yapılandırma dosyası, yazılan PHP programının taşınabilirliğini etkiler. Nitekim, bir web uygulaması bir web formundaki C alanının değerini almalıysa, register_globals yapılandırma değişkeninin değeri on mi yoksa off mi olduğuna bağlı olarak bunu çeşitli şekillerde yapabilir:
- off: değer, yönteme göre $HTTP_GET_VARS["C"] veya _GET["C"] veya $HTTP_POST_VARS["C"] veya $_POST["C"] olarak alınacaktır; bu, müşterinin form değerlerini göndermek için kullandığı yönteme (GET/POST) bağlıdır
- : yukarıdakiyle aynı, ayrıca $C; çünkü C alanının değeri, alanla aynı adı taşıyan bir değişkende global hale getirilmiştir
Bir geliştirici, kullandığı web/PHP sunucusunun $C değişkenine register_globals'ten on'e kadar değerler atadığı için $C notasyonunu kullanarak bir program yazarsa, bu program, aynı değişkenin off olarak ayarlandığı bir web/PHP sunucusuna taşındığında artık çalışmayacaktır. Bu nedenle, web/PHP sunucusunun yapılandırmasına bağlı işlevleri kullanmaktan kaçınarak program yazmaya özen gösterilmelidir.
3.3. Çalışma sırasında PHP'i yapılandırma
Bir PHP programının taşınabilirliğini artırmak için, PHP'in bazı yapılandırma değişkenlerini kendiniz ayarlayabilirsiniz. Bu değişkenler, programın yürütülmesi sırasında ve yalnızca o program için değiştirilir. Bu süreçte iki işlev yararlıdır:
confVariable yapılandırma değişkeninin değerini döndürür | |
confVariable yapılandırma değişkeninin değerini sabitler |
Aşağıda, track_errors yapılandırma değişkeninin değerinin ayarlandığı bir örnek verilmiştir:
<?php
// track_errors yapılandırma değişkeninin değeri
echo "track_errors=".ini_get("track_errors")."\n";
// bu değerin değiştirilmesi
ini_set("track_errors","off");
// doğrulama
echo "track_errors=".ini_get("track_errors")."\n";
?>
Çalıştırıldığında şu sonuçlar elde edilir:
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
track_errors yapılandırma değişkeninin değeri başlangıçta 1 idi (~on). Bu değeri off olarak değiştiriyoruz. Uygulamamızın belirli yapılandırma değişkeni değerlerine dayanması gerekiyorsa, bu değişkenleri programın içinde başlatmanın daha güvenli olacağını unutmayalım.
3.4. Örneklerin çalıştırılma ortamı
Bu ders notundaki örnekler aşağıdaki yapılandırma ile çalıştırılacaktır:
- Windows 2000 üzerinde PC
- Apache 1.3 sunucusu
- PHP 4.3
Apache sunucusunun yapılandırması httpd.conf dosyasında yapılmıştır. Aşağıdaki satırlar, Apache'ye PHP dosyasını Apache'ye entegre bir modül olarak yüklemesini ve .php dahil olmak üzere belirli uzantılara sahip belgelere yönelik tüm istekleri PHP yorumlayıcısına yönlendirmesini belirtir. Bu, PHP programlarımız için kullanacağımız varsayılan uzantıdır.
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
Ayrıca, Apache için bir poly takma adı tanımladık:
Alias "/poly/" "e:/data/serge/web/php/poly/"
<Directory "e:/data/serge/web/php/poly">
Options Indexes FollowSymLinks Includes
AllowOverride All
#Order allow,deny
Allow from all
</Directory>
<poly> yolunu e:/data/serge/web/php/poly olarak adlandıralım. Bir tarayıcıyla Apache sunucusundan doc.php belgesini istemek istersek, URL http://localhost/poly/doc.php adresini kullanacağız. Apache sunucusu, URL adresinde poly takma adını tanıyacak ve ardından URL /poly/doc.php adresini <poly>\doc.php belgesiyle ilişkilendirecektir.
3.5. İlk örnek
İlk web/php uygulamamızı yazalım. Aşağıdaki metin heure.php dosyasına kaydedilir:
<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>
Bu sayfayı bir tarayıcıyla çağırdığımızda şu sonucu elde ederiz:

Sayfanın dinamik kısmı şu kod tarafından oluşturulmuştur: PHP :
Tam olarak ne oldu? Tarayıcı, URL http://localhost/poly/intro/heure.php adresini talep etti. Web sunucusu (örnekte Apache) bu isteği aldı ve istenen belgenin .php uzantısı nedeniyle bu isteği PHP yorumlayıcısına iletmesi gerektiğini algıladı. Yorumlayıcı daha sonra heure.php belgesini analiz eder ve <?php > etiketleri arasında bulunan tüm kod bölümlerini çalıştırır ve her birini PHP, echo veya print komutlarıyla yazılmış satırlarla değiştirir. Böylece PHP yorumlayıcısı, yukarıdaki kod parçasını çalıştıracak ve onu echo komutuyla yazılan satırla değiştirecektir:
Tüm PHP kod bölümleri yürütüldükten sonra, PHP belgesi, daha sonra müşteriye gönderilen basit bir HTML belgesi haline gelir.
PHP koduyla HTML kodunun birbirine karışmasını mümkün olduğunca önlemeye çalışacağız. Bu amaçla, önceki uygulamayı şu şekilde yeniden yazabiliriz:
<!-- PHP kodu -->
<?php
// anlık saati alır
$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>
Tarayıcıda elde edilen sonuç aynıdır:

İkinci sürüm, PHP kodunda HTML koduna göre daha az kod içerdiği için ilkinden daha iyidir. Böylece sayfanın yapısı daha net bir şekilde görülebilir. PHP kodunu ve HTML kodunu iki farklı dosyaya yerleştirerek bu işlemi daha da ileriye götürebiliriz. PHP kodu, heure3.php dosyasında saklanır:
<!-- kod PHP -->
<?php
// o anki saati alıyoruz
$maintenant=time();
$maintenant=date("j/m/y, h:i:s",$maintenant);
// yanıt görüntülenir
include "heure3-page1.php";
?>
HTML kodu ise heure3-page1.php dosyasında saklanmaktadır:
<!-- 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>
Tarayıcı heure3.php belgesini talep ettiğinde, bu belge PHP yorumlayıcı tarafından yüklenecek ve analiz edilecektir. Şu satıra gelindiğinde
Yorumlayıcı, heure3-page1.php dosyasını heure3.php'in kaynak koduna dahil edecek ve onu çalıştıracaktır. Böylece, sanki aşağıdaki PHP kodu varmış gibi her şey gerçekleşir:
<!-- kod PHP -->
<?php
// anlık saati alır
$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>
Elde edilen sonuç öncekiyle aynıdır:

PHP ve HTML kodlarını farklı dosyalara yerleştirme çözümü daha sonra benimsenecektir. Bu çözümün çeşitli avantajları vardır:
- Müşteriye gönderilen sayfaların yapısı, PHP kodunun içinde kaybolmaz. Böylece, grafik becerileri olan ancak PHP konusunda çok az bilgisi olan bir "web tasarımcısı" tarafından bu sayfaların bakımı yapılabilir.
- PHP kodu, istemcilerin isteklerine yönelik bir "ön uç" görevi görür. Amacı, istemciye yanıt olarak geri gönderilecek sayfa için gerekli verileri hesaplamaktır.
Ancak bu çözümün bir dezavantajı vardır: tek bir belgenin yüklenmesini gerektirmek yerine, birden fazla belgenin yüklenmesini gerektirir; bu da performans kaybına yol açabilir.
3.6. Bir web istemcisi tarafından gönderilen parametreleri almak
3.6.1. POST aracılığıyla
Kullanıcının iki bilgi girmesi gereken aşağıdaki formu ele alalım: bir isim ve bir yaş.

Kullanıcı Nom ve Age alanlarını doldurduktan sonra, submit türündeki Envoyer düğmesine basar. Bunun üzerine formdaki değerler sunucuya gönderilir. Sunucu, formu geri gönderirken, aldığı değerleri listeleyen bir tablo da ekler:

Tarayıcı, aşağıdaki nomage.php uygulamasından formu ister:
<?php
// beklenen parametreler mevcut mu?
$post=isset($_POST["txtNom"]) && isset($_POST["txtAge"]);
if($post){
// istemci tarafından "gönderilen" txtNom ve txtAge parametrelerini alıyoruz
$nom=$_POST["txtNom"];
$age=$_POST["txtAge"];
} else {
$nom="";
$age="";
}//if
// sayfanın görüntülenmesi
include "nomage-p1.php";
?>
Bazı açıklamalar:
- "alan" adı verilen HTML form alanı, GET yöntemi veya POST yöntemi ile sunucuya gönderilebilir. GET yöntemi ile gönderilirse, sunucu bu alanı $_GET["champ"] değişkeninden alabilir;POST yöntemi ile gönderilirse.
- Bir verinin varlığı, isset(veri) işleviyle test edilebilir; bu işlev, veri varsa true, yoksa false değerini döndürür.
- nomage.php uygulaması üç değişken oluşturur: $nom form adı için, $age yaş için ve $post ise değerlerin "gönderilip gönderilmediğini" belirtmek için. Bu üç değişken, nomage-p1.php sayfasına iletilir. Bu sayfanın müşteriye verilecek yanıtın oluşturulmasında rol oynadığına dikkat edilmelidir; ancak müşteri bunun farkında değildir. Müşteri açısından, yanıtı veren uygulama nomage.php'tir.
- Bir müşteri nomage.php uygulamasını ilk kez çağırdığında, $post hatası alınır. Nitekim bu ilk çağrıda, sunucuya hiçbir form değeri iletilmez.
nomage-p1.php sayfası şu şekildedir:
<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
// Gönderilen değerler var mı?
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>
nomage-p1.php uygulaması, frmPersonne formunu gösterir. Bu form şu etiketle tanımlanmıştır:
Etiketin action özniteliği tanımlanmadığından, tarayıcı form verilerini, bu verileri almak için sorguladığı URL'e, yani c.a.d'e iletecektir. nomage.php uygulaması.
nomage.php uygulamasının çağrılma durumlarını iki ayrı durum olarak ele alalım:
- Kullanıcı uygulamayı ilk kez çağırıyor. Dolayısıyla nomage.php uygulaması, nomage-p1.php uygulamasını çağırırken ona şu değerleri sağlıyor ($nom,$age,$post)=("","",false) değerlerini iletir. Bunun üzerine nomage-p1.php uygulaması boş bir form görüntüler.
- Kullanıcı formu doldurur ve Envoyer düğmesini (submit türü) kullanır. Form değerleri (txtNom, txtAge) daha sonra nomage.php uygulamasına "gönderilir" (method="post", <form> içinde) uygulamasına "gönderilir" (action özniteliği <form> içinde tanımlanmamıştır). nomage.php uygulaması ($nom, $age,$post)=(txtNom,txtAge,true) hesaplar ve bunları nomage-p1.php uygulamasına iletir; bu uygulama da önceden doldurulmuş bir form ile alınan değerlerin tablosunu görüntüler.
3.6.2. bir GET aracılığıyla
Form değerlerinin bir GET aracılığıyla sunucuya iletilmesi durumunda, nomage.php uygulaması aşağıdaki nomage2.php uygulamasına dönüşür:
<?php
// beklenen parametreler var mı?
$get=isset($_GET["txtNom"]) && isset($_GET["txtAge"]);
if($get){
// txtNom ve txtAge "GETTés" parametreleri istemci tarafından alınır
$nom=$_GET["txtNom"];
$age=$_GET["txtAge"];
} else {
$nom="";
$age="";
}//eğer
// sayfa görüntülenir
include "nomage-p2.php";
?>
nomage-p2.php uygulaması, aşağıdaki ayrıntılar dışında nomage-p1.php uygulamasıyla aynıdır:
- form etiketi değiştirilmiştir:
- Uygulama artık $post yerine $get değişkenini alıyor:
Çalıştırma sırasında, forma değerler girilip sunucuya gönderildiğinde, tarayıcı URL alanında, değerlerin GET yöntemiyle gönderildiğini gösterir:
![]()
3.7. Bir web istemcisi tarafından gönderilen HTTP başlıklarını alma
Bir tarayıcı bir web sunucusuna istek gönderdiğinde, sunucuya bir dizi HTTP başlığı gönderir. Bazen bu başlıklara erişmek yararlı olabilir. İlk olarak, $_SERVER ilişkisel tablosundan yararlanabiliriz. Bu tablo, web sunucusu tarafından sağlanan çeşitli bilgileri içerir; bunların arasında, istemci tarafından sağlanan HTTP başlıkları da bulunur. $_SERVER tablosundaki tüm değerleri görüntüleyen aşağıdaki programı ele alalım:
<?php
// web sunucusuyla ilgili değişkenleri gösterir
// düz metin gönderiliyor
header("Content-type: text/plain");
// $_SERVER ilişkisel dizisini tarama
reset($_SERVER);
while (list($clé,$valeur)=each($_SERVER)){
echo "$clé : $valeur\n";
}//while
?>
Bu kodu headers.php olarak kaydedelim ve bu URL dosyasını bir tarayıcıyla açalım:

Tarayıcı tarafından gönderilen HTTP başlıkları da dahil olmak üzere bir dizi bilgi elde ediyoruz. Bunlar, HTTP ile başlayan anahtarlara ait değerlerdir. Yukarıda elde edilen bilgilerin bazılarını ayrıntılı olarak inceleyelim:
web istemcisi tarafından kabul edilen belge türleri | |
belgelerde kabul edilen karakter türleri | |
belgeler için kabul edilen kodlama türleri | |
belgeler için kabul edilen dil türleri | |
sunucuyla bağlantı türü. Keep-Alive: sunucu, yanıtını verdikten sonra bağlantıyı açık tutmalıdır | |
? bağlantının açık kalabileceği maksimum süre | |
istemci tarafından sorgulanan ana bilgisayar | |
istemcinin kimliği | |
Müşterinin adresi IP | |
müşteri tarafından kullanılan iletişim bağlantı noktası | |
sunucu tarafından kullanılan protokol HTTP | |
istemci tarafından kullanılan sorgulama yöntemi (GET veya POST) | |
istenen URL'in arkasına yerleştirilen ?param1=val1¶m2=val2&... isteği (GET yöntemi) |
Önceki programın kodunu biraz değiştirerek, yalnızca HTTP başlıklarını alabiliriz:
<?php
// web sunucusuyla ilgili değişkenleri görüntüler
// düz metin gönderilir
header("Content-type: text/plain");
// $_SERVER ilişkisel dizisini tarama
reset($_SERVER);
while (list($clé,$valeur)=each($_SERVER)){
// HTTP başlığı mı?
if(strtolower(substr($clé,0,4))=="http")
echo substr($clé,5)." : $valeur\n";
}//while
?>
Tarayıcıda elde edilen sonuç şöyledir:

Kesin bir HTTP başlığı istiyorsak, örneğin $_SERVER["HTTP_ACCEPT"] yazmalıyız.
3.8. Ortam bilgilerini alma
Web/PHP sunucusu, yürütme ortamının çeşitli özelliklerini depolayan $_ENV tablosu aracılığıyla öğrenebileceğimiz bir ortamda çalışır. Aşağıdaki env1.php uygulamasını ele alalım:
<?php
// web sunucusuyla ilgili değişkenleri görüntüler
// düz metin gönderilir
header("Content-type: text/plain");
// $_ENV ilişkisel dizisini tarar
reset($_ENV);
while (list($clé,$valeur)=each($_ENV)){
echo "$clé : $valeur\n";
}//while
?>
Bir tarayıcıda şu sonucu verir (kısmi görünüm):

Yukarıda örneğin, web/php sunucusunun Windows altında çalıştığı görülmektedir.
3.9. Örnekler
3.9.1. Dinamik form oluşturma - 1
Örnek olarak, tek bir denetim içeren bir formun oluşturulmasını ele alalım: bir açılır menü. Bu açılır menünün içeriği, bir diziden alınan değerlerle dinamik olarak oluşturulur. Gerçek hayatta bu değerler genellikle bir veritabanından alınır. Form şu şekildedir:

Yukarıdaki örnekte Envoyer komutunu çalıştırırsak, şu yanıtı alırız:

İlk formun kodu, oluşturulduktan sonra şu şekildedir:
<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>
PHP uygulaması, hem başlangıç formunu (değerler listesi) almak hem de bu formdaki değerleri işlemek ve yanıtı (seçilen değer) sağlamak için çağrılan bir ana sayfadan (valeurs.php) oluşur. Uygulama iki farklı sayfa oluşturur:
- valeurs-p1.php programı tarafından oluşturulacak ilk form sayfası
- kullanıcıya verilen yanıt sayfası, valeurs-p2.php programı tarafından oluşturulur
valeurs.php uygulaması şöyledir:
<?php
// değerler dizisi
$valeurs=array("un","deux","trois","quatre","cinq","six","sept","huit","neuf","dix");
// beklenen parametreler var mı
$requêteVide=! isset($_POST["cmbValeurs"]);
// kullanıcının seçimini alıyoruz
if ($requêteVide){
// ilk istek
include "valeurs-p1.php";
}else{
// POST'e yanıt
$choix=$_POST["cmbValeurs"];
include "valeurs-p2.php";
}
?>
Bu program, değer tablosunu tanımlar ve müşterinin isteği boşsa başlangıç formunu oluşturmak için valeurs-p1.php'i, geçerli bir istek varsa yanıtı oluşturmak için valeurs-p2.php'i çağırır. valeurs1-php programı şöyledir:
<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";
}//for
?>
</select>
<input type="submit" value="Envoyer" name="cmdEnvoyer">
</form>
</body>
</html>
Kombinasyon menüsünün değer listesi, valeurs.php tarafından iletilen $valeurs tablosundan dinamik olarak oluşturulur. valeurs-p2.php programı şu yanıtı oluşturur:
<html>
<head>
<title>réponse</title>
</head>
<body>
<h2>Vous avez choisi le nombre <?php echo $choix ?></h2>
</body>
</html>
Burada, yine valeurs.php tarafından aktarılan $choix değişkeninin değerini görüntülemekle yetiniyoruz.
3.9.2. Dinamik form oluşturma - 2
Önceki örneği şu şekilde değiştirerek ele alıyoruz. Sunulan form her zamanki gibidir:

Peki yanıt farklı mı?

Yanıtta, form geri gönderilir ve kullanıcının seçtiği sayı formun altında belirtilir. Ayrıca, bu sayı, yanıtta görüntülenen listede seçili olarak görünen sayıdır.
valeurs.php kodunun kodu şöyledir:
<?php
// yapılandırma
ini_set("register_globals","off");
// değer tablosu
$valeurs=array("un","deux","trois","quatre","cinq","six","sept","huit","neuf","dix");
// kullanıcının olası seçimi alınır
$choix=$_POST["cmbValeurs"];
// yanıt görüntülenir
include "valeurs-p1.php";
?>
Burada, küresel değişkenlerin olmaması için PHP'i özenle yapılandırdığımızı fark edeceksiniz. Küresel değişkenler güvenlik sorunlarına yol açtığı için bu genellikle alınması gereken bir önlemdir. Bir alternatif ise, kullanılan tüm değişkenleri başlatmaktır. Bu, aynı isimde olası bir küresel değişkeni “üzerine yazma” etkisine yol açacaktır.
Form sayfası, valeurs-p1.php tarafından görüntülenir:
<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++){
// mevcut seçenek, kullanıcının seçtiği seçeneğe eşitse, onu seç
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
// sayfa devamı
if(isset($choix)){
echo "<hr>\n";
echo "<h3>Vous avez choisi le nombre $choix</h3>\n";
}
?>
</body>
</html>
Sayfa oluşturma programı, valeurs.php programı tarafından aktarılan $choix değişkenine dayanmaktadır. Burada, sayfanın HTML yapısının, PHP koduyla ciddi şekilde "kirlenmeye" başladığını belirtelim. valeurs.php ön ucu, aşağıdaki yeni sürümde gösterildiği gibi daha fazla iş yapabilir:
<?php
// yapılandırma
ini_set("register_globals","off");
// değer tablosu
$valeurs=array("un","deux","trois","quatre","cinq","six","sept","huit","neuf","dix");
// kullanıcının olası seçimini alın
$choix=$_POST["cmbValeurs"];
// görüntülenecek değerlerin listesi hesaplanır
$HTMLvaleurs="";
for($i=0;$i<count($valeurs);$i++){
// mevcut seçenek, kullanıcının seçtiği seçeneğe eşitse, onu seç
if (isset($choix) && $choix==$valeurs[$i])
$HTMLvaleurs.="<option selected>$valeurs[$i]</option>\n";
else $HTMLvaleurs.="<option>$valeurs[$i]</option>\n";
}//for
// sayfanın ikinci kısmı hesaplanır
$HTMLpart2="";
if(isset($choix)){
$HTMLpart2="<hr>\n";
$HTMLpart2.="<h3>Vous avez choisi le nombre $choix</h3>\n";
}//eğer
// yanıt görüntülenir
include "valeurs-p2.php";
?>
Sayfa artık aşağıdaki valeurs-p2.php programı tarafından oluşturulmaktadır:
<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
// değer listesi görüntülenir
echo $HTMLvaleurs;
?>
</select>
<input type="submit" value="Envoyer" name="cmdEnvoyer">
</form>
<?php
// 2. bölüm görüntülenir
echo $HTMLpart2;
?>
</body>
</html>
HTML kodu artık PHP kodunun büyük bir kısmından arındırılmıştır. Ancak, bir müşterinin talebini analiz edip işleyen bir ön uç programı ile, ön uçtan iletilen verilerle yapılandırılmış sayfaları görüntülemekle görevli programlara ayrılmanın amacını hatırlatmak gerekir: bu, PHP geliştiricisinin işini grafik tasarımcının işinden ayırmaktır. Geliştirici PHP ön uç üzerinde çalışırken, grafik tasarımcı web sayfaları üzerinde çalışır. Yeni sürümümüzde, grafik tasarımcı artık örneğin sayfanın 2. bölümü üzerinde çalışamaz, çünkü bu bölümün koduna (HTML) erişimi yoktur. İlk sürümde ise bunu yapabilirdi. Dolayısıyla, bu iki yöntemden hiçbiri mükemmel değildir.
3.9.3. Dinamik form oluşturma - 3
Yukarıdaki sorunla aynı sorunu ele alıyoruz, ancak bu sefer değerler bir veritabanından alınmaktadır. Örneğimizde bu veritabanı MySQL'tir:
- veritabanının adı dbValeurs
- veritabanının sahibi admDbValeurs'tir ve şifresi mdpDbValeurs'tir
- veritabanında tvaleurs adlı tek bir tablo bulunmaktadır
- bu tablonun "değer" adlı tek bir tamsayı alanı vardır
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)
Bir veritabanı kullanan uygulamada genellikle aşağıdaki adımlar bulunur:
- SGBD'e bağlanma
- SQL sorgularının SGBD veritabanına gönderilmesi
- Bu sorguların sonuçlarının işlenmesi
- SGBD ile bağlantının kapatılması
- ve 3. adımlar tekrar tekrar gerçekleştirilir; bağlantı ancak veritabanının kullanımı sona erdiğinde kesilir. Bu, etkileşimli bir şekilde veritabanı kullanmış olan herkes için nispeten klasik bir akış şemasıdır. Aşağıdaki tablo, PHP komutunu kullanarak bu farklı işlemleri gerçekleştirmek için gerekli talimatları vermektedir:
$connexion=mysql_pconnect($hote,$user,$pwd) $connexion=mysql_connect($hote,$user,$pwd) $hote: SGBD ve MySQL'in çalıştığı makinenin internet adı. Nitekim, uzaktaki SGBD ve MySQL ile çalışmak mümkündür. $user: SGBD ve MySQL'te bilinen bir kullanıcının adı $pwd: şifresi $connexion: Oluşturulan bağlantı mysql_pconnect, SGBD ve MySQL ile kalıcı bir bağlantı kurar. Bu tür bir bağlantı, komut dosyasının sonunda kapatılmaz. Açık kalır. Böylece, SGBD ile yeni bir bağlantı açılması gerektiğinde, PHP aynı kullanıcıya ait mevcut bir bağlantı arar. Bulursa, onu kullanır. Bu sayede zaman kazanılır. mysql_connect, kalıcı olmayan bir bağlantı oluşturur; dolayısıyla SGBD ve MySQL ile yapılan iş bittiğinde bu bağlantı kapatılır. | |
$résultats=mysql_db_query($base,$requête,$connexion) $base: üzerinde çalışacağımız MySQL veritabanı $requête: SQL sorgusu (insert, delete, update, select, ...) $connexion: SGBD ile MySQL arasındaki bağlantı $résultats: sorgu sonuçları – sorgunun bir select mi yoksa güncelleme işlemi (insert, update, delete, ...) mi olduğuna göre farklılık gösterir | |
$résultats=mysql_db_query($base,"select ...",$connexion) Bir select sorgusunun sonucu bir tablodur, yani satır ve sütunlardan oluşan bir kümedir. Bu tabloya $résultats aracılığıyla erişilebilir. $ligne=mysql_fetch_row($résultats) komutu, tablodan bir satırı okur ve bunu $ligne'e bir dizi biçiminde yerleştirir. Böylece $ligne[i], alınan satırın i. sütunudur. mysql_fetch_row işlevi tekrar tekrar çağrılabilir. Her seferinde, $résultats tablosundan yeni bir satır okur. Tablonun sonuna ulaşıldığında, işlev false değerini döndürür. Böylece $résultats tablosu şu şekilde kullanılabilir: while($satır=mysql_fetch_row($sonuçlar)){ // geçerli satırı işler $satır }//while | |
$résultats=mysql_db_query($base,"insert ...",$connexion) $résultats değeri, işlemin başarılı olup olmadığına göre doğru veya yanlış olur. İşlem başarılı olursa, mysql_affected_rows işlevi, güncelleme işlemiyle değiştirilen satır sayısını öğrenmenizi sağlar. | |
mysql_close($bağlantı) $connexion: SGBD'e bir bağlantı MySQL |
valeurs.php ön uç kodunun son hali şu şekildedir:
<?php
// yapılandırma
ini_set("register_globals","off");
ini_set("display_errors","off");
ini_set("track_errors","on");
// değer tablosu
list($erreur,$valeurs)=getValeurs();
// Hata mı oluştu?
if($erreur){
// hata sayfası görüntüleme
include "valeurs-err.php";
// son
return;
}//if
// kullanıcının seçimini alınıyor
$choix=$_POST["cmbValeurs"];
// görüntülenecek değerlerin listesi hesaplanır
$HTMLvaleurs="";
for($i=0;$i<count($valeurs);$i++){
// mevcut seçenek, kullanıcının seçtiği seçeneğe eşitse, bu seçenek seçilir
if (isset($choix) && $choix==$valeurs[$i])
$HTMLvaleurs.="<option selected>$valeurs[$i]</option>\n";
else $HTMLvaleurs.="<option>$valeurs[$i]</option>\n";
}//for
// sayfanın ikinci kısmı hesaplanır
$HTMLpart2="";
if(isset($choix)){
$HTMLpart2="<hr>\n";
$HTMLpart2.="<h3>Vous avez choisi le nombre $choix</h3>\n";
}//eğer
// yanıt görüntülenir
include "valeurs-p1.php";
// son
return;
// ------------------------------------------------------------------------
function getValeurs(){
// bir veritabanından değerleri alır MySQL
$user="admDbValeurs";
$pwd="mdpDbValeurs";
$db="dbValeurs";
$hote="localhost";
$table="tvaleurs";
$champ="valeur";
// sunucuya kalıcı bir bağlantı açılır MySQL
// ya da aksi takdirde normal bir bağlantı
($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.");
// değerleri alır
$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.");
// değerler bir diziye eklenir
$valeurs=array();
while($ligne=mysql_fetch_row($selectValeurs)){
$valeurs[]=$ligne[0];
}//while
// bağlantının kapatılması (eğer kalıcı bir bağlantıysa, aslında kapatılmayacaktır)
mysql_close($connexion);
// sonucun döndürülmesi
return array("",$valeurs);
}//getValeurs
?>
Bu sefer, açılır menüye eklenecek değerler bir tablo tarafından değil, getValeurs() işlevi tarafından sağlanmaktadır. Bu işlev:
- kayıtlı bir kullanıcı adı ve şifresini ileterek mySQL sunucusuyla kalıcı bir bağlantı (mysql_pconnect) açar.
- Bağlantı kurulduktan sonra, dbValeurs veritabanındaki tvaleurs tablosunda bulunan değerleri almak için select sorgusu gönderilir.
- select sorgusunun sonucu, $valeurs tablosuna yerleştirilir ve bu tablo çağıran programa döndürülür.
- İşlev aslında iki sonuç içeren bir dizi ($erreur, $valeurs) döndürür; bu dizinin ilk elemanı, varsa bir hata mesajıdır, aksi takdirde boş bir dizedir.
- Çağıran program, hata olup olmadığını kontrol eder ve varsa valeurs-err.php sayfasını görüntüler. Bu sayfa şöyledir:
<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>
- Hata oluşmamışsa, çağıran program $valeurs dizisindeki değerlere erişebilir. Dolayısıyla, önceki soruna geri dönülür.
İşte iki yürütme örneği:
- hata ile

- hatasız

3.9.4. Dinamik form oluşturma - 4
Önceki örnekte, SGBD değerini değiştirirsek ne olur? Örneğin, MySQL'ten Oracle'a veya SQL Server'a geçersek ne olur? Değerleri sağlayan getValeurs() işlevini yeniden yazmak gerekir. Listede görüntülenecek değerleri almak için gerekli kodu bir işlevde toplamış olmanın avantajı, değiştirilmesi gereken kodun programın her yerine dağılmamış, net bir şekilde belirlenmiş olmasıdır. getValeurs() fonksiyonu, kullanılan SGBD'ten bağımsız olacak şekilde yeniden yazılabilir. Bunun için fonksiyonun, doğrudan SGBD ile değil, SGBD'in ODBC sürücüsüyle çalışması yeterlidir.
Piyasada çok sayıda veritabanı bulunmaktadır. MS Windows altında veritabanlarına erişimi standartlaştırmak amacıyla Microsoft, ODBC (Open DataBase Connectivity) adlı bir arayüz geliştirmiştir. Bu katman, her bir veritabanının kendine özgü özelliklerini standart bir arayüz altında gizler. Windows ortamında, veritabanlarına erişimi kolaylaştıran çok sayıda ODBC sürücüsü bulunmaktadır. Aşağıda, örneğin bir Windows makinesine yüklenmiş ODBC sürücülerinin bir listesi yer almaktadır:

SGBD ve MySQL sürücülerinin de birer ODBC sürücüsü vardır. ODBC sürücülerine dayanan bir uygulama, yeniden yazmaya gerek kalmadan yukarıdaki herhangi bir veritabanını kullanabilir.
![]() |
MySQL ve dbValeurs veritabanlarımıza ODBC sürücüsü aracılığıyla erişilebilir hale getirelim. Aşağıdaki prosedür Windows 2000 için geçerlidir. Win9x sistemlerinde prosedür çok benzerdir. ODBC kaynak yöneticisini etkinleştirin:

[Add] düğmesini kullanarak yeni bir veri kaynağı ODBC ekleyin:

ODBC MySQL sürücüsünü seçin, [Terminer] adımını uygulayın ve ardından veri kaynağının özelliklerini girin:

ODBC veri kaynağına verilen ad (odbc-değerler) | |
SGBD MySQL veritabanı kaynağını barındıran ve yöneten makinenin adı (localhost) | |
veri kaynağı olan MySQL veritabanının adı (dbValeurs) | |
Yönetilecek veritabanı (admDbValeurs) üzerinde yeterli erişim haklarına sahip bir kullanıcı: MySQL | |
şifresi (mdpDbValeurs) |
PHP, ODBC sürücüleri ile çalışabilir. Aşağıdaki tabloda bilinmesi gereken yararlı işlevler verilmiştir:
$connexion=odbc_pconnect($dsn,$user,$pwd) $connexion=odbc_connect($dsn,$user,$pwd) $dsn: DSN (Veri Kaynağı Adı) adlı, SGBD'in çalıştığı makinenin adı $user: SGBD tarafından tanınan bir kullanıcının adı $pwd: Bu kullanıcının şifresi $connexion: Oluşturulan bağlantı odbc_pconnect, SGBD ile kalıcı bir bağlantı oluşturur. Bu tür bir bağlantı, komut dosyasının sonunda kapatılmaz. Açık kalır. Böylece, SGBD ile yeni bir bağlantı açılması gerektiğinde, PHP aynı kullanıcıya ait mevcut bir bağlantı arar. Bulursa, onu kullanır. Bu sayede zaman kazanılır. odbc_connect, kalıcı olmayan bir bağlantı oluşturur; dolayısıyla SGBD ile yapılan iş bittiğinde bu bağlantı kapatılır. | |
$requêtePréparée=odbc_prepare($connexion,$requête) $requête: bir SQL sorgusu (ekle, sil, güncelle, seç, ...) $connexion: SGBD'e bağlantı $requête sorgusunu analiz eder ve yürütülmesi için hazırlar. Bu şekilde "hazırlanan" sorgu, $requêtePréparée sonucu ile referanslanır. Bir sorguyu yürütülmesi için hazırlamak zorunlu değildir, ancak sorgu analizi yalnızca bir kez yapıldığından performansı artırır. Ardından, hazırlanan sorgunun yürütülmesi istenir. Hazırlanmamış bir sorgunun yürütülmesi tekrar tekrar istenirse, sorgunun analizi her seferinde yapılır ve bu gereksiz bir işlemdir. Hazırlandıktan sonra sorgu şu komutla yürütülür: $res=odbc_execute($requêtePréparée) tarafından yürütülür; sorgunun yürütülmesinin başarılı olup olmadığına göre doğru veya yanlış değerini verir | |
Bir select sorgusunun sonucu bir tablodur, yani satır ve sütunlardan oluşan bir kümedir. Bu tabloya $requêtePréparée aracılığıyla erişilebilir. $res=odbc_fetch_row($requêtePréparée), select sorgusunun sonuç tablosundan bir satır okur. Sorgunun yürütülmesinin başarılı olup olmadığına göre true veya false değerini döndürür. Alınan satırın öğelerine odbc_result işlevi aracılığıyla erişilebilir: $val=odbc_result($requêtePréparée,i): az önce okunan satırın i numaralı sütunu $val=odbc_result($requêtePréparée,"nomColonne"): az önce okunan satırın nomColonne sütunu odbc_fetch_row işlevi tekrar tekrar çağrılabilir. Her çağrıldığında, sonuç tablosundan yeni bir satır okur. Tablonun sonuna ulaşıldığında, işlev false değerini döndürür. Böylece sonuç tablosu şu şekilde kullanılabilir: | |
odbc_close($bağlantı) $connexion: SGBD'e bir bağlantı MySQL |
ODBC veritabanındaki değerleri almakla görevli getValeurs() işlevi şöyledir:
// ------------------------------------------------------------------------
function getValeurs(){
// değerleri bir veritabanından alır MySQL
$user="admDbValeurs";
$pwd="mdpDbValeurs";
$db="dbValeurs";
$dsn="odbc-valeurs";
$table="tvaleurs";
$champ="valeur";
// sunucuya kalıcı bir bağlantı açar MySQL
// ya da aksi takdirde normal bir bağlantı
($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.");
// değerlerin alınması
$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.");
// değerler bir diziye eklenir
$valeurs=array();
while(odbc_fetch_row($selectValeurs)){
$valeurs[]=odbc_result($selectValeurs,$champ);
}//while
// bağlantının kapatılması (eğer kalıcı bir bağlantıysa, aslında kapatılmayacaktır)
odbc_close($connexion);
// sonucun döndürülmesi
return array("",$valeurs);
}//getValeurs
?>
odbc-valeurs veritabanını etkinleştirmeden yeni uygulamayı çalıştırırsak, şu sonuç elde edilir:

ODBC sürücüsü tarafından döndürülen hata kodunun (odbc_error()=S1000) pek açıklayıcı olmadığı dikkat çekmektedir. odbc-valeurs veritabanı kullanılabilir hale getirildiğinde, önceki ile aynı sonuçlar elde edilir.
Sonuç olarak, bu çözümün uygulama bakımı açısından uygun olduğu söylenebilir. Nitekim, veritabanında bir değişiklik yapılması gerektiğinde, uygulamanın değiştirilmesine gerek kalmayacaktır. Sistem yöneticisi, yeni veritabanı için basitçe ODBC adlı yeni bir veri kaynağı oluşturacaktır. Yine bakım açısından, veritabanına erişim parametrelerini ($dsn, $user, $pwd) ayrı bir dosyaya yerleştirmek ve uygulamanın başlangıçta bu dosyayı (include) yüklemesini sağlamak iyi bir fikir olacaktır.
3.9.5. Bir formdan değerleri almak
Web istemcisi tarafından gönderilen bir formun değerlerini daha önce birçok kez aldık. Aşağıdaki örnek, en yaygın HTML bileşenlerini bir araya getiren bir formu gösterir ve istemci tarayıcısı tarafından gönderilen parametreleri almaya yöneliktir. Form şu şekildedir:

Form önceden doldurulmuş olarak gelir. Ardından kullanıcı formda değişiklik yapabilir:

[Envoyer] düğmesine tıklarsa, sunucu ona formdaki değerlerin listesini geri gönderir:

Form, HTML statik bir sayfadır balises.html:
<html>
<head>
<title>balises</title>
<script language="JavaScript">
function effacer(){
alert("Vous avez cliqué sur le bouton Effacer");
}//silme
</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>
Aşağıdaki tablo, bu belgedeki farklı etiketlerin işlevlerini ve bir formdaki farklı bileşen türleri için PHP tarafından alınan değeri göstermektedir. HTML C adlı bir alanın değeri, bir POST veya bir GET aracılığıyla gönderilebilir. İlk durumda bu değer $_GET["C"] değişkeninde, ikinci durumda ise $_POST["C"] değişkeninde alınır. Aşağıdaki tablo, POST kullanıldığını varsaymaktadır.
Denetim | HTML etiketi | PHP tarafından alınan değer |
<form method="POST" > | ||
<input type="text" name="txtSaisie" size="20" value="birkaç kelime"> | $_POST["txtSaisie"]: formun txtSaisie alanında bulunan değer | |
<input type="password" name="txtMdp" size="20" value="unMotDePasse"> | $_POST["txtmdp"]: Formdaki txtMdp alanında bulunan değer | |
<textarea rows="2" name="areaSaisie" cols="20"> satır1 satır2 satır3 </textarea> | $_POST["areaSaisie"]: areaSaisie alanında tek bir karakter dizisi biçiminde bulunan satırlar: "ligne1\r\nligne2\r\nligne3". Satırlar, "\r\n" dizisiyle birbirinden ayrılır. | |
<input type="radio" value="Evet" name="R1">Evet <input type="radio" name="R1" value="hayır" checked>Hayır | $_POST["R1"]: Duruma göre "evet" veya "hayır" olarak işaretlenen radyo düğmesinin değeri (=value). | |
<input type="checkbox" name="C1" value="bir">1 <input type="checkbox" name="C2" value="iki" checked>2 <input type="checkbox" name="C3" value="üç">3 | $_POST["C1"]: kutucuk işaretlendiğinde değer (=value); aksi takdirde değişken mevcut değildir. Dolayısıyla, C1 kutucuğu işaretlenmişse, $_POST["C1"] değeri "bir" olur; aksi takdirde $_POST["C1"] mevcut değildir. | |
<select size="1" name="cmbValeurs"> <option>seçim1</option> <option selected>seçenek2</option> <option>seçenek3</option> </select> | $_POST["cmbValeurs"] : listeden seçilen seçenek, örneğin "seçenek3". | |
<select size="3" name="lst1"> <option selected>liste1</option> <option>liste2</option> <option>liste3</option> <option>liste4</option> <option>liste5</option> </select> | $_POST["lst1"]: listeden seçilen seçenek, örneğin "liste5". | |
<select size="3" name="lst2[]" multiple> <option>liste1</option> <option>liste2</option> <option selected>liste3</option> <option>liste4</option> <option>liste5</option> </select> | $_POST["lst2"]: listeden seçilen seçeneklerin tablosu; örneğin, ["liste3,"liste5"]. Bu özel durum için HTML etiketinin kendine özgü sözdizimine dikkat edilmelidir: lst2[]. | |
<input type="hidden" name="secret" value="uneValeur"> | $_POST["secret"]: alanın değeri (=value), burada "uneValeur". |
Örneğimizde, formdaki değerler parameters.php programına aktarılır:
Bu programın kodu şöyledir:
<?php
// yapılandırma
ini_set("register_globals","off");
ini_set("display_errors","off");
// çağırma yöntemi
$méthode=$_SERVER["REQUEST_METHOD"];
// parametrelerin alınması
// bu, parametrelerin gönderilme yöntemine bağlıdır
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"];
// istek geçerli mi?
$requêteValide=isset($R1) && (isset($C1) || isset($C2) || isset($C3))
&& isset($txtSaisie) && isset($txtMdp) && isset($areaSaisie)
&& isset($cmbValeurs) && isset($lst1) && isset($lst2)
&& isset($secret);
// sayfa görüntüleme
if ($requêteValide)
include "parameters-p1.php";
else include "balises.html";
?>
Bu programın bazı noktalarını ayrıntılı olarak inceleyelim:
- Uygulama, form değerlerinin sunucuya aktarılma biçiminden bağımsızdır. Olası iki durumda da (GET ve POST), aktarılan değerler sözlüğüne $param ile başvurulur.
- areaSaisie alanının içeriği olan "satır1\r\nsatır2\r\n..." kullanılarak, explode("\r\n", $param["areaSaisie"]) komutuyla bir dize tablosu oluşturulur. Böylece [ligne1,ligne2,...] dizisi elde edilir. Bu diziden implode işlevi kullanılarak "ligne1<br>ligne2<br>..." dizesi oluşturulur.
- Çoklu seçim listesi lst2'nin değeri bir dizidir; örneğin, ["option3","option5"]. Bundan yola çıkarak, implode işleviyle "option3<br>option5" karakter dizesi oluşturulur.
- Uygulama, tüm parametrelerin ayarlandığını kontrol eder. Burada, herhangi bir URL işlevinin manuel olarak veya program yoluyla çağrılabileceğini ve beklenen parametrelerin her zaman mevcut olmayabileceğini unutmamak gerekir. Parametreler eksikse balises.html sayfası, aksi takdirde parameters-p1.php sayfası görüntülenir. Bu sayfa, parameters.php'te alınan ve hesaplanan değerleri bir tabloda gösterir:
<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. Oturum Takibi
3.10.1. Sorun
Bir web uygulaması, sunucu ile istemci arasında birden fazla form alışverişinden oluşabilir. Bu durumda işlem şu şekilde gerçekleşir:
1. adım
- C1 istemcisi sunucuyla bir bağlantı kurar ve ilk isteğini gönderir.
- Sunucu, F1 formunu C1 istemcisine gönderir ve 1. adımda açılan bağlantıyı kapatır.
2. adım
- C1 istemcisi formu doldurur ve sunucuya geri gönderir. Bunu yapmak için tarayıcı, sunucuyla yeni bir bağlantı kurar.
- Sunucu, 1 numaralı formdaki verileri işler, bu verilerden I1 bilgilerini hesaplar, F2 formunu C1 istemcisine gönderir ve 3 numaralı adımda açık olan bağlantıyı kapatır.
3. adım
- 3. ve 4. adımların döngüsü, 5. ve 6. adımlarda tekrarlanır. 6. adımın sonunda, sunucu F1 ve F2 adlı iki formu almış olacak ve bunlardan I1 ve I2 bilgilerini hesaplamış olacaktır.
Buradaki sorun şudur: Sunucu, I1 ve I2 bilgilerini C1 istemcisiyle ilişkilendirerek nasıl saklayabilir? Bu soruna, C1 istemcisinin oturum takibi denir. Sorunun kökenini anlamak için, aynı anda birden fazla istemciye hizmet veren TCP-IP sunucu uygulamasının şemasını inceleyelim:
![]() |
Klasik bir TCP-IP istemci-sunucu uygulamasında:
- istemci, sunucuyla bir bağlantı kurar
- bu bağlantı üzerinden sunucuyla veri alışverişi yapar
- bağlantı, iki taraftan biri tarafından kapatılır
Bu mekanizmanın iki önemli noktası şunlardır:
- her istemci için tek bir bağlantı kurulur
- bu bağlantı, sunucunun müşterisiyle olan iletişim süresince kullanılır
Sunucunun belirli bir anda hangi istemciyle çalıştığını bilmesini sağlayan şey, onu istemcisiyle bağlayan bağlantı, başka bir deyişle "kanal"dır. Bu kanal belirli bir istemciye ayrıldığından, bu kanaldan gelen her şey o istemciden gelir ve bu kanala gönderilen her şey aynı istemciye ulaşır.
HTTP istemci-sunucu mekanizması yukarıdaki şemayı takip eder; ancak istemci-sunucu iletişiminin, istemci ile sunucu arasında tek bir veri alışverişiyle sınırlı olması bu mekanizmanın ayırt edici özelliğidir:
- istemci sunucuya bir bağlantı açar ve isteğini iletir
- sunucu yanıtını verir ve bağlantıyı kapatır
T1 zamanında, bir C istemcisi sunucuya bir istek gönderirse, tek seferlik istek-yanıt alışverişi için kullanılacak olan C1 bağlantısını alır. T2 zamanında, aynı istemci sunucuya ikinci bir istek gönderirse, C1 bağlantısından farklı olan C2 bağlantısını alır. Sunucu açısından, kullanıcı C'nin bu ikinci isteği ile ilk isteği arasında hiçbir fark yoktur: her iki durumda da sunucu, bu istemciyi yeni bir istemci olarak değerlendirir. Müşteri C’nin sunucuya yaptığı farklı bağlantılar arasında bir bağlantı kurulabilmesi için, müşteri C’nin sunucu tarafından bir “düzenli kullanıcı” olarak “tanınması” ve sunucunun bu düzenli kullanıcı hakkında sahip olduğu bilgileri alması gerekir.
Şu şekilde çalışan bir yönetim sistemi düşünelim:
- Tek bir kuyruk vardır
- Birden fazla gişe vardır. Dolayısıyla birden fazla müşteri aynı anda hizmet alabilir. Bir gişe boşaldığında, bir müşteri kuyruktan çıkarak o gişede hizmet alır
- Müşteri ilk kez geliyorsa, gişe görevlisi ona üzerinde bir numara yazılı bir jeton verir. Müşteri yalnızca bir soru sorabilir. Cevabını aldıktan sonra gişeden ayrılmalı ve kuyruğun sonuna geçmelidir. Gişe görevlisi, bu müşterinin bilgilerini jeton numarasıyla eşleşen bir dosyaya kaydeder.
- Sırası tekrar geldiğinde, müşteri önceki seferkinden farklı bir gişe görevlisi tarafından hizmet alabilir. Gişe görevlisi, müşteriden jetonunu ister ve jeton numarasını taşıyan dosyayı alır. Müşteri yine bir talepte bulunur, yanıt alır ve dosyaya yeni bilgiler eklenir.
- ve böyle devam eder... Zamanla, müşteri tüm taleplerine yanıt alır. Farklı talepler arasındaki takip, jeton ve buna bağlı dosya sayesinde gerçekleştirilir.
Bir web istemci-sunucu uygulamasındaki oturum izleme mekanizması, yukarıdaki işleyişe benzerdir:
- ilk isteğinde, bir müşteriye web sunucusu tarafından bir jeton verilir
- kimliğini doğrulamak için sonraki her talebinde bu jetonu sunar
Jeton farklı şekillerde olabilir:
- formda gizli bir alan
- istemci ilk isteğini gönderir (sunucu, istemcinin jetonu olmadığını fark ederek onu tanır)
- Sunucu yanıtını (bir form) gönderir ve jetonu bu formun gizli bir alanına yerleştirir. Bu anda bağlantı kesilir (istemci jetonuyla birlikte pencereden ayrılır). Sunucu, gerekirse bu jetona bazı bilgileri ilişkilendirmiştir.
- İstemci, formu geri göndererek ikinci isteğini yapar. Sunucu, formdan jetonu alır. Böylece, jeton sayesinde ilk istekte hesaplanan bilgilere erişerek istemcinin ikinci isteğini işleyebilir. Jetona bağlı dosyaya yeni bilgiler eklenir, müşteriye ikinci bir yanıt verilir ve bağlantı ikinci kez kapatılır. Jeton, kullanıcının bir sonraki isteğinde sunabilmesi için yanıt formuna yeniden eklenir.
- ve böyle devam eder...
Bu tekniğin en büyük dezavantajı, jetonun bir forma eklenmesi gerekliliğidir. Sunucunun yanıtı bir form değilse, gizli alan yöntemi artık kullanılamaz.
- Çerez yöntemi
- istemci ilk isteğini yapar (sunucu, istemcinin jetonu olmadığını fark eder)
- sunucu, yanıtının HTTP başlıklarına bir çerez ekleyerek yanıt verir. Bu, HTTP Set-Cookie komutu kullanılarak yapılır:
Set-Cookie: param1=değer1;param2=değer2;....
burada parami parametre adları, valeursi ise bunların değerleridir. Parametreler arasında jeton da bulunur. Çoğu zaman çerezde sadece jeton bulunur; diğer bilgiler ise sunucu tarafından jetonla ilişkili klasöre kaydedilir. Çerezi alan tarayıcı, bunu diskteki bir dosyada saklar. Sunucudan yanıt alındıktan sonra bağlantı kapatılır (istemci, jetonuyla birlikte oturumu sonlandırır).
- (devam)
- istemci, sunucuya ikinci isteğini gönderir. Sunucuya her istek gönderildiğinde, tarayıcı elindeki tüm çerezler arasında, istenen sunucudan gelen bir çerez olup olmadığına bakar. Varsa, bunu sunucuya her zaman HTTP komutu biçiminde gönderir; bu Cookie komutunun sözdizimi, sunucu tarafından kullanılan Set-Cookie komutunun sözdizimine benzerdir:
Cookie: param1=değer1;param2=değer2;....
Tarayıcı tarafından gönderilen HTTP başlıkları arasında, sunucu, müşteriyi tanımlamasına ve onunla ilişkili bilgileri bulmasına olanak tanıyan jetonu bulacaktır.
Bu, en yaygın olarak kullanılan jeton türüdür. Bunun bir dezavantajı vardır: kullanıcı, tarayıcısını çerezleri kabul etmeyecek şekilde yapılandırabilir. Bu tür kullanıcılar, çerez kullanan web uygulamalarına erişemez.
- URL'in yeniden yazımı
- istemci ilk isteğini gönderir (sunucu, istemcinin çerezi olmadığı için onu tanır)
- sunucu yanıtını gönderir. Bu yanıt, kullanıcının uygulamaya devam etmek için kullanması gereken bağlantılar içerir. Bu bağlantıların her birinin URL adresine, sunucu URL;token=değer biçiminde bir token ekler.
- Kullanıcı uygulamaya devam etmek için bağlantılardan birine tıkladığında, tarayıcı web sunucusuna istekte bulunur ve başlıklarda HTTP, URL, URL;token=istenen değer bilgisini gönderir. Böylece sunucu, jetonu alabilir.
3.10.2. Oturum izleme için API ve PHP
Şimdi, oturum takibi için yararlı olan başlıca yöntemleri sunuyoruz:
, mevcut isteğin ait olduğu oturumu başlatır. Eğer istek henüz bir oturumun parçası değilse, oturum oluşturulur. | |
cari oturumun kimliği | |
bir oturumun verilerini depolayan sözlük. Okuma ve yazma erişimi mevcuttur | |
Mevcut oturumda bulunan verileri siler. Bu veriler, devam eden istemci-sunucu iletişimi sırasında kullanılabilir durumda kalır, ancak bir sonraki iletişimde kullanılamaz hale gelir. |
3.10.3. Örnek 1
Wrox yayınevi tarafından basılan ve Eyrolles tarafından dağıtılan "J2EE ile Programlama" kitabından esinlenerek hazırladığımız bir örneği sunuyoruz. Bu örnek, bir PHP oturumunun nasıl çalıştığını gösterir. Ana sayfa şöyledir:

Bu sayfada şu öğeler bulunur:
- ID() işleviyle elde edilen oturumun session_id'i. Tarayıcı tarafından oluşturulan bu ID, tarayıcının aynı ağaç yapısından bir URL talep ettiğinde geri gönderdiği bir çerez aracılığıyla istemciye gönderilir. Oturumu sürdüren şey budur.
- Tarayıcının istekleri doğrultusunda artan ve oturumun başarıyla sürdürüldüğünü gösteren bir sayaç.
- Mevcut oturuma bağlı verileri silmeye yarayan bir bağlantı. Bu işlem, session_destroy() işlevi ile gerçekleştirilir
- Sayfayı yeniden yüklemek için bir bağlantı
cycledevie.php uygulamasının kodu şöyledir:
<?php
//cycledevie.php
// yapılandırma
ini_set("register_globals","off");
ini_set("display_errors","off");
// bir oturum başlatılıyor
session_start();
// geçersiz kılmalı mıyız?
$action=$_GET["action"];
if($action=="invalider"){
// oturum sonu
session_destroy();
}//if
// sayaç yönetimi
if(! isset($_SESSION["compteur"]))
// sayaç mevcut değil - oluşturuluyor
$_SESSION["compteur"]=0;
// sayaç mevcut - artırılıyor
else $_SESSION["compteur"]++;
// geçerli oturumun ID değeri alınır
$idSession=session_id();
// sayaç alınır
$compteur=$_SESSION["compteur"];
// görüntüleme sayfasına kontrolü devrediyoruz
include "cycledevie-p1.php";
?>
Aşağıdaki noktalara dikkat edelim:
- uygulama başladığı anda bir oturum başlatılır. İstemci bir oturum jetonu göndermişse, bu tanımlayıcıya sahip oturum yeniden başlatılır ve bu oturumla ilişkili tüm veriler $_SESSION sözlüğüne yerleştirilir. Aksi takdirde, yeni bir oturum jetonu oluşturulur.
- Müşteri, değeri "invalider" olan bir action parametresi göndermişse, oturum verileri bir sonraki iletişim için "silinecek" olarak işaretlenir. Normal bir iletişimden farklı olarak, bu veriler iletişimin sonunda sunucuya kaydedilmez.
- $_SESSION sözlüğünden oturumla ilişkili bir sayaç ve oturum ID'i (session_id()) alınır.
- Müşteriye gönderilecek sayfa, cycledevie-p1.php programı tarafından oluşturulur.
cycledevie-p1.php sayfası, müşteriye gönderilen sayfayı görüntüler:
<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>
Her iki bağlantının da sonuna eklenmiş olan URL koduna dikkat edin:
- cycledevie.php, sayfayı yeniden yüklemek için kullanılır
- cycledevie.php?action=invalider oturumu geçersiz kılmak için. Bu durumda, action=invalider parametresi URL'e eklenir. Bu parametre, cycledevie.php sunucu programı tarafından $action=$_GET["action"] komutu aracılığıyla alınacaktır.
Sayfayı arka arkaya iki kez yeniden yükleyelim:

Sayaç başarıyla artırıldı. Oturum kimliği ID değişmedi. Şimdi oturumu geçersiz kılalım:

Oturumun ID değerini kaybettiğimizi, ancak sayacın bir kez daha artırıldığını görüyoruz. Sayfayı yeniden yükleyelim:

Görüldüğü gibi, önceki oturumla aynı ID numarasıyla yeniden başlıyoruz. Sayaç ise sıfırdan başlıyor. Dolayısıyla session_destroy() işlevinin anında bir etkisi yoktur. Mevcut oturum verileri, silme işleminin yapıldığı işlemden sonraki istemci-sunucu iletişimi için silinir. ID oturumu değişmemiştir; bu da session_destroy() işlevinin yeni bir ID oluşturarak yeni bir oturum başlatmadığını göstermektedir. Oturum jetonu çerezi, istemci tarayıcısı tarafından geri gönderildi ve PHP, bu jetondan artık veri içermeyen oturumu geri aldı.
Önceki testler, çerezleri kullanacak şekilde yapılandırılmış Netscape tarayıcısıyla gerçekleştirildi. Şimdi tarayıcıyı çerezleri kullanmayacak şekilde yapılandıralım. Bu, tarayıcının sunucu tarafından gönderilen çerezleri ne depolayacağı ne de geri göndereceği anlamına gelir. Bu durumda oturumların artık çalışmaması beklenir. İlk bir veri alışverişiyle deneyelim:

ID oturum kimliğimiz var; bu, session_start() tarafından üretilen kimliktir. Şu bağlantıyla sayfayı yeniden yükleyelim:

Şaşırtıcı bir şekilde, yukarıdaki sonuçlar aktif bir oturum olduğunu ve sayaçın doğru şekilde yönetildiğini gösteriyor. Çerezler devre dışı bırakıldığı için sunucu ile tarayıcı arasında artık jeton alışverişi yapılmıyorken bu nasıl mümkün olabilir? Yukarıdaki ekran görüntüsündeki URL bize cevabı veriyor:
Bu, "Sayfayı Yenile" bağlantısının URL kodudur. Tarayıcı tarafından görüntülenen sayfanın kaynak kodunu inceleyelim:
<a href="cycledevie.php?action=invalider&PHPSESSID=587ce26f943a288d8f41212e30fed13c">Invalider la session</a>
<a href="cycledevie.php?PHPSESSID=587ce26f943a288d8f41212e30fed13c">Recharger la page</a>
cycledevie-p1.php sayfasındaki iki bağlantının başlangıç kodunun şu olduğunu hatırlayalım:
<a href="cycledevie.php?action=invalider">Invalider la session</a>
<a href="cycledevie.php">Recharger la page</a>
Dolayısıyla PHP yorumlayıcısı, oturum belirtecini ekleyerek iki bağlantının URL kodlarını kendi kendine yeniden yazmıştır. Böylece bağlantılar etkinleştirildiğinde bu belirteç tarayıcı tarafından doğru şekilde iletilir. Bu da, çerezler etkinleştirilmemiş olsa bile oturumun doğru şekilde yönetilmesini açıklar.
3.10.4. Örnek 3
Önceki compteur uygulamasının istemcisi olacak bir PHP uygulaması yazmayı planlıyoruz. Bu uygulama, N parametresiyle birlikte bu uygulamayı N kez arka arkaya çağıracaktır. Amacımız, programlanmış bir web istemcisini ve oturum jetonunun nasıl yönetileceğini göstermektir. Başlangıç noktamız, şu şekilde çağrılan genel bir web istemcisi olacaktır:
clientweb URL GET/HEAD
- URL: istenen URL
- GET/HEAD: GET, sayfadan HTML kodunu istemek için; HEAD, yalnızca başlıklarla sınırlandırmak için; HTTP
İşte URL http://localhost/poly/sessions/2/cycledevie.php ile ilgili bir örnek. Bu program, daha önce açıklanan programın aynısıdır, ancak küçük bir fark vardır:
// çerezin yolunu belirliyoruz
session_set_cookie_params(0,"/poly/sessions/2");
// oturum başlatılıyor
session_start();
session_set_cookie_params işlevi, oturum jetonunu içerecek çerezin belirli parametrelerini ayarlamaya olanak tanır. İlk parametre, çerezin ömrüdür. Sıfır ömür, çerezin onu alan tarayıcı kapatıldığında silineceği anlamına gelir. İkinci parametre, tarayıcının çerezi geri göndermesi gereken URL sunucularının yoludur. Yukarıdaki örnekte, tarayıcı çereği localhost makinesinden aldıysa, çereği http://localhost/poly/sessions/2/ dizin ağacında bulunan herhangi bir URL makinesine geri gönderecektir.
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>
clientweb programı, sunucudan aldığı her şeyi görüntüler. Yukarıda, sunucunun istemcisine bir çerez gönderdiği HTTP Set-cookie komutu görülmektedir. Burada çerez iki bilgi içerir:
- PHPSESSID, oturum jetonudur
- çerezin ait olduğu URL adresini tanımlayan yol. path=/poly/sessions/2, tarayıcıya, çerezi gönderen makineden /poly/sessions/2 ile başlayan bir URL talep ettiği her seferinde çerezi sunucuya geri göndermesi gerektiğini belirtir.
- Bir çerez, geçerlilik süresini de tanımlayabilir. Burada bu bilgi bulunmamaktadır. Dolayısıyla çerez, tarayıcı kapatıldığında silinecektir. Bir çerezin geçerlilik süresi örneğin N gün olabilir. Geçerli olduğu sürece, tarayıcı, kendi etki alanındaki (Path) URL'lerden herhangi biri her görüntülendiğinde çerezi geri gönderecektir. CD adlı bir çevrimiçi satış sitesini ele alalım. Bu site, müşterisinin katalogdaki gezinme sürecini takip edebilir ve zamanla tercihlerini belirleyebilir: örneğin klasik müzik. Bu tercihler, 3 ay süreli bir çerezde saklanabilir. Aynı müşteri bir ay sonra siteye geri dönerse, tarayıcı çerezi sunucu uygulamasına geri gönderir. Sunucu uygulaması, çerezde bulunan bilgilere göre oluşturulan sayfaları müşterinin tercihlerine göre uyarlayabilir.
Web istemcisinin kodu aşağıdadır.
<?php
// yapılandırma
dl("php_curl.dll"); // kütüphane CURL
// sözdizimi: $0 URL GET
// üç argüman gereklidir
if($argc != 3){
// hata mesajı
fputs(STDERR,"Syntaxe : $argv[0] URL GET/HEAD");
// durdurma
exit(1);
}//if
// üçüncü argüman GET veya HEAD olmalıdır
$header=strtolower($argv[2]);
if($header!="get" && $header!="head"){
// hata mesajı
fputs(STDERR,"Syntaxe : $argv[0] URL GET/HEAD");
// durdur
exit(2);
}//if
// ilk argüman URL'tir
$URL=strtolower($argv[1]);
// bağlantı hazırlığı
$connexion=curl_init($URL);
// bağlantı ayarları
curl_setopt($connexion,CURLOPT_HEADER,1);
if($header=="head") curl_setopt($connexion,CURLOPT_NOBODY,1);
// bağlantının gerçekleştirilmesi
curl_exec($connexion);
// bağlantının kapatılması
curl_close($connexion);
// son
exit(0);
?>
Önceki program CURL kütüphanesini kullanır:
CURL nesnesini, ulaşılması gereken URL ile başlatır | |
bağlantının belirli seçeneklerinin değerlerini belirler. Programda kullanılan ikisini şöyle sıralayabiliriz: CURLOPT_HEADER=1: sunucu tarafından gönderilen HTTP başlıklarını almayı sağlar CURLOPT_NOBODY=1: HTTP başlıklarının ardında sunucu tarafından gönderilen belgeyi yok sayar | |
istenen seçeneklerle $URL adresine bağlantı kurar. Sunucunun gönderdiği her şeyi ekrana görüntüler | |
bağlantıyı kapatır |
Yukarıdaki program oldukça basittir. Ancak CURL kütüphanesi, sunucunun yanıtını ayrıntılı bir şekilde işlemek, örneğin satır satır analiz etmek gibi işlemleri desteklemez. Aşağıdaki program, önceki programla aynı işlevi görür, ancak PHP'in temel ağ işlevlerini kullanır. Bu program, cycledevie.php uygulamamız için bir istemci yazmanın başlangıç noktası olacaktır.
<?php
// sözdizimi: $0 URL GET/HEAD
// üç argüman gereklidir
if($argc != 3){
// hata mesajı
fputs(STDERR,"Syntaxe : $argv[0] URL GET/HEAD");
// durdurma
exit(1);
}//if
// bağlantı ve sonucun görüntülenmesi
$résultats=getURL($argv[1],$argv[2]);
if(isset($résultats->erreur)){
// hata
echo "L'erreur suivante s'est produite : $résultats->erreur\n";
}else{
// sunucu yanıtının görüntülenmesi
echo $résultats->réponse;
}//if
// son
exit(0);
//-----------------------------------------------------------------------
function getURL($URL,$header){
// $URL'e bağlanır
// başlık değerine göre GET veya HEAD oluşturur
// sunucunun yanıtı, işlevin sonucunu oluşturur
//, URL'i analiz eder
$url=parse_url($URL);
// protokol
if(strtolower($url["scheme"])!="http"){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//eğer
// makine
$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
// bağlantı noktası
$port=$url["port"];
if(! isset($port)) $port=80;
// yol
$chemin=$url["path"];
// istek
if(isset($url["query"])){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//if
// $header analizi
$header=strtoupper($header);
if($header!="GET" && $header!="HEAD"){
// hata mesajı
$résultats->erreur="méthode [$header] doit être GET ou HEAD";
// durdurma
return $résultats;
}//if
// $port bağlantı noktasında $hote bağlantısı açılıyor
$connexion=fsockopen($hote,$port,&$errno,&$erreur);
// hata durumunda geri dönüş
if(! $connexion){
$résultats->erreur="Echec de la connexion au site ($hote,$port) : $erreur";
return $résultats;
}//if
// $connexion, iki yönlü bir iletişim akışını temsil eder
// istemci (bu program) ile bağlanılan web sunucusu arasında
// bu kanal, komut ve bilgi alışverişi için kullanılır
// iletişim protokolü HTTP'tir
// istemci, URL / dosyasını istemek için get komutunu gönderir
// "get" komutunun sözdizimi: URL HTTP/1.0
// HTTP protokolünün başlıkları (headers) boş bir satırla bitmelidir
fputs($connexion, "$header $chemin HTTP/1.0\n\n");
// sunucu şimdi $connexion kanalında yanıt verecektir. Bu verilerin tamamını
// bu verileri gönderecek ve ardından kanalı kapatacaktır. Dolayısıyla istemci, $connexion
// kanal kapanana kadar gelen tüm verileri okur
$résultats->réponse="";
while($ligne=fgets($connexion,10000))
$résultats->réponse.=$ligne;
// sonra istemci de bağlantıyı kapatır
fclose($connexion);
// geri
return $résultats;
}//getURL
?>
Bu programın bazı noktalarını inceleyelim:
- Program iki parametre kabul eder:
- Ekranda içeriğini görüntülemek istediğimiz bir URL http adresi.
- sadece başlıkları (HTTP (HEAD) başlıklarını mı, yoksa URL ile ilişkili belgenin gövdesini de (GET) görüntülemek istediğinize bağlı olarak kullanılacak bir yöntem.
- Her iki parametre de getURL işlevine aktarılır. Bu işlev bir $résultats nesnesi döndürür. Bu nesne, bir hata varsa erreur alanına, aksi takdirde réponse alanına sahiptir. erreur alanı, olası bir hata mesajını depolamak için kullanılır. réponse alanı, bağlantı kurulan web sunucusunun yanıtını içerir.
- getURL işlevi, URL ve $URL'i parse_url işleviyle analiz eder. $url=parse_url($URL) komutu, aşağıdaki olası anahtarları içeren $url ilişkisel dizisini oluşturacaktır:
- scheme: URL'in protokolü (http, ftp, ..)
- host: URL'in sunucusu
- port: URL'in bağlantı noktası
- path: URL'in yolu
- sorgu dizesi: URL'in parametreleri
Bir URL, http://machine[:port][/chemin] biçimindeyse geçerlidir.
- $header parametresi de kontrol edilir
- parametreler doğrulanıp doğru olduğu tespit edildikten sonra, makine üzerinde ($hote, $port) bir TCP bağlantısı oluşturulur, ardından $header parametresine göre HTTP, GET veya HEAD komutu gönderilir.
- ardından sunucudan gelen yanıt okunur ve $résultats->réponse değişkenine atanır.
Programın çalıştırılması sonucunda şu sonuçlar elde edilir:
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>
Dikkatli bir okuyucu, kullanılan istemci programına göre sunucunun yanıtının farklı olduğunu fark etmiş olacaktır. İlk durumda sunucu, HTTP başlığını göndermişti: Transfer-Encoding: chunked; ancak bu başlık ikinci durumda gönderilmedi. Bunun nedeni, ikinci istemcinin HTTP başlığını göndermiş olmasıdır: get URL HTTP/1.0 başlığını göndermiş olması ve URL'i talep etmesinden kaynaklanmaktadır; bu başlık, istemcinin HTTP protokolünün 1.0 sürümüyle çalıştığını belirtir ve sunucuyu da aynı protokolü kullanarak yanıt vermeye zorlar. Oysa HTTP başlığındaki Transfer-Encoding: chunked, HTTP sürüm 1.1 protokolüne aittir. Bu nedenle sunucu, yanıtında bunu kullanmamıştır. Bu durum, ilk istemcinin isteğini, HTTP sürüm 1.1 protokolüyle çalıştığını belirterek gönderdiğini göstermektedir.
Şimdi, şu şekilde çağrılan clientCompteur programını oluşturuyoruz:
clientCompteur URL N [JSESSIONID]
- URL: cycledevie uygulamasının URL'si
- N: Bu uygulamaya yapılacak çağrı sayısı
- PHPSESSID: isteğe bağlı parametre - oturum jetonu
Programın amacı, oturum çerezini yöneterek cycledevie.php uygulamasını N kez çağırmak ve her seferinde sunucu tarafından döndürülen sayaç değerini görüntülemektir. N çağrı tamamlandığında, sayaç değeri N-1 olmalıdır. İşte ilk bir çalıştırma örneği:
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 şunu görüntüler:
- sunucuya gönderdiği HTTP başlıklarını --> entêteEnvoyé biçiminde
- aldığı HTTP başlıklarını <-- entêteReçu biçiminde
- her çağrıdan sonra sayaç değeri
Görüldüğü gibi, ilk çağrı sırasında:
- istemci çerez göndermiyor
- sunucu bir çerez gönderir (Set-Cookie:)
Sonraki çağrılarda:
- istemci, ilk çağrı sırasında sunucudan aldığı çerezi her seferinde geri gönderir. Bu sayede sunucu, istemciyi tanıyabilir ve sayacını artırabilir.
- sunucu ise artık çerez göndermiyor
Yukarıdaki jetonu üçüncü parametre olarak geçirerek önceki programı yeniden çalıştırıyoruz:
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]
Burada görüldüğü gibi, müşterinin ilk çağrısından itibaren sunucu geçerli bir oturum çerezi alır. Bu durum, potansiyel bir güvenlik açığına işaret ediyor olabilir. Eğer ağ üzerinden bir oturum jetonunu ele geçirebilirsem, o oturumu başlatan kişi gibi davranabilirim. Örneğimizde, ilk istek (oturum çerezi olmadan), oturumu başlatan kişiyi temsil eder (belki de kendisine çerez alma hakkı verecek bir kullanıcı adı ve şifre ile) ve ikinci istek (oturum çerezi ile), ilk isteğin oturum çerezini "çalmış" olan kişiyi temsil eder. Eğer söz konusu işlem bir banka işlemiyse, bu durum can sıkıcı hale gelebilir...
İstemci kodu şu şekildedir:
<?php
// sözdizimi: $0 URL N [PHPSESSID]
// üç argüman gereklidir
if($argc!=3 && $argc!=4){
// hata mesajı
fputs(STDERR,"Syntaxe : $argv[0] URL N [PHPSESSID]");
// durdurma
exit(1);
}//if
// parametrelerin alınması
$URL=$argv[1];
$N=$argv[2];
$PHPSESSID=$argv[3];
// bağlantı ve sonucun görüntülenmesi
$résultats=getURL($URL,$N,$PHPSESSID);
if(isset($résultats->erreur)){
// hata
echo "L'erreur suivante s'est produite : $résultats->erreur\n";
}
// son
exit(0);
//-----------------------------------------------------------------------
function getURL($URL,$N,$PHPSESSID){
// URL'e bağlanıyor
// başlık değerine göre GET veya HEAD oluşturur
// sunucunun yanıtı işlevin sonucunu oluşturur
//, URL'i analiz eder
$url=parse_url($URL);
// protokol
if(strtolower($url["scheme"])!="http"){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//eğer
// makine
$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
// bağlantı noktası
$port=$url["port"];
if(! isset($port)) $port=80;
// yol
$chemin=$url["path"];
// istek
if(isset($url["query"])){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//if
// $N'in doğrulanması
if (! preg_match("/^\d+$/",$N)){
// hata
$résultats->erreur="nombre [$N] erroné";
// son
return $résultats;
}//if
// $N çağrıları $URL'e yapılır
for($i=0;$i<$N;$i++){
// $port bağlantı noktasında $hote bağlantısı açılıyor
$connexion=fsockopen($hote,$port,&$errno,&$erreur);
// hata durumunda geri dönüş
if(! $connexion){
$résultats->erreur="Echec de la connexion au site ($hote,$port) : $erreur";
return $résultats;
}//if
// $connexion, istemci (bu program) ile erişilen web sunucusu arasında çift yönlü bir iletişim akışını temsil eder
// istemci (bu program) ile bağlanılan web sunucusu arasında
// bu kanal, komut ve bilgi alışverişi için kullanılır
// iletişim protokolü HTTP'tir
// istemci, HTTP başlıklarını gönderir
// get URL HTTP/1.1
envoie($connexion, "GET $chemin HTTP/1.1\n");
// host: sunucu:bağlantı noktası
envoie($connexion, "Host: $hote:$port\n");
// Bağlantı: kapat
envoie($connexion, "Connection: close\n");
// Çerez: $PHPSESSID
if($PHPSESSID) envoie($connexion, "Cookie: PHPSESSID=$PHPSESSID\n");
// boş satır
envoie($connexion,"\n");
// sunucu artık $connexion kanalında yanıt verecek. Tüm
// verilerini gönderecek ve ardından kanalı kapatacaktır.
// İstemci, boş satırla biten HTTP başlıklarını okumaya başlar
$CHUNKED=0;
while(($ligne=fgets($connexion,10000)) && (($ligne=rtrim($ligne))!="")){
// satır yankısı
echo "<-- $ligne\n";
// henüz bulunmamışsa jeton aranır
if(! $PHPSESSID){
// set-cookie satırını arama
if(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i",$ligne,$champs)){
// jeton bulundu - saklanıyor
$PHPSESSID=$champs[1];
}//if
}//if
// belgenin aktarım modunu arama
if(! $CHUNKED){
// Transfer-Encoding: chunked satırını arama
if(preg_match("/^Transfer-Encoding: chunked/i",$ligne,$champs)){
// parça parça aktarım
$CHUNKED=1;
}//if
}//if
}//sonraki satır
// satırın yankılanması
echo "<-- $ligne\n";
// belgenin okunması, gönderilme şekline bağlıdır
if($CHUNKED) $document=getChunkedDoc($connexion);
else $document=getDoc($connexion);
// belgede sayaç aranıyor
if(preg_match("/<br>compteur : (\d+)/i",$document,$champs)){
// sayaç bulundu - görüntüleniyor
echo "\n[Le compteur est égal à $champs[1]]\n\n";
}//if
// istemci bağlantıyı kapatır
fclose($connexion);
}//for i
}//getURL
//--------------------------
function getDoc($connexion){
// $connexion adresindeki belge okunuyor
$doc="";
while($ligne=fread($connexion,10000))
$doc.=$ligne;
// son
return $doc;
}//getDoc
//--------------------------
function getChunkedDoc($connexion){
// $connexion adresindeki belgenin okunması
// bu belge, şu biçimde parçalar halinde gönderilir
// parçanın karakter sayısı onaltılık olarak
// parçanın devamı
// boş satır
// 1. satırdan parçanın boyutu okunur
$taille=hexdec(rtrim(fgets($connexion,10000)));
// ardından gelen belge okunur
$doc="";
while($taille!=0){
// $taille karakterlerinden oluşan parça okunur
$doc.=fread($connexion,$taille);
// boş satır
fgets($connexion,10000);
// sonraki parça
// parçanın boyutu okunur
$taille=hexdec(rtrim(fgets($connexion,10000)));
}//while
// bitti
return $doc;
}// getChunkedDoc
//--------------------------
function envoie($flux,$msg){
// $msg'i $flux'e gönder
fwrite($flux,$msg);
// ekran çıktısı
echo "--> $msg";
}//gönderir
?>
Bu programın önemli noktalarını inceleyelim:
- N adet istemci-sunucu iletişimi gerçekleştirmemiz gerekiyor. Bu nedenle bunlar bir döngü içinde yer alıyor
- Her iletişimde, istemci sunucuyla bir bağlantı kurar TCP-IP. Bağlantı kurulduktan sonra, isteğinin başlıklarını sunucuya gönderir HTTP:
<?php
...
// istemci, HTTP başlıklarını gönderir
// get URL HTTP/1.1
envoie($connexion, "GET $chemin HTTP/1.1\n");
// host: sunucu:bağlantı noktası
envoie($connexion, "Host: $hote:$port\n");
// Bağlantı: kapat
envoie($connexion, "Connection: close\n");
// Çerez: $PHPSESSID
if($PHPSESSID) envoie($connexion, "Cookie: PHPSESSID=$PHPSESSID\n");
// boş satır
envoie($connexion,"\n");
PHPSESSID jetonu mevcutsa, çerez biçiminde gönderilir; aksi takdirde gönderilmez. İstemcinin HTTP/1.1 protokolüyle çalıştığını belirttiği unutulmamalıdır. Bu durum, daha sonra sunucunun istemciye HTTP: Transfer-Encoding: chunked başlığını göndereceğini açıklar; bu başlık HTTP/1.1 protokolüne aittir, ancak HTTP/1.0 protokolüne ait değildir.
- İsteğini gönderdikten sonra istemci, sunucudan yanıt bekler. Öncelikle bu yanıtın HTTP başlıklarını inceler. Bu başlıklarda şu iki satırı arar:
"Cookie:" satırı, PHPSESSID oturum jetonunu içeren HTTP başlığıdır. İstemci, bir sonraki iletişim sırasında sunucuya geri göndermek üzere bu jetonu almalıdır. Transfer-Encoding: chunked satırı varsa, sunucunun bir belgeyi parçalar halinde göndereceğini belirtir. Her parça, istemciye şu biçimde gönderilir:
Transfer-Encoding: chunked satırı mevcut değilse, belge HTTP başlık satırının ardından tek seferde gönderilir. Dolayısıyla, bu satırın varlığına veya yokluğuna göre belgenin alınma şekli farklılık gösterecektir. HTTP başlıklarının yorumlanma kodu şöyledir:
<?php
...
// Müşteri, boş bir satırla biten HTTP başlıklarını okumaya başlar
$CHUNKED=0;
while(($ligne=fgets($connexion,10000)) && (($ligne=rtrim($ligne))!="")){
// satır yankısı
echo "<-- $ligne\n";
// henüz bulunmamışsa jeton aranır
if(! $PHPSESSID){
// set-cookie satırını arar
if(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i",$ligne,$champs)){
// jeton bulundu - saklanıyor
$PHPSESSID=$champs[1];
}//if
}//if
// belgenin aktarım modunu arama
if(! $CHUNKED){
// Transfer-Encoding: chunked satırını arama
if(preg_match("/^Transfer-Encoding: chunked/i",$ligne,$champs)){
// parça parça aktarım
$CHUNKED=1;
}//if
}//if
}//sonraki satır
- Jeton ilk kez bulunduğunda, sunucuya yapılan sonraki çağrılarda artık aranmayacaktır. Yanıtın HTTP başlıkları işlendiğinde, HTTP başlıklarının ardından gelen belgeye geçilir. Bu belge, aktarım moduna göre farklı şekilde okunur:
<?php
...
// belgenin okunması, gönderilme şekline bağlıdır
if($CHUNKED) $document=getChunkedDoc($connexion);
else $document=getDoc($connexion);
- Alınan $document belgesinde, sayaç değerini veren satır aranır. Bu arama da yine bir düzenli ifade kullanılarak yapılır:
<?php
...
// belgede sayaç aranıyor
if(preg_match("/<br>compteur : (\d+)/i",$document,$champs)){
// sayaç bulundu - görüntüleniyor
echo "\n[Le compteur est égal à $champs[1]]\n\n";
}//if
- Sunucu belgeyi tek seferde gönderirse, belgenin alınması basittir:
<?php
...
//--------------------------
function getDoc($connexion){
// $connexion adresindeki belge okunuyor
$doc="";
while($ligne=fgets($connexion,10000))
$doc.=$ligne;
// son
return $doc;
}//getDoc
- Sunucu belgeyi birkaç parça halinde gönderirse, belgenin okunması daha karmaşık hale gelir:
<?php
...
function getChunkedDoc($connexion){
// $connexion adresindeki belgenin okunması
// bu belge, şu biçimde parçalar halinde gönderilir
// parçanın karakter sayısı onaltılık olarak
// parçanın devamı
// 1. satırda parçanın boyutu okunur
$taille=hexdec(rtrim(fgets($connexion,10000)));
// ardından gelen belge okunur
$doc="";
while($taille!=0){
// $taille karakterlik parça okunur
$doc.=fread($connexion,$taille);
// boş satır
fgets($connexion,10000);
// sonraki parça
// parçanın boyutu okunur
$taille=hexdec(rtrim(fgets($connexion,10000)));
}//while
// bitti
return $doc;
}// getChunkedDoc
Bir belge parçasının şu biçimde gönderildiğini hatırlayalım:
Bu nedenle önce belgenin boyutunu okuruz. Boyut bilindiğinde, fread işlevinden $taille karakterini $connexion akışından ve ardından gelen boş satırı okumasını isteriz. Sunucu, boyutu 0 olan bir belge göndereceğini bildirene kadar bu işlemi tekrarlarız.
3.10.5. Örnek 4
Önceki örnekte, web istemcisi jetonu bir çerez (cookie) biçiminde geri gönderir. Jetonun, istenen URL isteğinin içinde URL;PHPSESSID=xxx biçiminde de geri gönderilebileceğini görmüştük. Bunu kontrol edelim. clientCompteur.php programı, clientCompteur2.php’e dönüştürülür ve şu şekilde değiştirilir:
<?php
...
....
// istemci başlıkları gönderir 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: sunucu:bağlantı noktası
envoie($connexion, "Host: $hote:$port\n");
// Bağlantı: kapat
envoie($connexion, "Connection: close\n");
// boş satır
envoie($connexion,"\n");
....
Dolayısıyla müşteri, GET ve URL aracılığıyla sayacın URL değerini talep eder;PHPSESSID=xx HTTP/1.1 ve artık çerez göndermiyor. Tek değişiklik budur. İşte ilk çağrının sonuçları:
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]
İlk istekte istemci, oturum jetonu olmadan URL'i talep eder. Sunucu, jetonu göndererek yanıt verir. Ardından istemci, aldığı jetonu ekleyerek aynı URL'i tekrar sorgular. Sayaçın artmış olduğu görülüyor; bu da sunucunun bunun aynı oturum olduğunu doğru bir şekilde tanıdığının kanıtıdır.
3.10.6. Örnek 5
Bu örnek, page1, page2 ve page3 olarak adlandıracağımız üç sayfadan oluşan bir uygulamayı göstermektedir. Kullanıcı bu sayfaları şu sırayla almalıdır:
- page1, bir bilgi (ad) isteyen bir formdur
- page2, page1'deki formun gönderilmesine yanıt olarak alınan bir formdur. İkinci bir bilgi ister: yaş
- page3, page1'den alınan adı ve page2'den alınan yaşı görüntüleyen HTML adlı bir belgedir.
Üç adet istemci-sunucu iletişimi vardır:
- ilk iletişimde, sayfa1 formu istemci tarafından talep edilir ve sunucu tarafından gönderilir
- ikinci iletişimde, istemci page1 formunu (ad) sunucuya gönderir. Karşılığında page2 formunu alır; form hatalıysa tekrar page1 formunu alır.
- Üçüncü iletişimde, istemci page2 formunu (yaş) sunucuya gönderir. Karşılığında page3 formunu alır; form hatalıysa tekrar page2 formunu alır. page3 belgesi, adı ve yaşı görüntüler. Ad, ikinci iletişimde sunucu tarafından alınmış ve o andan itibaren "unutulmuştur". Adın ikinci iletişimde kaydedilmesi ve üçüncü iletişimde kullanılabilir olması için bir oturum kullanılır.
İlk veri alışverişinde alınan page1 sayfası şöyledir:

Ad alanını dolduruyoruz:

[Suite] düğmesini kullanıyoruz ve ardından aşağıdaki page2 sayfasını elde ediyoruz:

Yaş alanını dolduruyoruz:

[Suite] düğmesini kullandığımızda, aşağıdaki page3 sayfası görüntülenir:

page1 sayfasını sunucuya gönderdiğimizde, isim alanı boşsa sunucu bir hata kodu ile geri gönderebilir:

page2 sayfasını sunucuya gönderdiğimizde, yaş bilgisi geçersizse sunucu bir hata kodu ile yanıt verebilir:

Uygulama altı programdan oluşur:
page1.php'i çağırır | |
page1 sayfasını görüntüler. page1 sayfasındaki form, etape2.php tarafından işlenir. | |
sayfa1 formundaki değerleri işler. Hata varsa, sayfa1 page1.php tarafından yeniden görüntülenir; aksi takdirde sayfa2, page2.php tarafından görüntülenir. | |
page2'yi görüntüler. page2'nin formu, etape3.php tarafından işlenir. | |
sayfa2'deki form değerlerini işler. Hata varsa, page2.php tarafından sayfa2 yeniden görüntülenir; aksi takdirde page3.php tarafından sayfa3 görüntülenir. | |
page3'ü görüntüler. |
Uygulamanın 1. adımı, aşağıdaki etape1.php programı tarafından işlenir:
<?php
// etape1.php
// yapılandırma
ini_set("register_globals","off");
ini_set("display_errors","off");
// oturum başlatma
session_start();
$_SESSION["session"]=""; //oturum değişkenini sıfırla
// sayfa1 hazırlığı
$requête->nom="";
$requête->erreurs=array();
// sayfa1 görüntüleme
include "page1.php";
// son
exit(0);
?>
Aşağıdaki noktalara dikkat edelim:
- Uygulama, oturum takibi gerektirir. Bu nedenle, oturumun her adımı yeni bir oturum başlatır.
- Saklanacak oturum bilgileri, $session nesnesinde tutulacaktır.
- Uygulamanın farklı (üç) sayfasının görüntülenmesi için gerekli bilgiler, $requête nesnesine yerleştirilecektir.
page1.php programı, $requête'te bulunan bilgileri görüntüler:
<? // 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>
<? // hatalar mı?
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>
<? }//eğer ?>
</body>
</html>
- Sayfa, iki alan içeren bir $requête nesnesi alır: nom ve erreurs. Bu iki alanın değerini görüntüler.
- Ayrıca bir form da içerir. Bu formdaki değerler (ad), POST yöntemi aracılığıyla etape2.php programına gönderilir:
etape2.php uygulaması, page1 formundaki değerleri işlemekle görevlidir; hata varsa (yanlış isim) page1'i yeniden görüntüler, aksi takdirde yaşı öğrenmek için page2'yi görüntüler.
<?php
// etape2.php
// yapılandırma
ini_set("register_globals","off");
ini_set("display_errors","off");
// oturum başlatma
session_start();
// normalde bir "ad" parametresi olması gerekir
// istekte kayıtlı bir parametre olmalı
$requête->nom=$_POST["nom"];
// parametre yoksa, hata vermeden sayfa1 gönderilir
if (! isset($requête->nom)){
$requête->nom="";
$requête->erreurs=array();
include "page1.php";
exit(0);
}//if
// "nom" parametresi varsa - geçerliliğini kontrol ederiz
$page=calculerPage($requête);
// Hata var mı?
if(count($page->erreurs)!=0){
// hatalı sayfa 1
$requête->erreurs=$page->erreurs;
include "page1.php";
exit(0);
}//eğer
// hata yok - isim oturumda saklanır
unset($session);
$session->nom=$requête->nom;
$_SESSION["session"]=$session;
// 2. sayfa görüntüleniyor
$requête->age="";
$requête->erreurs=array();
include "page2.php";
// son
exit(0);
// ---------calculerPage
function calculerPage($requête){
// isteğin geçerliliğini kontrol eder $requête
// $page->hatalar'da bir hata tablosu döndürür
// başlangıçta hata yok
$page->erreurs=array();
// ad alanı boş olmamalıdır
if (preg_match("/^\s*$/",$requête->nom)){
$page->erreurs[]="Vous n'avez pas indiqué de nom";
}
// sayfaya geri dön
return $page;
}//calculerPage
?>
- etape2, öncelikle beklenen nom parametresinin mevcut olup olmadığını kontrol eder. Eğer yoksa, boş bir sayfa1'i yeniden görüntüler. Bu durum, etape2'in parametre aktarmayan bir istemci tarafından doğrudan çağrılması halinde mümkündür.
- nom parametresi mevcutsa, geçerliliği kontrol edilir. Bu, calculerPage adlı bir prosedür aracılığıyla yapılır; bu prosedürün görevi, bir hata dizisi olan erreurs alanına sahip bir $page nesnesi oluşturmaktır. Tek bir hata olması mümkündür, ancak bir hata listesini yönetebileceğimizi göstermek istedik.
- Hata varsa, page1 sayfası hata listesiyle birlikte yeniden görüntülenir.
- Hata yoksa, ad, geçerli oturumla ilgili verileri saklayan $session nesnesinde saklanır. Ardından page2 sayfası görüntülenir.
page2.php programı sayfa2'yi görüntüler:
<? // 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>
<? // hatalar mı?
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];
}//for
?>
</ul>
</font>
<? } ?>
</body>
</html>
Bu sayfanın çalışma prensibi, page2.php sayfasınınkine çok benzerdir. $requête nesnesinin içeriğini görüntüler; bu nesne, nom, age ve erreurs alanlarını içerir. etape3.php tarafından işlenecek değerleri içeren bir form görüntüler.
etape3.php programı, page2 formundaki değerleri işler; bu değerler burada yaş bilgisine indirgenmiştir:
<?php
// etape3.php
// yapılandırma
ini_set("register_globals","off");
ini_set("display_errors","off");
// oturum başlatma
session_start();
// ad ve yaş parametreleri alınır
$requête->age=$_POST["age"];
$session=$_SESSION["session"];
$requête->nom=$session->nom;
// normalde bir isim ve yaş olması gerekir
if (! isset($requête->age) || ! isset($requête->nom)){
// çağrı hatalıysa, sayfa1'i göndeririz
$_SESSION["session"]=""; // önlem olarak
$requête->nom="";
$requête->erreurs=array();
include "page1.php";
exit(0);
}//if
// "age" parametresi varsa - geçerliliğini kontrol ederiz
$page=calculerPage($requête);
// Hata var mı?
if(count($page->erreurs)!=0){
// hatalar içeren 2. sayfa
$requête->erreurs=$page->erreurs;
include "page2.php";
exit(0);
}//if
// hata yok - oturumda yaş bilgisi kaydedildi
$session->age=$requête->age;
$_SESSION["session"]=$session;
// 3. sayfanın görüntülenmesi
include "page3.php";
// son
exit(0);
// ---------calculerPage
function calculerPage($requête){
// isteğin geçerliliğini kontrol eder $requête
// $page->hatalar içinde bir hata tablosu döndürür
// başlangıçta hata yok
$page->erreurs=array();
// yaşın geçerli bir biçime sahip olması gerekir
if (! preg_match("/^\s*\d{1,3}\s*$/",$requête->age)){
$page->erreurs[]="âge incorrect";
}
// sayfaya geri dön
return $page;
}//calculerPage
?>
- Program, önce oturumdan (sayfa1'den gelen) adı ve sayfa2'deki formdan yaşı alır. Bu bilgilerden biri eksikse, sayfa1 görüntülenir.
- Ardından yaşın geçerliliği kontrol edilir. Yaş yanlışsa, sayfa2 bir hata listesiyle birlikte yeniden görüntülenir. Yaş doğruysa, sayfa3 görüntülenir. Bu sayfa, iki formdan (sayfa1, sayfa2) elde edilen iki değeri (ad, yaş) görüntüler.

