3. مقدمهای بر برنامهنویسی وب در PHP
3.1. برنامهنویسی PHP
شایان ذکر است که PHP به خودی خود یک زبان است و اگرچه عمدتاً در زمینه توسعه برنامههای وب استفاده میشود، اما میتوان از آن در زمینههای دیگر نیز بهره برد. مستند «PHP by Example» که در http://shiva.istia.univ-angers.fr/~tahe/pub/php/php.pdf در دسترس است، مبانی این زبان را پوشش میدهد. ما در اینجا فرض میکنیم که این مبانی قبلاً آموخته شدهاند. بیایید از یک مثال ساده برای نشان دادن نحوه اجرای یک برنامه PHP در ویندوز استفاده کنیم. کد زیر با نام coucou.php ذخیره شده است.
این برنامه در یک پنجره DOS ویندوز اجرا میشود:
dos>"e:\program files\easyphp\php\php.exe" coucou.php
X-Powered-By: PHP/4.3.0-dev
Content-type: text/html
coucou
توجه داشته باشید که تفسیرگر PHP بهطور پیشفرض موارد زیر را ارسال میکند:
- تفسیرگر PHP، php.exe است و معمولاً در دایرکتوری <php> نصب نرمافزار قرار دارد.
- سربرگهای X-Powered-By و Content-type:
- خط خالی که سرآیندهای HTTP را از بقیهٔ سند جدا میکند
- سندی که از متنی که توسط تابع echo تولید شده، تشکیل شده است
3.2. فایل پیکربندی برای مفسر PHP
رفتار مفسر PHP توسط یک فایل پیکربندی به نام php.ini که در ویندوز در خود دایرکتوری ویندوز ذخیره شده است، پیکربندی میشود. این فایل نسبتاً بزرگی است؛ در ویندوز، برای نسخهٔ ۴.۲ از PHP، تقریباً به ۱۰۰۰ خط میرسد که خوشبختانه سهچهارم آن را توضیحات تشکیل میدهند. بیایید برخی از ویژگیهای پیکربندی PHP را بررسی کنیم:
اجازه میدهد دستورالعملها بین تگهای <? > گنجانده شوند. در off، این موارد باید بین <?php ... > گنجانده شوند. | |
در on امکان استفاده از نحوی <% =variable %> که توسط فناوری ASP (صفحات سرور پویا) استفاده میشود، فراهم است. | |
اجازه میدهد تا هدر HTTP X-Powered-By: PHP/4.3.0-dev ارسال شود. در off، این هدر حذف میشود. | |
دامنه ردیابی خطا را تعیین میکند. در اینجا، تمام خطاها (E_ALL) به جز هشدارهای زمان اجرا (~E_NOTICE) گزارش خواهند شد | |
برای قرار دادن خطاها در جریان HTML که به کلاینت ارسال میشود، آن را روی on تنظیم کنید. بنابراین این خطاها در مرورگر نمایش داده میشوند. توصیه میشود این گزینه را روی off تنظیم کنید. | |
خطاها در یک فایل ثبت خواهند شد | |
آخرین خطای رخ داده را در متغیر $php_errormsg ذخیره میکند | |
فایل گزارش خطا را تنظیم میکند (اگر log_errors=on باشد) | |
در on، تعدادی متغیر به صورت سراسری درمیآیند. این یک آسیبپذیری امنیتی محسوب میشود. | |
به طور پیشفرض هدر HTTP را تولید میکند: Content-type: text/html | |
فهرست دایرکتوریهایی که برای فایلهای مورد نیاز دستورات include یا require اسکن خواهند شد | |
پوشهای که فایلهای حاوی جلسات مختلف جاری در آن ذخیره میشوند. درایو مربوطه همان درایوی است که PHP روی آن نصب شده است. در اینجا، /temp به e:\temp اشاره دارد. |
این فایل پیکربندی بر قابلیت حمل برنامه PHP نوشتهشده تأثیر میگذارد. در واقع، اگر یک برنامه وب نیاز داشته باشد که مقدار یک فیلد C را از یک فرم وب بازیابی کند، میتواند این کار را به روشهای مختلفی انجام دهد، بسته به اینکه آیا متغیر پیکربندی register_globals روی on یا off تنظیم شده باشد:
- خاموش: مقدار توسط $HTTP_GET_VARS["C"] یا _GET["C"] یا $HTTP_POST_VARS["C"] یا $_POST["C"]، بسته به روشی (GET/POST) که توسط کلاینت برای ارسال مقادیر فرم استفاده میشود
- روی: همانند مورد بالا، بهعلاوه $C، زیرا مقدار فیلد C در متغیری با همان نام فیلد، جهانی شده است
اگر یک توسعهدهنده برنامهای را با استفاده از notation $C بنویسد، زیرا سرور وب/PHP که استفاده میکند متغیر register_globals را روی on تنظیم کرده است، آن برنامه دیگر کار نخواهد کرد اگر به سرور وب/PHP منتقل شود که در آن همان متغیر روی off تنظیم شده باشد. بنابراین باید برنامههایی بنویسیم که از ویژگیهای وابسته به پیکربندی سرور وب/PHP اجتناب کنند.
3.3. پیکربندی PHP در زمان اجرا
برای بهبود قابلیت حمل یک برنامه PHP، میتوانید برخی متغیرهای پیکربندی PHP را خودتان تنظیم کنید. این متغیرها در حین اجرای برنامه تغییر میکنند و فقط برای همان برنامه اعمال میشوند. دو تابع در این فرآیند مفید هستند:
مقدار متغیر پیکربندی confVariable را برمیگرداند | |
مقدار متغیر پیکربندی confVariable را تعیین میکند |
در اینجا مثالی از تنظیم مقدار متغیر پیکربندی track_errors آورده شده است:
<?php
//مقدار متغیر پیکربندی track_errors
echo "track_errors=".ini_get("track_errors")."\n";
// تغییر به این مقدار
ini_set("track_errors","off");
//تأیید
echo "track_errors=".ini_get("track_errors")."\n";
?>
هنگام اجرا، نتایج زیر به دست میآیند:
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 در ابتدا 1 (~on) بود. اکنون روی off تنظیم شده است. شایان ذکر است که اگر برنامه ما نیازمند اتکا به مقادیر خاصی از متغیرهای پیکربندی باشد، عاقلانه است که این متغیرها را در داخل خود برنامه مقداردهی اولیه کنیم.
3.4. زمینه اجرای مثالها
مثالهای این جزوه با پیکربندی زیر اجرا خواهند شد:
- PC روی ویندوز ۲۰۰۰
- سرور Apache 1.3
- PHP 4.3
سرور آپاچی در فایل httpd.conf پیکربندی شده است. خطوط زیر به آپاچی دستور میدهند که PHP را بهعنوان یک ماژول یکپارچه با آپاچی بارگذاری کند و هر درخواست برای یک سند با پسوندهای خاص فایل، از جمله .php، را به تفسیرگر PHP ارسال نماید. این پسوند پیشفرض است که برای برنامههای 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
علاوه بر این، ما یک نام مستعار «poly» برای Apache تعریف کردهایم:
Alias "/poly/" "e:/data/serge/web/php/poly/"
<Directory "e:/data/serge/web/php/poly">
Options Indexes FollowSymLinks Includes
AllowOverride All
#دستور allow,deny
Allow from all
</Directory>
بیایید مسیر <poly> را مسیر e:/data/serge/web/php/poly بنامیم. اگر بخواهیم سند doc.php را از سرور آپاچی با استفاده از مرورگر درخواست کنیم، از نشانی http://localhost/poly/doc.php URL استفاده خواهیم کرد. سرور آپاچی نام مستعار poly را در URL تشخیص داده و سپس URL /poly/doc.php را با سند <poly>\doc.php.
3.5. یک مثال اول
بیایید اولین برنامه وب/PHP خود را بنویسیم. متن زیر در فایل 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>
اگر این صفحه را با یک مرورگر وب درخواست کنیم، نتیجه زیر را دریافت میکنیم:

بخش پویا صفحه توسط کد PHP تولید شده است:
دقیقاً چه اتفاقی افتاد؟ مرورگر http://localhost/poly/intro/heure.php را درخواست کرد. سرور وب (در این مثال آپاچی) این درخواست را دریافت کرد و به دلیل پسوند .php در سند درخواستی، تشخیص داد که باید این درخواست را به تفسیرگر PHP ارسال کند. سپس تفسیرگر سند heure.php را تجزیه و تحلیل کرده و تمام بخشهای کدی را که بین <? قرار دارند اجرا میکند.php > و هر یک از آنها را با خطوطی که توسط دستورالعملهای PHP، echo یا print نوشته شدهاند، جایگزین میکند. بدین ترتیب، مفسر PHP بخش کد فوق را اجرا کرده و آن را با خط تولیدشده توسط دستور echo جایگزین میکند:
پس از اجرای تمام بخشهای کد PHP، سند PHP به یک سند ساده HTML تبدیل میشود که سپس برای کلاینت ارسال میگردد.
ما تلاش خواهیم کرد تا حد امکان از ترکیب کد PHP و کد HTML خودداری کنیم. برای این منظور، میتوانیم برنامه قبلی را به صورت زیر بازنویسی کنیم:
<!-- کد PHP -->
<?php
// بازیابی زمان فعلی
$maintenant=time();
$maintenant=date("j/m/y, h:i:s",$maintenant);
?>
<!-- کد 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>
نتیجه نمایش داده شده در مرورگر یکسان است:

نسخه دوم بهتر از نسخه اول است زیرا کد PHP در کد HTML کمتر است. این باعث میشود ساختار صفحه واضحتر شود. میتوانیم یک قدم فراتر برویم و کد PHP و کد HTML را در دو فایل جداگانه قرار دهیم. کد PHP در فایل heure3.php ذخیره شده است:
<!-- کد PHP -->
<?php
//زمان فعلی را بازیابی میکند
$maintenant=time();
$maintenant=date("j/m/y, h:i:s",$maintenant);
// پاسخ را نمایش میدهد
include "heure3-page1.php";
?>
کد HTML در فایل heure3-page1.php ذخیره شده است:
<!-- کد 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>
وقتی مرورگر سند heure3.php را درخواست میکند، توسط پارسر PHP بارگذاری و تجزیه خواهد شد. با برخورد به خط
مفسر فایل heure3-page1.php را در کد منبع heure3.php وارد کرده و آن را اجرا میکند. این بدان معناست که گویی ما کد زیر PHP را داشتیم:
<!--کد PHP -->
<?php
//زمان فعلی را بازیابی میکند
$maintenant=time();
$maintenant=date("j/m/y, h:i:s",$maintenant);
?>
<!-- کد 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>
نتیجه همانند قبل است:

راهکار قرار دادن کدهای PHP و HTML در فایلهای جداگانه از این پس اتخاذ خواهد شد. این روش چندین مزیت دارد:
- ساختار صفحات ارسالی به کلاینت در کد PHP پنهان نمیشود. این بدان معناست که آنها میتوانند توسط یک «طراح وب» با مهارتهای طراحی گرافیک اما با تخصص اندک در PHP نگهداری شوند.
- کد PHP به عنوان «فرانتاند» برای درخواستهای کلاینت عمل میکند. هدف آن محاسبه دادههای مورد نیاز برای صفحهای است که در پاسخ به کلاینت بازگردانده میشود.
با این حال، این راهحل یک عیب دارد: به جای نیاز به بارگذاری یک سند واحد، به بارگذاری چندین سند نیاز دارد که ممکن است منجر به کاهش عملکرد شود.
3.6. بازیابی پارامترهای ارسالشده توسط یک کلاینت وب
3.6.1. از طریق POST
فرم زیر را در نظر بگیرید که در آن کاربر باید دو مورد اطلاعات را وارد کند: یک نام و یک سن.

پس از اینکه کاربر فیلدهای Nom و Age را پر کرد، سپس دکمه Envoyer را که از نوع submit است، کلیک میکند. سپس مقادیر فرم به سرور ارسال میشوند. سرور فرم را همراه با آرایهای که شامل مقادیری است که دریافت کرده، بازمیگرداند:

مرورگر فرم را از برنامه nomage.php زیر درخواست میکند:
<?php
//آیا پارامترهای مورد انتظار را داریم؟
$post=isset($_POST["txtNom"]) && isset($_POST["txtAge"]);
if($post){
//پارامترهای txtNom و txtAge ارسالشده توسط کلاینت را بازیابی میکند
$nom=$_POST["txtNom"];
$age=$_POST["txtAge"];
} else {
$nom="";
$age="";
}//اگر
//صفحه را نمایش دهید
include "nomage-p1.php";
?>
برخی توضیحات:
- یک فیلد فرم با نام HTML میتواند با استفاده از متد GET یا متد POST به سرور ارسال شود. اگر با استفاده از متد GET ارسال شود، سرور میتواند آن را از متغیر $_GET["champ"] و از متغیر $_POST["champ"] بازیابی کند اگراز طریق متد POST ارسال میشود.
- وجود یک مقدار داده را میتوان با تابع isset(data) بررسی کرد که در صورت وجود مقدار، true را بازمیگرداند و در غیر این صورت، false را.
- برنامه nomage.php سه متغیر ایجاد میکند: $nom برای نام فرم، $age برای سن، و $post برای نشان دادن اینکه آیا مقادیری ارسال (posted) شدهاند یا خیر. این سه متغیر به صفحه nomage-p1.php ارسال میشوند. باید توجه داشت که در حالی که این صفحه در تولید پاسخ به کلاینت نقش دارد، کلاینت از این موضوع بیخبر است. از دیدگاه کلاینت، این برنامه nomage.php است که پاسخ میدهد.
- اولین باری که یک کلاینت درخواست برنامه nomage.php را میدهد، $post روی false تنظیم میشود. این به این دلیل است که در این اولین فراخوانی، هیچ مقدار فرم به سرور ارسال نمیشود.
صفحه nomage-p1.php به شرح زیر است:
<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
//آیا هیچ مقداری ارسال شده است؟
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 فرم frmPersonne را نمایش میدهد. این توسط تگ تعریف شده است:
از آنجایی که ویژگی action تگ تعریف نشده است، مرورگر دادههای فرم را به URL که برای دریافت آن پرسوجو کرده است، یعنی c.a.d، ارسال خواهد کرد. برنامه nomage.php.
بیایید بین دو سناریوی فراخوانی برنامه nomage.php تمایز قائل شویم:
- این اولین باری است که کاربر آن را فراخوانی میکند. بنابراین، برنامه nomage.php برنامه nomage-p1.php را فراخوانی میکند و مقادیر را در اختیار آن قرار میدهد ($nom,$age,$post)=("","",false). سپس برنامه nomage-p1.php یک فرم خالی نمایش میدهد.
- کاربر فرم را پر میکند و روی دکمه Envoyer (از نوع submit) کلیک میکند. مقادیر فرم (txtNom, txtAge) سپس «ارسال» (method="post" در <form>) به برنامه nomage.php میشوند (ویژگی action در <form> تعریف نشده است). برنامه nomage.php محاسبه میکند ($nom, $age,$post) = (txtNom, txtAge, true) و آنها را به برنامه nomage-p1.php ارسال میکند که سپس یک فرم از پیش پرشده را همراه با جدول مقادیر بازیابیشده نمایش میدهد.
3.6.2. از طریق GET
اگر مقادیر فرم از طریق یک GET به سرور ارسال شوند، برنامه nomage.php به برنامه بعدی nomage2.php تبدیل میشود:
<?php
//آیا پارامترهای مورد انتظار را داریم؟
$get=isset($_GET["txtNom"]) && isset($_GET["txtAge"]);
if($get){
//پارامترهای txtNom و txtAge "GETTés" توسط کلاینت بازیابی میشوند
$nom=$_GET["txtNom"];
$age=$_GET["txtAge"];
} else {
$nom="";
$age="";
}//اگر
//صفحه نمایش داده میشود
include "nomage-p2.php";
?>
برنامه nomage-p2.php با برنامه nomage-p1.php یکسان است، به جز جزئیات زیر:
- برچسب form اصلاح شده است:
- برنامه اکنون به جای $post، متغیری به نام $get را بازیابی میکند:
در زمان اجرا، هنگامی که مقادیری در فرم وارد و به سرور ارسال میشوند، مرورگر در فیلد URL خود این واقعیت را منعکس میکند که مقادیر با استفاده از روش GET ارسال شدهاند:
![]()
3.7. بازیابی هدرهای HTTP ارسالشده توسط یک کلاینت وب
هنگامی که یک مرورگر درخواستی را به یک وبسرور ارسال میکند، تعدادی هدر HTTP را برای آن میفرستد. گاهی اوقات دسترسی به این هدرها مفید است. برای شروع، میتوانیم از آرایهٔ asociative $_SERVER استفاده کنیم. این شامل اطلاعات مختلفی است که توسط سرور وب ارائه میشود، از جمله، در میان موارد دیگر، سربرگهای HTTP که توسط کلاینت ارائه شدهاند. برنامه زیر را در نظر بگیرید که تمام مقادیر آرایه $_SERVER را نمایش میدهد:
<?php
//متغیرهای مربوط به سرور وب را نمایش میدهد
//ارسال متن ساده
header("Content-type: text/plain");
// تکرار آرایهٔ asociative $_SERVER
reset($_SERVER);
while (list($clé,$valeur)=each($_SERVER)){
echo "$clé : $valeur\n";
}//در حالی که
?>
بیایید این کد را با نام headers.php ذخیره کنیم و با استفاده از یک مرورگر وب، این URL را درخواست کنیم:

ما تعدادی اطلاعات از جمله هدرهای HTTP ارسالشده توسط مرورگر را بازیابی میکنیم. اینها مقادیری هستند که با کلیدهایی که با HTTP شروع میشوند مرتبط هستند. بیایید نگاهی دقیقتر به برخی از اطلاعات بهدستآمده در بالا بیندازیم:
انواع اسنادی که توسط کلاینت وب پذیرفته میشوند | |
انواع کاراکترهای پذیرفتهشده در اسناد | |
انواع کدگذاریهای پذیرفتهشده برای اسناد | |
انواع زبانهای پذیرفتهشده برای اسناد | |
نوع اتصال به سرور. Keep-Alive: سرور باید پس از بازگرداندن پاسخ خود، اتصال را باز نگه دارد | |
؟ حداکثر مدت زمانی که اتصال میتواند باز باقی بماند | |
ماشین میزبان مورد پرسوجو توسط کلاینت | |
هویت کلاینت | |
آدرس مشتری IP | |
پورت ارتباطی مورد استفاده توسط مشتری | |
پروتکل HTTP مورد استفاده توسط سرور | |
روش پرسوجو مورد استفاده توسط کلاینت (GET یا POST) | |
درخواست ?param1=val1¶m2=val2&... که پس از URL درخواستی (روش GET) ارسال میشود |
با ایجاد یک تغییر جزئی در کد برنامه قبلی، میتوانیم تنها سربرگهای HTTP را بازیابی کنیم:
<?php
//متغیرهای مرتبط با سرور وب را نمایش میدهد
//ارسال متن ساده
header("Content-type: text/plain");
// تکرار آرایهٔ asociative $_SERVER
reset($_SERVER);
while (list($clé,$valeur)=each($_SERVER)){
// سربرگ HTTP ؟
if(strtolower(substr($clé,0,4))=="http")
echo substr($clé,5)." : $valeur\n";
}//در حالی که
?>
نتیجه نمایش داده شده در مرورگر به شرح زیر است:

اگر شما یک هدر HTTP خاص میخواهید، برای مثال مینویسید: $_SERVER["HTTP_ACCEPT"].
3.8. بازیابی اطلاعات محیط
سرور وب/PHP در محیطی اجرا میشود که جزئیات آن را میتوان از طریق آرایه $_ENV بهدست آورد، که ویژگیهای مختلف محیط زمان اجرا را ذخیره میکند. برنامه زیر را در نظر بگیرید: env1.php:
<?php
// متغیرهای مرتبط با سرور وب را نمایش میدهد
//متن ساده را ارسال میکند
header("Content-type: text/plain");
// تکرار میکند آرایهٔ asociative $_ENV
reset($_ENV);
while (list($clé,$valeur)=each($_ENV)){
echo "$clé : $valeur\n";
}//در حالی که
?>
این در یک مرورگر (نمایان جزئی) نتیجه زیر را تولید میکند:

همانطور که در بالا مشاهده میشود، برای مثال، سرور وب/PHP روی ویندوز OS NT در حال اجرا است.
3.9. مثالها
3.9.1. تولید پویا فرم – ۱
بگذارید بهعنوان مثال تولید فرم حاوی تنها یک کنترل را در نظر بگیریم: یک لیست کشویی. محتوای این لیست کشویی بهصورت پویا با استفاده از مقادیری که از یک آرایه گرفته شدهاند، ساخته میشود. در عمل، این مقادیر اغلب از یک پایگاه داده بازیابی میشوند. فرم به این صورت است:

اگر در مثال بالا، Envoyer را اجرا کنیم، پاسخ زیر را دریافت میکنیم:

کد HTML برای فرم اولیه، پس از تولید، به شرح زیر است:
<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 شامل یک صفحه اصلی، valeurs.php، است که هم برای بازیابی فرم اولیه (فهرست مقادیر) و هم برای پردازش مقادیر آن و بازگرداندن پاسخ (مقدار انتخابشده) فراخوانی میشود. این برنامه دو صفحهٔ متفاوت تولید میکند:
- صفحه فرم اولیه، که توسط برنامه valeurs-p1.php تولید میشود
- صفحه نمایشدهنده پاسخ ارائهشده به کاربر، که توسط برنامه valeurs-p2.php تولید خواهد شد
برنامه valeurs.php به شرح زیر است:
<?php
// آرایه مقادیر
$valeurs=array("un","deux","trois","quatre","cinq","six","sept","huit","neuf","dix");
//آیا پارامترهای مورد انتظار را داریم
$requêteVide=! isset($_POST["cmbValeurs"]);
//بازیابی انتخاب کاربر
if ($requêteVide){
// درخواست اولیه
include "valeurs-p1.php";
}else{
//پاسخ به یک POST
$choix=$_POST["cmbValeurs"];
include "valeurs-p2.php";
}
?>
این تابلوی مقادیر را تعریف میکند و در صورتی که درخواست مشتری خالی باشد، valeurs-p1.php را برای تولید فرم اولیه فراخوانی میکند، یا در صورتی که یک درخواست معتبر دریافت شده باشد، valeurs-p2.php را برای تولید پاسخ فراخوانی میکند. برنامه valeurs1-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++){
echo "<option>$valeurs[$i]</option>\n";
}//برای
?>
</select>
<input type="submit" value="Envoyer" name="cmdEnvoyer">
</form>
</body>
</html>
فهرست مقادیر در لیست کشویی بهصورت پویا از جدول $valeurs که توسط valeurs.php ارسال شده است، تولید میشود. برنامه valeurs-p2.php پاسخ را تولید میکند:
<html>
<head>
<title>réponse</title>
</head>
<body>
<h2>Vous avez choisi le nombre <?php echo $choix ?></h2>
</body>
</html>
در اینجا، ما به سادگی مقدار متغیر $choix را نمایش میدهیم، که توسط valeurs.php نیز ارسال میشود.
3.9.2. تولید پویا فرم – ۲
ما مثال قبلی را برداشته و آن را به شرح زیر تغییر میدهیم. فرم نمایشدادهشده هنوز هم یکسان است:

آیا پاسخ متفاوت است؟

در پاسخ، فرم بازگردانده میشود و عدد انتخابشده توسط کاربر در زیر آن نمایش داده میشود. علاوه بر این، این عدد همان عددی است که در فهرست نمایشدادهشده توسط پاسخ بهعنوان انتخابشده ظاهر میشود.
کد برای valeurs.php به شرح زیر است:
<?php
// پیکربندی
ini_set("register_globals","off");
// جدول مقادیر
$valeurs=array("un","deux","trois","quatre","cinq","six","sept","huit","neuf","dix");
// بازیابی انتخاب کاربر، در صورت وجود
$choix=$_POST["cmbValeurs"];
// پاسخ را نمایش میدهد
include "valeurs-p1.php";
?>
توجه داشته باشید که در اینجا ما با دقت PHP را پیکربندی کردهایم تا هیچ متغیر سراسری وجود نداشته باشد. این معمولاً یک اقدام احتیاطی معقول است، زیرا متغیرهای سراسری خطرات امنیتی ایجاد میکنند. راه حل دیگر این است که تمام متغیرهای مورد استفاده را مقداردهی اولیه کنید. این کار هر متغیر سراسری با نام مشابه را «بهروزرسانی» (overwrite) میکند.
صفحه فرم توسط 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++){
// اگر گزینهٔ فعلی با انتخاب کاربر مطابقت داشته باشد، آن را انتخاب کنید
if (isset($choix) && $choix==$valeurs[$i])
echo "<option selected>$valeurs[$i]</option>\n";
else echo "<option>$valeurs[$i]</option>\n";
}//برای
?>
</select>
<input type="submit" value="Envoyer" name="cmdEnvoyer">
</form>
<?php
// صفحهٔ بعدی
if(isset($choix)){
echo "<hr>\n";
echo "<h3>Vous avez choisi le nombre $choix</h3>\n";
}
?>
</body>
</html>
برنامهٔ تولید صفحه بر متغیر $choix که توسط برنامهٔ valeurs.php ارسال میشود، متکی است. در اینجا باید توجه داشت که ساختار صفحه HTML به شدت با کد PHP شلوغ شده است. برنامه فرانتاند valeurs.php میتواند کارهای بیشتری انجام دهد، همانطور که در نسخه جدید زیر نشان داده شده است:
<?php
// پیکربندی
ini_set("register_globals","off");
// جدول مقادیر
$valeurs=array("un","deux","trois","quatre","cinq","six","sept","huit","neuf","dix");
// انتخاب کاربر را بازیابی میکند، در صورت وجود
$choix=$_POST["cmbValeurs"];
// فهرست مقادیر قابل نمایش را محاسبه میکند
$HTMLvaleurs="";
for($i=0;$i<count($valeurs);$i++){
//اگر گزینهٔ جاری با انتخاب کاربر مطابقت داشت، آن را انتخاب میکند
if (isset($choix) && $choix==$valeurs[$i])
$HTMLvaleurs.="<option selected>$valeurs[$i]</option>\n";
else $HTMLvaleurs.="<option>$valeurs[$i]</option>\n";
}//برای
//بخش دوم صفحه را محاسبه میکند
$HTMLpart2="";
if(isset($choix)){
$HTMLpart2="<hr>\n";
$HTMLpart2.="<h3>Vous avez choisi le nombre $choix</h3>\n";
}//اگر
//پاسخ نمایش داده میشود
include "valeurs-p2.php";
?>
این صفحه اکنون توسط برنامه زیر تولید میشود: valeurs-p2.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
// نمایش لیست مقادیر
echo $HTMLvaleurs;
?>
</select>
<input type="submit" value="Envoyer" name="cmdEnvoyer">
</form>
<?php
//نمایش بخش ۲
echo $HTMLpart2;
?>
</body>
</html>
کد HTML اکنون بخش عمدهای از کد PHP را از خود جدا کرده است. با این حال، بیایید هدف از تقسیم سیستم به یک برنامه فرانتاند که درخواست مشتری را تحلیل و پردازش میکند و برنامههای صرفاً مسئول نمایش صفحاتی هستند که با دادههای ارسالشده توسط فرانتاند پیکربندی شدهاند، را به یاد آوریم: هدف، جدا کردن کار توسعهدهنده PHP از کار طراح گرافیک است. توسعهدهنده PHP روی فرانتاند کار میکند، در حالی که طراح گرافیک روی صفحات وب کار میکند. در نسخه جدید ما، طراح گرافیک دیگر، برای مثال، نمیتواند روی بخش ۲ صفحه کار کند، زیرا دیگر به کد آن HTML دسترسی ندارد. در نسخه اول، این امکان برایشان وجود داشت. بنابراین هیچکدام از این دو روش کامل نیستند.
3.9.3. تولید پویا فرم – ۳
ما با همان مشکلی که قبلاً داشتیم روبرو هستیم، اما این بار مقادیر از یک پایگاه داده گرفته میشوند. در مثال ما، این پایگاه داده MySQL است:
- این پایگاه داده dbValeurs نام دارد
- مالک آن admDbValeurs با رمز عبور mdpDbValeurs است
- پایگاه داده یک جدول واحد به نام tvaleurs دارد
- این جدول تنها یک فیلد عددی به نام «value» دارد
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)
در یک برنامه کاربردی که از پایگاه داده استفاده میکند، مراحل زیر معمولاً درگیر هستند:
- اتصال به SGBD
- ارسال پرسوجوها SQL به یک پایگاه داده SGBD
- پردازش نتایج این پرسوجوها
- بستن اتصال به SGBD
مراحل ۲ و ۳ بهطور مکرر انجام میشوند و اتصال تنها زمانی بسته میشود که پایگاه داده بهطور کامل مورد استفاده قرار گرفته باشد. این رویهای نسبتاً استاندارد برای هر کسی است که بهصورت تعاملی از پایگاه داده استفاده کرده باشد. جدول زیر دستورالعملهای PHP را برای انجام این عملیات مختلف با استفاده از SGBD و MySQL ارائه میدهد:
$connexion = mysql_pconnect($hote, $user, $pwd) $connexion=mysql_connect($hote,$user,$pwd) $hote: نام میزبان ماشینی که SGBD و MySQL روی آن در حال اجرا هستند. در واقع میتوان با نمونههای راه دور SGBD و MySQL کار کرد. $user: نام یک کاربر شناختهشدهٔ SGBD و MySQL $pwd: رمز عبور آنها $connexion: اتصال ایجاد شده mysql_pconnect یک اتصال پایدار با SGBD و MySQL برقرار میکند. چنین ارتباطی در پایان اسکریپت بسته نمیشود. این ارتباط باز باقی میماند. بنابراین، هنگامی که نیاز به باز کردن یک ارتباط جدید با SGBD باشد، PHP به دنبال یک ارتباط موجود متعلق به همان کاربر خواهد گشت. اگر ارتباطی را پیدا کند، از آن استفاده میکند. این کار در زمان صرفهجویی میکند. mysql_connect یک اتصال غیرمستمر ایجاد میکند که بنابراین پس از اتمام کار با SGBD و MySQL بسته میشود. | |
$résultats=mysql_db_query($base,$requête,$connexion) $base: پایگاه داده برای MySQL که با آن کار خواهیم کرد $requête: یک پرسوجو روی SQL (درج، حذف، بهروزرسانی، انتخاب و غیره) $connexion: اتصال به SGBD MySQL $résultats: نتایج پرسوجو – این نتایج بسته به اینکه پرسوجو یک عملیات SELECT باشد یا عملیات بهروزرسانی (INSERT، UPDATE، DELETE و غیره) متفاوت است. | |
$résultats=mysql_db_query($base,"select ...",$connexion) نتیجه یک عبارت SELECT یک جدول است، یعنی مجموعهای از سطرها و ستونها. به این جدول میتوان از طریق $résultats دسترسی داشت. تابع $ligne=mysql_fetch_row($résultats) یک سطر را از جدول میخواند و آن را در $ligne به صورت یک آرایه ذخیره میکند. بنابراین، $ligne[i] ستون i از سطر بازیابیشده است. تابع mysql_fetch_row را میتوان بارها فراخوانی کرد. هر بار، یک سطر جدید را از جدول $résultats میخواند. هنگامی که به انتهای جدول رسید، این تابع مقدار false را برمیگرداند. بنابراین، جدول $résultats را میتوان به صورت زیر استفاده کرد: while($row = mysql_fetch_row($results)) { // پردازش سطر فعلی $row }//while | |
$résultats=mysql_db_query($base,"insert ...",$connexion) مقدار $résultats بسته به اینکه عملیات موفق بوده یا ناموفق، میتواند true یا false باشد. در صورت موفقیت، تابع mysql_affected_rows تعداد سطرهایی را که توسط عملیات بهروزرسانی تغییر کردهاند بازمیگرداند. | |
mysql_close($connection) $connexion: یک اتصال به SGBD MySQL |
کد فرانتاند برای valeurs.php به صورت زیر در میآید:
<?php
// پیکربندی
ini_set("register_globals","off");
ini_set("display_errors","off");
ini_set("track_errors","on");
// جدول مقادیر
list($erreur,$valeurs)=getValeurs();
// آیا خطایی رخ داده است؟
if($erreur){
// نمایش صفحه خطا
include "valeurs-err.php";
//پایان
return;
}//اگر
// انتخاب کاربر را بازیابی کنید، در صورت وجود
$choix=$_POST["cmbValeurs"];
//محاسبه فهرست مقادیر قابل نمایش
$HTMLvaleurs="";
for($i=0;$i<count($valeurs);$i++){
//اگر گزینهٔ فعلی با انتخاب کاربر مطابقت داشته باشد، آن را انتخاب میکند
if (isset($choix) && $choix==$valeurs[$i])
$HTMLvaleurs.="<option selected>$valeurs[$i]</option>\n";
else $HTMLvaleurs.="<option>$valeurs[$i]</option>\n";
}//برای
//بخش دوم صفحه را محاسبه میکند
$HTMLpart2="";
if(isset($choix)){
$HTMLpart2="<hr>\n";
$HTMLpart2.="<h3>Vous avez choisi le nombre $choix</h3>\n";
}//اگر
// پاسخ نمایش داده میشود
include "valeurs-p1.php";
//پایان
return;
// ------------------------------------------------------------------------
function getValeurs(){
//مقادیر را از یک پایگاه داده بازیابی میکند MySQL
$user="admDbValeurs";
$pwd="mdpDbValeurs";
$db="dbValeurs";
$hote="localhost";
$table="tvaleurs";
$champ="valeur";
// یک اتصال پایدار به سرور MySQL را باز میکند
// یا در صورت عدم موفقیت، یک اتصال معمولی
($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.");
// مقادیر را بازیابی میکند
$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.");
//مقادیر در یک آرایه قرار میگیرند
$valeurs=array();
while($ligne=mysql_fetch_row($selectValeurs)){
$valeurs[]=$ligne[0];
}//در حالی که
//بستن اتصال (اگر پایدار باشد، در واقع بسته نخواهد شد)
mysql_close($connexion);
//بازگرداندن نتیجه
return array("",$valeurs);
}//getValeurs
?>
این بار، مقادیری که باید در لیست کشویی قرار گیرند، نه توسط یک آرایه، بلکه توسط تابع getValeurs() فراهم میشوند. این تابع:
- یک اتصال پایدار (mysql_pconnect) به سرور mySQL را با ارسال نام کاربری ثبتشده و رمز عبور آن باز میکند.
- پس از برقراری اتصال، یک پرسوجوی select برای بازیابی مقادیر موجود در جدول tvaleurs از پایگاه داده dbValeurs صادر میشود.
- نتیجه select در آرایه $valeurs قرار میگیرد که به برنامه فراخواننده بازگردانده میشود.
- این تابع در واقع یک آرایه حاوی دو نتیجه ($erreur, $valeurs) را بازمیگرداند، که در آن عنصر اول در صورت وجود خطا، یک پیام خطا است و در غیر این صورت یک رشته خالی است.
- برنامه فراخوانیکننده بررسی میکند که آیا خطایی رخ داده است و در صورت مثبت بودن، صفحه valeurs-err.php را نمایش میدهد. این به شرح زیر است:
<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>
- اگر هیچ خطایی رخ نداده باشد، برنامهٔ فراخوانیکننده به مقادیر آرایهٔ $valeurs دسترسی دارد. بنابراین دوباره به مشکل قبلی بازمیگردیم.
در اینجا دو مثال از اجرا آورده شده است:
- با خطا

- بدون خطا

3.9.4. تولید پویا فرم - ۴
در مثال قبلی، اگر SGBD را تغییر دهیم چه اتفاقی میافتد؟ اگر به عنوان مثال از MySQL به Oracle یا SQL Server تغییر دهیم چه؟ ما باید تابع getValeurs() را که مقادیر را فراهم میکند، بازنویسی کنیم. مزیت گروهبندی کد لازم برای بازیابی مقادیری که باید در لیست نمایش داده شوند در یک تابع واحد این است که کد مورد اصلاح بهوضوح هدفمند است و در سراسر برنامه پراکنده نیست. توابع getValeurs() را میتوان طوری بازنویسی کرد که از SGBD مورد استفاده مستقل باشد. تنها کافی است که این تابع به جای کار کردن مستقیماً با SGBD، با درایور ODBC برای SGBD کار کند.
بانکهای اطلاعاتی زیادی در بازار موجود هستند. برای استانداردسازی دسترسی به بانکهای اطلاعاتی در ویندوز MS، مایکروسافت رابطی به نام ODBC (Open DataBase Connectivity) توسعه داده است. این لایه ویژگیهای خاص هر پایگاه داده را پشت یک رابط استاندارد پنهان میکند. در ویندوز MS، درایورهای متعددی از ODBC وجود دارند که دسترسی به پایگاههای داده را تسهیل میکنند. در اینجا، بهعنوان مثال، فهرستی از درایورهای ODBC نصبشده روی یک سیستم ویندوزی آورده شده است:

SGBD MySQL همچنین دارای یک درایور ODBC است. یک برنامه که به درایورهای ODBC متکی است، میتواند از هر یک از پایگاههای داده فوق بدون نیاز به بازنویسی استفاده کند.
![]() |
بیایید پایگاه داده MySQL dbValeurs خود را از طریق یک درایور ODBC در دسترس قرار دهیم. رویه زیر برای ویندوز ۲۰۰۰ است. برای سیستمهای Win9x، این رویه بسیار مشابه است. مدیر منابع ODBC را فعال کنید:

برای افزودن یک منبع داده جدید ODBC، از دکمه [Add] استفاده کنید:

درایور ODBC MySQL را انتخاب کنید، سپس [Terminer] را انتخاب کرده و ویژگیهای منبع داده را مشخص کنید:

نام اختصاصدادهشده به منبع داده ODBC (مقادیر odbc) | |
نام ماشینی که میزبان SGBD و MySQL است و منبع داده را مدیریت میکند (localhost) | |
نام پایگاه داده MySQL، که منبع داده (dbValeurs) است | |
کاربری با حقوق دسترسی کافی به پایگاه داده MySQL برای مدیریت آن (admDbValeurs) | |
رمز عبور آنها (mdpDbValeurs) |
PHP قادر به کار با درایورهای ODBC است. جدول زیر توابع مفیدی را که باید از آنها آگاه باشید، فهرست میکند:
$connexion = odbc_pconnect($dsn, $user, $pwd) $connexion=odbc_connect($dsn,$user,$pwd) $dsn: نام DSN (نام منبع داده) ماشین که SGBD روی آن در حال اجرا است $user: نام کاربری که برای SGBD شناخته شده است $pwd: رمز عبور آنها $connexion: اتصال ایجادشده odbc_pconnect یک اتصال پایدار با SGBD برقرار میکند. چنین ارتباطی در پایان اسکریپت بسته نمیشود. این ارتباط باز باقی میماند. بنابراین، هنگامی که نیاز به باز کردن یک ارتباط جدید با SGBD باشد، PHP به دنبال یک ارتباط موجود متعلق به همان کاربر میگردد. اگر ارتباطی را پیدا کند، از آن استفاده میکند. این کار در زمان صرفهجویی میکند. odbc_connect یک اتصال غیرمستمر ایجاد میکند که بنابراین پس از اتمام وظیفه با SGBD بسته میشود. | |
$requêtePréparée=odbc_prepare($connexion,$requête) $requête: یک پرسوجو SQL (درج، حذف، بهروزرسانی، انتخاب، ...) $connexion: اتصال به SGBD پرسوجوی $requête را تحلیل کرده و آن را برای اجرا آماده میکند. پرسوجوی «آمادهشده» به این ترتیب توسط نتیجه $requêtePréparée ارجاع داده میشود. آمادهسازی یک پرسوجو برای اجرا اجباری نیست اما عملکرد را بهبود میبخشد، زیرا پرسوجو تنها یک بار تحلیل میشود. سپس پرسوجوی آمادهشده اجرا میشود. اگر یک پرسوجوی آماده نشده بارها اجرا شود، هر بار تحلیل میشود که این امر غیرضروری است. پس از آمادهسازی، پرسوجو توسط $res=odbc_execute($requêtePréparée) بسته به موفقیت یا عدم موفقیت اجرای پرسوجو، مقدار true یا false را برمیگرداند | |
نتیجه یک عبارت SELECT یک جدول است، یعنی مجموعهای از سطرها و ستونها. این جدول را میتوان از طریق $requêtePréparée دسترسی داشت. $res=odbc_fetch_row($requêtePréparée) یک سطر را از جدول نتیجه select میخواند. بسته به موفقیت یا عدم موفقیت پرسوجو، مقدار true یا false را برمیگرداند. عناصر سطر بازیابیشده از طریق تابع odbc_result در دسترس هستند: $val=odbc_result($requêtePréparée,i): ستون i از سطری که به تازگی خوانده شده است $val=odbc_result($requêtePréparée,"nomColonne"): ستون nomColonne از سطری که به تازگی خوانده شده است تابع odbc_fetch_row را میتوان بارها فراخوانی کرد. هر بار یک سطر جدید از جدول نتایج را میخواند. وقتی به انتهای جدول رسید، تابع مقدار false را برمیگرداند. بنابراین میتوان از جدول نتایج به صورت زیر استفاده کرد: | |
odbc_close($connection) $connexion: یک اتصال به SGBD MySQL |
تابع getValeurs() که مسئول بازیابی مقادیر از پایگاه داده ODBC است، به شرح زیر است:
// ------------------------------------------------------------------------
function getValeurs(){
// مقادیر را از یک پایگاه داده بازیابی میکند MySQL
$user="admDbValeurs";
$pwd="mdpDbValeurs";
$db="dbValeurs";
$dsn="odbc-valeurs";
$table="tvaleurs";
$champ="valeur";
//یک اتصال پایدار به سرور MySQL باز میکند
//یا، در صورت عدم موفقیت، یک اتصال معمولی
($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.");
// مقادیر را بازیابی میکند
$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.");
//مقادیر در یک آرایه قرار میگیرند
$valeurs=array();
while(odbc_fetch_row($selectValeurs)){
$valeurs[]=odbc_result($selectValeurs,$champ);
}//در حالی که
//بستن اتصال (اگر پایدار باشد، در واقع بسته نخواهد شد)
odbc_close($connexion);
//بازگرداندن نتیجه
return array("",$valeurs);
}//getValeurs
?>
اگر برنامه جدید را بدون فعالسازی پایگاه داده odbc-valeurs اجرا کنید، نتیجه زیر را دریافت میکنید:

شایان ذکر است که کد خطای بازگردانده شده توسط درایور ODBC (odbc_error()=S1000) چندان واضح نیست. اگر پایگاه داده odbc-valeurs در دسترس قرار گیرد، نتایج مشابهی با قبل به دست میآید.
در نتیجه، میتوان گفت که این راهحل برای نگهداری برنامه مفید است. در واقع، اگر نیاز به تغییر پایگاه داده باشد، خود برنامه نیازی به تغییر نخواهد داشت. مدیر سیستم به سادگی یک منبع داده جدید، ODBC، برای پایگاه داده جدید ایجاد خواهد کرد. مجدداً، با در نظر گرفتن نگهداری، توصیه میشود که پارامترهای دسترسی به پایگاه داده را تنظیم کنید ($dsn, $user, $pwd) در یک فایل جداگانه که برنامه در زمان راهاندازی (include) آن را بارگذاری میکند، پیکربندی شود.
3.9.5. استخراج مقادیر از یک فرم
ما قبلاً در چندین مورد مقادیری را از فرمی که توسط یک کلاینت وب ارسال شده است، بازیابی کردهایم. مثال زیر فرمی را نشان میدهد که شامل رایجترین اجزای HTML است و برای بازیابی پارامترهای ارسالشده توسط مرورگر کلاینت طراحی شده است. فرم به شرح زیر است:

این فرم از پیش پر شده است. کاربر سپس میتواند آن را تغییر دهد:

اگر روی دکمه [Envoyer] کلیک کنند، سرور لیست مقادیر فرم را بازمیگرداند:

فرم یک صفحهٔ ایستا است HTML balises.html:
<html>
<head>
<title>balises</title>
<script language="JavaScript">
function effacer(){
alert("Vous avez cliqué sur le bouton Effacer");
}//پاک
</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>
جدول زیر نقش برچسبهای مختلف در این سند و مقدار بازیابیشده توسط PHP برای انواع مختلف اجزای فرم را خلاصه میکند. مقدار یک فیلد با نام HTML C میتواند از طریق یک POST یا یک GET ارسال شود. در مورد اول، این مقدار در متغیر $_GET["C"] بازیابی میشود و در مورد دوم در متغیر $_POST["C"]. جدول زیر استفاده از POST را فرض میکند.
بررسی | برچسب HTML | مقدار بازیابیشده توسط PHP |
<form method="POST" > | ||
<input type="text" name="txtSaisie" size="20" value="چند کلمه"> | $_POST["txtSaisie"]: مقداری که در فیلد txtSaisie فرم وجود دارد | |
<input type="password" name="txtMdp" size="20" value="unMotDePasse"> | $_POST["txtmdp"]: مقداری که در فیلد txtMdp فرم وجود دارد | |
<textarea rows="2" name="areaSaisie" cols="20"> line1 خط ۲ خط ۳ </textarea> | $_POST["areaSaisie"]: خطوط موجود در فیلد areaSaisie به صورت یک رشته کاراکتری واحد: "ligne1\r\nligne2\r\nligne3". خطوط با دنباله "\r\n" از یکدیگر جدا شدهاند. | |
<input type="radio" value="Yes" name="R1">بله <input type="radio" name="R1" value="no" checked>خیر | $_POST["R1"]: مقدار (=value) دکمهٔ رادیویی انتخابشده به عنوان «بله» یا «خیر» به ترتیب مناسب. | |
<input type="checkbox" name="C1" value="one">1 <input type="checkbox" name="C2" value="two" checked>2 <input type="checkbox" name="C3" value="three">3 | $_POST["C1"]: مقدار (=value) چکباکس در صورت تیکخورده بودن؛ در غیر این صورت، متغیر وجود ندارد. بنابراین، اگر چکباکس C1 تیکخورده باشد، $_POST["C1"] برابر با "one" است؛ در غیر این صورت، $_POST["C1"] وجود ندارد. | |
<select size="1" name="cmbValeurs"> <option>انتخاب۱</option> <option selected>option2</option> <option>option3</option> </select> | $_POST["cmbValeurs"]: گزینهای که از لیست انتخاب شده است، برای مثال "option3". | |
<select size="3" name="lst1"> <option selected>list1</option> <option>list2</option> <option>list3</option> <option>list4</option> <option>list5</option> </select> | $_POST["lst1"]: گزینهای از لیست انتخاب شده است، برای مثال "list5". | |
<select size="3" name="lst2[]" multiple> <option>list1</option> <option>list2</option> <option selected>list3</option> <option>list4</option> <option>list5</option> </select> | $_POST["lst2"]: آرایهای از گزینهها که از لیست انتخاب شدهاند، برای مثال ["liste3,"liste5"]. توجه کنید به سینتکس خاص تگ HTML برای این مورد خاص: lst2[]. | |
<input type="hidden" name="secret" value="uneValeur"> | $_POST["secret"]: مقدار (=value) فیلد، در این مورد «uneValeur». |
در مثال ما، مقادیر فرم به برنامه parameters.php ارسال میشوند:
کد مربوط به مورد دوم به شرح زیر است:
<?php
// پیکربندی
ini_set("register_globals","off");
ini_set("display_errors","off");
// روش فراخوانی
$méthode=$_SERVER["REQUEST_METHOD"];
// بازیابی پارامترها
// این بستگی به روشی دارد که برای ارسال آنها استفاده میشود
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"];
//آیا درخواست معتبر است؟
$requêteValide=isset($R1) && (isset($C1) || isset($C2) || isset($C3))
&& isset($txtSaisie) && isset($txtMdp) && isset($areaSaisie)
&& isset($cmbValeurs) && isset($lst1) && isset($lst2)
&& isset($secret);
//نمایش صفحه
if ($requêteValide)
include "parameters-p1.php";
else include "balises.html";
?>
بیایید چند نکته از این برنامه را با جزئیات بیشتری بررسی کنیم:
- این برنامه مشخص نمیکند که مقادیر فرم چگونه به سرور ارسال میشوند. در هر دو حالت ممکن (GET و POST)، دیکشنری مقادیر ارسالشده توسط $param ارجاع داده میشود.
- از محتویات فیلد areaSaisie «line1\r\nline2\r\n...» با استفاده از تابع explode("\r\n", $param["areaSaisie"]) یک آرایه از رشتهها ایجاد میکنیم. این آرایه [ligne1,ligne2,...] را به ما میدهد. از روی آن، با استفاده از تابع implode، رشته "ligne1<br>ligne2<br>..." را ایجاد میکنیم.
- مقدار لیست چندگزینهای `lst2` یک آرایه است، برای مثال `["option3","option5"]`. از این آرایه، یک رشتهٔ متنی "option3 option5" با استفاده از تابع `implode` ایجاد میشود.
- برنامه بررسی میکند که همه پارامترها تنظیم شدهاند. مهم است که در اینجا توجه شود هر URL میتواند بهصورت دستی یا برنامهای فراخوانی شود و پارامترهای مورد انتظار لزوماً موجود نباشند. اگر هر یک از پارامترها وجود نداشته باشند، صفحه balises.html نمایش داده میشود؛ در غیر این صورت، صفحه parameters-p1.php نمایش داده میشود. این صفحه مقادیر بازیابیشده و محاسبهشده در parameters.php را در یک جدول نمایش میدهد:
<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. ردیابی جلسه
3.10.1. مشکل
یک برنامهٔ وب ممکن است شامل چندین تبادل فرم بین سرور و کلاینت باشد. این فرایند به شرح زیر عمل میکند:
مرحله ۱
- کلاینت C1 یک اتصال با سرور برقرار میکند و درخواست اولیه خود را ارسال میکند.
- سرور فرم F1 را برای کلاینت C1 ارسال میکند و اتصال بازشده در مرحلهٔ ۱ را میبندد.
مرحله ۲
- کلاینت C1 فرم را پر کرده و آن را به سرور بازمیفرستد. برای این کار، مرورگر یک اتصال جدید به سرور باز میکند.
- سرور دادههای فرم ۱ را پردازش میکند، اطلاعات I1 را از آن محاسبه میکند، یک فرم F2 را به کلاینت C1 ارسال میکند و اتصال باز شده در مرحله ۳ را میبندد.
مرحله ۳
- چرخه مراحل ۳ و ۴ در مراحل ۵ و ۶ تکرار میشود. در پایان مرحله ۶، سرور دو فرم، F1 و F2 را دریافت کرده و اطلاعات I1 و I2 را از روی آنها محاسبه خواهد کرد.
مسئله این است: سرور چگونه اطلاعات I1 و I2 مرتبط با کلاینت C1 را حفظ میکند؟ این مشکل به عنوان ردیابی جلسه برای کلاینت C1 شناخته میشود. برای درک علت اصلی، بیایید معماری یک برنامه سرور، TCP-IP، را که همزمان به چندین کلاینت خدمترسانی میکند، بررسی کنیم:
![]() |
در یک برنامهٔ معمول مشتری-سرور TCP-IP:
- کلاینت یک اتصال با سرور برقرار میکند
- از طریق این اتصال با سرور تبادل داده میکند
- ارتباط توسط یکی از دو طرف بسته میشود
دو نکته کلیدی این مکانیزم عبارتند از:
- برای هر کلاینت یک اتصال واحد ایجاد میشود
- این اتصال در تمام مدت مکالمه بین سرور و مشتریاش استفاده میشود
آنچه به سرور امکان میدهد در هر لحظه بداند با کدام کلاینت در حال کار است، اتصال است – یا به عبارت دیگر، «لوله»ای که آن را به کلاینتش متصل میکند. از آنجا که این لوله به یک کلاینت خاص اختصاص دارد، هر چیزی که از طریق این لوله دریافت میشود از آن کلاینت میآید و هر چیزی که از طریق این لوله ارسال میشود به همان کلاینت میرسد.
مکانیزم کلاینت-سرور HTTP از نمودار بالا پیروی میکند، با این تفاوت که تعامل کلاینت-سرور به یک تبادل واحد بین کلاینت و سرور محدود میشود:
- کلاینت یک اتصال به سرور برقرار میکند و درخواست خود را ارسال میکند
- سرور پاسخ خود را ارسال میکند و اتصال را قطع میکند
اگر در زمان T1، کلاینت C درخواستی به سرور ارسال کند، یک اتصال C1 دریافت میکند که برای یک تبادل درخواست-پاسخ واحد استفاده خواهد شد. اگر در زمان T2، همین کلاینت درخواست دومی به سرور ارسال کند، یک اتصال C2 به آن اختصاص داده میشود که با اتصال C1 متفاوت است. از دیدگاه سرور، بنابراین تفاوتی بین این درخواست دوم از کاربر C و درخواست اولیه او وجود ندارد: در هر دو مورد، سرور با مشتری مانند یک مشتری جدید رفتار میکند. برای اینکه پیوندی بین اتصالات مختلف مشتری C به سرور وجود داشته باشد، مشتری C باید توسط سرور به عنوان یک «مشتری دائمی» «شناسایی» شود و سرور باید اطلاعاتی را که در مورد آن مشتری دائمی در اختیار دارد، بازیابی کند.
بیایید سیستمی را تصور کنیم که به شرح زیر عمل میکند:
- یک صف واحد وجود دارد
- چندین باجه وجود دارد. بنابراین، چندین مشتری میتوانند بهطور همزمان مورد خدمترسانی قرار گیرند. وقتی یک باجه خالی میشود، مشتری صف را ترک میکند تا در آن باجه مورد خدمترسانی قرار گیرد.
- اگر این اولین مراجعه مشتری باشد، کارمند باجه یک توکن با شماره روی آن به او میدهد. مشتری فقط میتواند یک سؤال بپرسد. پس از دریافت پاسخ، باید باجه را ترک کرده و به انتهای صف برود. کارمند باجه مشخصات مشتری را در پروندهای با همان شماره ثبت میکند.
- وقتی نوبتشان دوباره برسد، ممکن است مشتری توسط کارمندی متفاوت از کسی که قبلاً به او خدمترسانی کرده بود، مورد خدمترسانی قرار گیرد. کارمند، توکن او را درخواست میکند و پروندهای را که شماره توکن روی آن است، پیدا میکند. بار دیگر، مشتری درخواستی ارائه میدهد، پاسخی دریافت میکند و اطلاعات بیشتری به پروندهاش اضافه میشود.
- و به همین ترتیب... با گذشت زمان، مشتری به تمام پرسشهای خود پاسخ خواهد گرفت. ارتباط بین پرسشهای مختلف از طریق توکن و پرونده مرتبط با آن حفظ میشود.
مکانیزم ردیابی جلسه در یک برنامه وب مشتری-سرور به روشی مشابه عمل میکند:
- هنگام ارسال اولین درخواست، یک توکن توسط وبسرور به کلاینت صادر میشود
- آنها این توکن را در هر درخواست بعدی برای شناسایی خود ارائه خواهند داد
توکن میتواند اشکال مختلفی داشته باشد:
- یک فیلد مخفی در یک فرم
- کلاینت اولین درخواست خود را ارسال میکند (سرور این را تشخیص میدهد زیرا کلاینت توکنی ندارد)
- سرور پاسخ خود (یک فرم) را بازمیگرداند و توکن را در یک فیلد مخفی درون آن قرار میدهد. در این نقطه، اتصال بسته میشود (کلاینت با توکن خود نقطهٔ سرویس را ترک میکند). ممکن است سرور اطلاعات را با این توکن مرتبط کرده باشد.
- کلاینت با ارسال مجدد فرم، درخواست دوم را ارسال میکند. سرور توکن را از فرم بازیابی میکند. سپس میتواند با استفاده از توکن برای دسترسی به اطلاعاتی که در درخواست اول محاسبه شده است، درخواست دوم کلاینت را پردازش کند. اطلاعات جدید به رکورد مرتبط با توکن اضافه میشود، پاسخ دوم برای کلاینت ارسال میشود و اتصال برای بار دوم بسته میشود. توکن بار دیگر در فرم پاسخ گنجانده شده است تا کاربر بتواند آن را در درخواست بعدی خود ارائه دهد.
- و غیره...
نقطه ضعف اصلی این تکنیک این است که توکن باید در یک فرم گنجانده شود. اگر پاسخ سرور یک فرم نباشد، دیگر نمیتوان از روش فیلد مخفی استفاده کرد.
- روش کوکی
- کلاینت اولین درخواست خود را ارسال میکند (سرور این را تشخیص میدهد زیرا کلاینت توکنی ندارد)
- سرور پاسخ خود را ارسال میکند و یک کوکی به هدرهای آن اضافه میکند (HTTP). این کار با استفاده از دستور Set-Cookie انجام میشود:
Set-Cookie: param1=value1;param2=value2;....
که در آن parami نام پارامترها و valeursi مقادیر آنها هستند. در میان پارامترها، توکن نیز وجود خواهد داشت. اغلب اوقات، تنها توکن در کوکی قرار میگیرد و سایر اطلاعات توسط سرور در پوشهای که به توکن مرتبط است، ثبت میشود. مرورگری که کوکی را دریافت میکند، آن را در فایلی روی هارد دیسک ذخیره خواهد کرد. پس از پاسخ سرور، اتصال بسته میشود (کلاینت جلسه را با توکن خود ترک میکند).
- (ادامه)
- کلاینت دومین درخواست خود را به سرور ارسال میکند. هر بار که درخواستی به سرور ارسال میشود، مرورگر تمام کوکیهای خود را بررسی میکند تا ببیند آیا کوکیای از سرور مورد نظر دارد یا خیر. اگر چنین باشد، آن را در قالب یک دستور HTTP – دستور «Cookie» – که نحوی مشابه دستور Set-Cookie مورد استفاده توسط سرور دارد، به سرور ارسال میکند:
Cookie: param1=value1;param2=value2;....
سرور در میان هدرهای HTTP ارسالشده توسط مرورگر، توکن را پیدا میکند که به آن امکان میدهد کلاینت را شناسایی کرده و اطلاعات مرتبط با آن را بازیابی کند.
این رایجترین شکل توکن است. این روش یک عیب دارد: کاربر میتواند مرورگر خود را طوری تنظیم کند که کوکیها را قبول نکند. در این صورت، چنین کاربرانی قادر به دسترسی به برنامههای وبی که از کوکیها استفاده میکنند، نخواهند بود.
- بازنویسی URL
- کلاینت اولین درخواست خود را ارسال میکند (سرور این را تشخیص میدهد زیرا کلاینت توکنی ندارد)
- سرور پاسخ خود را ارسال میکند. این پاسخ شامل لینکهایی است که کاربر باید برای ادامه کار با برنامه از آنها استفاده کند. در URL هر یک از این لینکها، سرور توکن را به شکل URL;token=value اضافه میکند.
- وقتی کاربر برای ادامه استفاده از برنامه روی یکی از لینکها کلیک میکند، مرورگر درخواستی را به وبسرور ارسال میکند که در آن توکن مورد نظر در هدرها به شکل HTTP URL URL;token=value گنجانده شده است. سپس سرور قادر است توکن را بازیابی کند.
3.10.2. API و PHP برای ردیابی جلسه
اکنون روشهای اصلی مفید برای ردیابی جلسه را ارائه میدهیم:
جلسهای را که درخواست فعلی به آن تعلق دارد، آغاز میکند. اگر درخواست هنوز بخشی از هیچ جلسهای نبود، یک جلسه جدید ایجاد میشود. | |
شناسهی جلسهی جاری | |
واژهنامهٔ ذخیرهسازی دادههای جلسه. دسترسی خواندن و نوشتن | |
دادههای موجود در جلسهٔ جاری را حذف میکند. این دادهها در تبادل جاری بین کلاینت و سرور در دسترس باقی میمانند اما در تبادل بعدی در دسترس نخواهند بود. |
3.10.3. مثال ۱
ما مثالی را ارائه میدهیم که از کتاب «Programming with J2EE» منتشرشده توسط Wrox و توزیعشده توسط Eyrolles الهام گرفته شده است. این مثال نشان میدهد که یک جلسه PHP چگونه کار میکند. صفحه اصلی به شرح زیر است:

این شامل عناصر زیر است:
- ID برای جلسه، که از طریق تابع session_id() به دست آمده است. این ID که توسط مرورگر تولید میشود، از طریق کوکی که مرورگر هنگام درخواست URL از همان ساختار درختی بازمیگرداند، به کلاینت ارسال میشود. این همان چیزی است که جلسه را حفظ میکند.
- یک شمارنده که با هر درخواست از مرورگر افزایش مییابد و نشان میدهد که جلسه در حال حفظ است.
- یک لینک برای حذف دادههای مرتبط با جلسهٔ جاری. این کار توسط تابع session_destroy() انجام میشود
- یک لینک برای بارگذاری مجدد صفحه
کد برنامه cycledevie.php به شرح زیر است:
<?php
//cycledevie.php
//پیکربندی
ini_set("register_globals","off");
ini_set("display_errors","off");
//یک جلسه آغاز میشود
session_start();
//آیا باید باطل شود؟
$action=$_GET["action"];
if($action=="invalider"){
//پایان جلسه
session_destroy();
}//اگر
// مدیریت شمارنده
if(! isset($_SESSION["compteur"]))
// شمارنده وجود ندارد – آن را ایجاد کنید
$_SESSION["compteur"]=0;
// شمارنده وجود دارد – آن را افزایش دهید
else $_SESSION["compteur"]++;
//بازیابی ID برای جلسهٔ جاری
$idSession=session_id();
// استخراج شمارنده
$compteur=$_SESSION["compteur"];
//کنترل به صفحه نمایش منتقل میشود
include "cycledevie-p1.php";
?>
لطفاً به نکات زیر توجه کنید:
- به محض شروع برنامه، یک جلسه آغاز میشود. اگر کلاینت توکن جلسهای ارسال کرده باشد، جلسه با آن شناسه از سر گرفته شده و تمام دادههای مرتبط در دیکشنری $_SESSION قرار میگیرند. در غیر این صورت، یک توکن جلسه جدید ایجاد میشود.
- اگر کلاینت پارامتر action را با مقدار «invalider» ارسال کرده باشد، دادههای جلسه برای تبادل بعدی به عنوان «قابل حذف» علامتگذاری میشود. برخلاف یک تبادل عادی، این دادهها در پایان تبادل روی سرور ذخیره نخواهد شد.
- یک شمارنده مرتبط با جلسه از فرهنگ لغت $_SESSION، به همراه ID (session_id()) جلسه بازیابی میشود.
- صفحه ارسالشده به کلاینت توسط برنامه cycledevie-p1.php تولید میشود.
صفحه cycledevie-p1.php صفحهای را که به مشتری ارسال میشود نمایش میدهد:
<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>
توجه کنید که URL به انتهای هر یک از دو لینک اضافه شده است:
- cycledevie.php برای بارگذاری مجدد صفحه
- cycledevie.php?action=invalider برای باطل کردن جلسه. در این حالت، پارامتر action=invalider به URL افزوده میشود. این پارامتر توسط برنامهٔ سرور cycledevie.php از طریق دستور $action=$_GET["action"] بازیابی میشود.
بیایید صفحه را دو بار پشت سر هم رفرش کنیم:

شمارنده واقعاً افزایش یافته است. شناسه جلسه ID تغییر نکرده است. اکنون بیایید جلسه را باطل کنیم:

میتوانیم ببینیم که شناسه جلسه ID را از دست دادهایم، اما شمارنده یک بار دیگر افزایش یافته است. بیایید صفحه را دوباره بارگذاری کنیم:

میتوانیم ببینیم که همانند قبل، دوباره با همان شناسه جلسه، ID، شروع میکنیم. با این حال، شمارنده به صفر بازنشانی میشود. بنابراین، تابع session_destroy() تأثیر فوری ندارد. دادههای جلسه جاری فقط برای تبادل کلاینت-سرور که پس از تبادل مربوط به حذف انجام میشود، حذف میگردند. سشن ID تغییر نکرده است، که این امر نشان میدهد تابع session_destroy() با ایجاد یک ID جدید، سشن جدیدی را آغاز نمیکند. کوکی توکن جلسه توسط مرورگر کلاینت بازگردانده شد و PHP جلسه را از این توکن بازیابی کرد – جلسهای که دیگر هیچ دادهای در آن وجود نداشت.
آزمایشهای قبلی با مرورگر Netscape که برای استفاده از کوکیها پیکربندی شده بود، انجام شد. اکنون بیایید آن را طوری پیکربندی کنیم که از کوکیها استفاده نکند. این بدان معناست که نه کوکیهای ارسالشده توسط سرور را ذخیره میکند و نه آنها را بازمیگرداند. بنابراین، انتظار داریم که جلسات دیگر کار نکنند. بیایید این را با یک تبادل اولیه امتحان کنیم:

ما شناسه جلسه ID را داریم، همان شنایدهای که توسط session_start() تولید شده است. بیایید صفحه را با استفاده از لینک زیر دوباره بارگذاری کنیم:

شگفتآور است که نتایج بالا نشان میدهند ما یک جلسه فعال داریم و شمارنده به درستی مدیریت میشود. با توجه به اینکه کوکیها غیرفعال شدهاند و دیگر هیچ تبادل توکنی بین سرور و مرورگر وجود ندارد، این چگونه ممکن است؟ URL در اسکرینشات بالا پاسخ را ارائه میدهد:
این URL مربوط به لینک «بارگذاری مجدد صفحه» است. بیایید کد منبع صفحهای را که مرورگر نمایش میدهد بررسی کنیم:
<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، به شرح زیر است:
<a href="cycledevie.php?action=invalider">Invalider la session</a>
<a href="cycledevie.php">Recharger la page</a>
تفسیرگر PHP بنابراین بهطور خودکار لینکهای URL را با افزودن توکن جلسه بازنویسی کرده است. این کار تضمین میکند که هنگام کلیک روی لینکها، توکن بهدرستی توسط مرورگر ارسال شود. این موضوع توضیح میدهد که چرا حتی با غیرفعال بودن کوکیها، جلسه همچنان بهدرستی مدیریت میشود.
3.10.4. مثال ۳
ما پیشنهاد میکنیم یک برنامه PHP بنویسیم که بهعنوان کلاینت برای برنامه قبلی compteur عمل کند. این برنامه آن را N بار متوالی فراخوانی میکند، که در آن N بهعنوان یک پارامتر ارسال میشود. هدف ما نشان دادن یک کلاینت وب برنامهریزیشده و نحوه مدیریت توکن جلسه است. نقطه شروع ما یک کلاینت وب عمومی خواهد بود که به شکل زیر فراخوانی میشود:
clientweb URL GET/HEAD
- URL: URL درخواستی
- GET/HEAD: GET برای درخواست کد HTML از صفحه، HEAD برای محدود کردن نتایج تنها به سربرگها، HTTP
در اینجا مثالی با استفاده از URL http://localhost/poly/sessions/2/cycledevie.php آورده شده است. این برنامه همان برنامهٔ قبلاً توصیفشده است، با یک تفاوت جزئی:
//مسیر کوکی تنظیم شده است
session_set_cookie_params(0,"/poly/sessions/2");
//یک جلسه آغاز میشود
session_start();
تابع session_set_cookie_params به شما امکان میدهد تا پارامترهای خاصی را برای کوکی که توکن جلسه را در خود خواهد داشت، تنظیم کنید. اولین پارامتر، طول عمر کوکی است. طول عمر صفر به این معنی است که کوکی هنگام بستن مرورگری که آن را دریافت کرده است، حذف میشود. پارامتر دوم، مسیری است به URL که مرورگر باید کوکی را به آن بازگرداند. در مثال بالا، اگر مرورگر کوکی را از ماشین localhost دریافت کرده باشد، آن را به هر URL که در ساختار 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>
برنامه clientweb همه چیز را که از سرور دریافت میکند نمایش میدهد. در بالا دستور Set-cookie HTTP نشان داده شده است که سرور برای ارسال یک کوکی به کلاینت خود از آن استفاده میکند. در اینجا کوکی شامل دو بخش اطلاعات است:
- PHPSESSID که توکن جلسه است
- مسیر، که URL را که کوکی به آن تعلق دارد، تعریف میکند. path=/poly/sessions/2 به مرورگر دستور میدهد که هر بار یک URL را که با /poly/sessions/2 از دستگاهی که کوکی را برای آن ارسال کرده است شروع میشود، درخواست میکند، کوکی را به سرور بازگرداند.
- یک کوکی همچنین میتواند تاریخ انقضا را مشخص کند. در این مورد، این اطلاعات وجود ندارد. بنابراین کوکی هنگام بستن مرورگر حذف خواهد شد. برای مثال، یک کوکی ممکن است دوره انقضای N روزه داشته باشد. تا زمانی که معتبر است، مرورگر هر بار که به یکی از URL از دامنه آن (Path) دسترسی پیدا میکند، آن را بازمیفرستد. بیایید یک فروشگاه آنلاین در CD را در نظر بگیریم. این فروشگاه میتواند تاریخچه مرور مشتری را از طریق کاتالوگ خود ردیابی کرده و به تدریج ترجیحات او را شناسایی کند: برای مثال، موسیقی کلاسیک. این ترجیحات میتوانند در یک کوکی با عمر سه ماهه ذخیره شوند. اگر همان مشتری پس از یک ماه به سایت بازگردد، مرورگر کوکی را به سمت برنامه سرور ارسال میکند. برنامه سرور بر اساس اطلاعات موجود در کوکی، میتواند صفحات تولید شده را متناسب با ترجیحات مشتری تنظیم کند.
کد کلاینت وب در ادامه آمده است.
<?php
// پیکربندی
dl("php_curl.dll"); //کتابخانه CURL
// دستور زبان: $0 URL GET
// سه آرگومان لازم است
if($argc != 3){
// پیام خطا
fputs(STDERR,"Syntaxe : $argv[0] URL GET/HEAD");
//پایان
exit(1);
}//اگر
// آرگومان سوم باید GET یا HEAD باشد
$header=strtolower($argv[2]);
if($header!="get" && $header!="head"){
// پیام خطا
fputs(STDERR,"Syntaxe : $argv[0] URL GET/HEAD");
// ایست
exit(2);
}//اگر
// آرگومان اول یک URL است
$URL=strtolower($argv[1]);
// آمادهسازی اتصال
$connexion=curl_init($URL);
// پیکربندی اتصال
curl_setopt($connexion,CURLOPT_HEADER,1);
if($header=="head") curl_setopt($connexion,CURLOPT_NOBODY,1);
// اجرای اتصال
curl_exec($connexion);
// بستن اتصال
curl_close($connexion);
//پایان
exit(0);
?>
برنامه قبلی از کتابخانه CURL استفاده میکند:
یک شیء CURL را با هدف URL مقداردهی اولیه میکند | |
مقادیر مربوط به برخی گزینههای اتصال را تعیین میکند. در اینجا دو گزینهای که در برنامه استفاده شدهاند آورده شده است: CURLOPT_HEADER=1: امکان بازیابی سربرگهای HTTP ارسالشده توسط سرور را فراهم میکند CURLOPT_NOBODY=1: اجازه میدهد سند ارسالشده توسط سرور نادیده گرفته شود، به جز سربرگها HTTP | |
یک اتصال به $URL با گزینههای مشخصشده برقرار میکند. همه چیزهایی را که سرور ارسال میکند روی صفحه نمایش میدهد | |
ارتباط را قطع میکند |
برنامه قبلی نسبتاً ساده است. با این حال، کتابخانه CURL اجازه دستکاری دقیق پاسخ سرور، مانند تحلیل خط به خط آن را نمیدهد. برنامهٔ زیر همان کار برنامهٔ قبلی را انجام میدهد اما با استفاده از توابع شبکهٔ پایهای PHP. این برنامه بهعنوان نقطهٔ شروع برای نوشتن یک کلاینت برای برنامهٔ cycledevie.php ما عمل خواهد کرد.
<?php
//نحو: $0 URL GET/HEAD
// سه آرگومان لازم است
if($argc != 3){
// پیام خطا
fputs(STDERR,"Syntaxe : $argv[0] URL GET/HEAD");
//خاتمه
exit(1);
}//اگر
//اتصال و نمایش نتیجه
$résultats=getURL($argv[1],$argv[2]);
if(isset($résultats->erreur)){
// خطا
echo "L'erreur suivante s'est produite : $résultats->erreur\n";
}else{
// نمایش پاسخ سرور
echo $résultats->réponse;
}//اگر
// پایان
exit(0);
//-----------------------------------------------------------------------
function getURL($URL,$header){
//به $URL متصل میشود
//بسته به مقدار هدر، یک GET یا یک HEAD تولید میکند
//پاسخ سرور نتیجه تابع را تشکیل میدهد
// URL را تحلیل میکند
$url=parse_url($URL);
//پروتکل
if(strtolower($url["scheme"])!="http"){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//اگر
// ماشین
$hote=$url["host"];
if(! isset($hote)){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//اگر
// پورت
$port=$url["port"];
if(! isset($port)) $port=80;
// مسیر
$chemin=$url["path"];
//درخواست
if(isset($url["query"])){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//اگر
// تحلیل $header
$header=strtoupper($header);
if($header!="GET" && $header!="HEAD"){
// پیام خطا
$résultats->erreur="méthode [$header] doit être GET ou HEAD";
// پایان
return $résultats;
}//اگر
// باز کردن یک اتصال روی پورت $port از $hote
$connexion=fsockopen($hote,$port,&$errno,&$erreur);
// در صورت خطا بازگشت
if(! $connexion){
$résultats->erreur="Echec de la connexion au site ($hote,$port) : $erreur";
return $résultats;
}//if
// $connexion نشاندهنده یک جریان ارتباطی دوطرفه است
//بین کلاینت (این برنامه) و وبسروری که در حال تماس با آن است
//این کانال برای تبادل دستورات و اطلاعات استفاده میشود
//پروتکل ارتباطی HTTP است
// کلاینت فرمان GET را برای درخواست URL / ارسال میکند
// دستور GET: URL HTTP/1.0
// سربرگهای پروتکل HTTP باید با یک خط خالی پایان یابند
fputs($connexion, "$header $chemin HTTP/1.0\n\n");
// سرور اکنون در کانال $connexion پاسخ خواهد داد. آن همه را ارسال خواهد کرد
// این دادهها را میخواند و سپس کانال را میبندد. بنابراین کلاینت همه چیز را که از $connexion میرسد میخواند
//تا زمانی که کانال بسته شود
$résultats->réponse="";
while($ligne=fgets($connexion,10000))
$résultats->réponse.=$ligne;
// سپس کلاینت به نوبه خود اتصال را قطع میکند
fclose($connexion);
// برگشت
return $résultats;
}//getURL
?>
بیایید چند نکته را در این برنامه بررسی کنیم:
- برنامه دو پارامتر را میپذیرد:
- یک فایل HTTP با نام URL که مایلید محتویات آن را روی صفحه نمایش دهید.
- یک متد، یا GET یا HEAD، که بسته به اینکه فقط سربرگها را میخواهید (HTTP (HEAD) یا همچنین بدنه سند مرتبط با URL (GET).
- هر دو پارامتر به تابع getURL ارسال میشوند. این تابع یک شیء $résultats را بازمیگرداند. این شیء در صورت وجود خطا دارای فیلد erreur و در غیر این صورت دارای فیلد réponse است. میدان erreur برای ذخیره هرگونه پیام خطا استفاده میشود. میدان réponse حاوی پاسخ از وبسرور تماسگرفته شده است.
- تابع getURL با استفاده از تابع parse_url، توابع URL و $URL را تحلیل میکند. دستور $url=parse_url($URL) آرایهٔ asociative $url را با کلیدهای ممکن زیر ایجاد میکند:
- scheme: پروتکل URL (http، ftp و غیره)
- میزبان: میزبان URL
- پورت: پورت URL
- مسیر: مسیر برای URL
- querystring: پارامترها برای URL
یک URL در صورتی معتبر است که به شکل http://machine[:port][/chemin] باشد.
- پارامتر $header نیز بررسی میشود
- پس از بررسی و تأیید صحت پارامترها، یک اتصال TCP بر روی دستگاه برقرار میشود ($hote, $port)، سپس بسته به پارامتر $header، فرمان HTTP، GET یا HEAD ارسال میشود.
- سپس پاسخ سرور خوانده شده و در $résultats->response ذخیره میشود.
اجرای برنامه نتایج زیر را تولید میکند:
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>
خوانندهٔ دقیق متوجه شده است که پاسخ سرور بسته به نرمافزار کلاینت مورد استفاده متفاوت است. در مورد اول، سرور هدر HTTP: Transfer-Encoding: chunked را ارسال کرد؛ این هدر در مورد دوم ارسال نشد. این به این دلیل است که کلاینت دوم هدر HTTP را ارسال کرد: get URL HTTP/1.0، که یک URL را درخواست میکند و نشان میدهد که از نسخه 1.0 پروتکل HTTP استفاده میکند، و بدین ترتیب سرور را ملزم میسازد که با استفاده از همان پروتکل پاسخ دهد. با این حال، هدر Transfer-Encoding: chunked در HTTP به پروتکل HTTP نسخه 1.1 تعلق دارد. در نتیجه، سرور از آن در پاسخ خود استفاده نکرد. این نشان میدهد که کلاینت اول در درخواست خود اعلام کرده بود که از پروتکل HTTP نسخه ۱.۱ استفاده میکند.
اکنون برنامه clientCompteur را ایجاد میکنیم که به شرح زیر فراخوانی میشود:
clientCompteur URL N [JSESSIONID]
- URL: URL برنامه cycledevie
- N: تعداد تماسهایی که باید با این برنامه برقرار شود
- PHPSESSID: پارامتر اختیاری – توکن جلسه
هدف برنامه این است که برنامه cycledevie.php را N بار فراخوانی کند، کوکی جلسه را مدیریت کرده و هر بار مقدار شمارنده بازگشتی از سرور را نمایش دهد. در پایان N فراخوانی، مقدار شمارنده باید N-1 باشد. در اینجا یک مثال اولیه از اجرا آورده شده است:
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]
برنامه نمایش میدهد:
- سربرگهای HTTP را که به سرور در قالب --> entêteEnvoyé ارسال میکند
- سربرگهای HTTP، که در قالب <-- entêteReçu دریافت میکند
- مقدار شمارنده پس از هر فراخوانی
میتوانیم ببینیم که در طول اولین تماس:
- کلاینت کوکی ارسال نمیکند
- سرور یکی را ارسال میکند (Set-Cookie:)
برای درخواستهای بعدی:
- کلاینت بهطور سیستماتیک کوکیای را که در اولین درخواست از سرور دریافت کرده، بازمیفرستد. این امر به سرور امکان میدهد آن را شناسایی کرده و شمارندهاش را افزایش دهد.
- سرور، از طرف خود، دیگر کوکی ارسال نمیکند
ما برنامه قبلی را دوباره اجرا میکنیم و توکن بالا را به عنوان سومین پارامتر ارسال میکنیم:
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]
در اینجا میبینیم که از اولین درخواست کلاینت، سرور یک کوکی جلسه معتبر دریافت میکند. این ممکن است نشاندهنده یک آسیبپذیری امنیتی بالقوه باشد. اگر بتوانم یک توکن جلسه را در شبکه رهگیری کنم، میتوانم هویت کاربری را که جلسه را آغاز کرده است، جعل کنم. در مثال ما، درخواست اول (بدون توکن جلسه) نمایانگر کاربری است که جلسه را آغاز کرده است (شاید با نام کاربری و رمز عبوری که به او حق دریافت توکن را میدهد)، و درخواست دوم (با توکن جلسه) نمایانگر کاربری است که توکن جلسه را از درخواست اول «هک» کرده است. اگر تراکنش مورد نظر یک تراکنش بانکی باشد، این میتواند به یک مشکل جدی تبدیل شود...
کد کلاینت به شرح زیر است:
<?php
//نحو: $0 URL N [PHPSESSID]
//سه آرگومان لازم است
if($argc!=3 && $argc!=4){
// پیام خطا
fputs(STDERR,"Syntaxe : $argv[0] URL N [PHPSESSID]");
// ایست
exit(1);
}//اگر
//در حال بازیابی تنظیمات
$URL=$argv[1];
$N=$argv[2];
$PHPSESSID=$argv[3];
// ورود و نمایش نتیجه
$résultats=getURL($URL,$N,$PHPSESSID);
if(isset($résultats->erreur)){
//خطا
echo "L'erreur suivante s'est produite : $résultats->erreur\n";
}
//پایان
exit(0);
//-----------------------------------------------------------------------
function getURL($URL,$N,$PHPSESSID){
//به URL متصل میشود
//بسته به مقدار هدر، یک GET یا یک HEAD تولید میکند
//پاسخ سرور نتیجه تابع را تشکیل میدهد
// تحلیل میکند URL
$url=parse_url($URL);
//پروتکل
if(strtolower($url["scheme"])!="http"){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//اگر
// ماشین
$hote=$url["host"];
if(! isset($hote)){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//اگر
// پورت
$port=$url["port"];
if(! isset($port)) $port=80;
// مسیر
$chemin=$url["path"];
//درخواست
if(isset($url["query"])){
$résultats->erreur="l'URL [$URL] n'est pas au format http://machine[:port][/chemin]";
return $résultats;
}//اگر
//بررسی $N
if (! preg_match("/^\d+$/",$N)){
// خطا
$résultats->erreur="nombre [$N] erroné";
// پایان
return $résultats;
}//اگر
// فراخوانیها از $URL به $N انجام میشوند
for($i=0;$i<$N;$i++){
// باز کردن یک اتصال روی پورت $port از $hote
$connexion=fsockopen($hote,$port,&$errno,&$erreur);
//در صورت رخ دادن خطا بازمیگردد
if(! $connexion){
$résultats->erreur="Echec de la connexion au site ($hote,$port) : $erreur";
return $résultats;
}//اگر
// $connexion نشاندهنده یک جریان ارتباطی دوطرفه است
//بین مشتری (این برنامه) و وبسروری که در تماس است
//این کانال برای تبادل دستورات و اطلاعات استفاده میشود
//پروتکل ارتباطی HTTP است
// کلاینت سربرگها را ارسال میکند HTTP
// get URL HTTP/1.1
envoie($connexion, "GET $chemin HTTP/1.1\n");
// host: host:port
envoie($connexion, "Host: $hote:$port\n");
// ارتباط: بسته
envoie($connexion, "Connection: close\n");
// کوکی: $PHPSESSID
if($PHPSESSID) envoie($connexion, "Cookie: PHPSESSID=$PHPSESSID\n");
// خط خالی
envoie($connexion,"\n");
// سرور اکنون در کانال $connexion پاسخ خواهد داد. آن همه را ارسال خواهد کرد
// دادههای خود را ارسال میکند و سپس کانال را میبندد.
// کلاینت با خواندن سربرگهای HTTP که با یک خط خالی پایان مییابند، شروع میکند
$CHUNKED=0;
while(($ligne=fgets($connexion,10000)) && (($ligne=rtrim($ligne))!="")){
//خط اکو
echo "<-- $ligne\n";
// در صورت یافت نشدن توکن، به دنبال آن بگردد
if(! $PHPSESSID){
// جستجو برای خط 'set-cookie'
if(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i",$ligne,$champs)){
//توکن پیدا شده است – آن را ذخیره کنید
$PHPSESSID=$champs[1];
}//اگر
}//اگر
// جستوجوی حالت انتقال سند
if(! $CHUNKED){
//در جستجوی خط «Transfer-Encoding: chunked»
if(preg_match("/^Transfer-Encoding: chunked/i",$ligne,$champs)){
//انتقال تکهای
$CHUNKED=1;
}//if
}//اگر
}//خط بعدی
// خط اکو
echo "<-- $ligne\n";
// نحوهٔ خواندن سند بستگی به نحوهٔ ارسال آن دارد
if($CHUNKED) $document=getChunkedDoc($connexion);
else $document=getDoc($connexion);
// جستوجوی شمارنده در سند
if(preg_match("/<br>compteur : (\d+)/i",$document,$champs)){
//اگر شمارنده پیدا شد – آن را نمایش دهید
echo "\n[Le compteur est égal à $champs[1]]\n\n";
}//اگر
// کلاینت اتصال را قطع میکند
fclose($connexion);
}//برای i
}//getURL
//--------------------------
function getDoc($connexion){
// خواندن سند در $connexion
$doc="";
while($ligne=fread($connexion,10000))
$doc.=$ligne;
//پایان
return $doc;
}//getDoc
//--------------------------
function getChunkedDoc($connexion){
// خواندن سند در $connexion
// این سند به صورت تکهها در قالب ارسال میشود
// تعداد کاراکترها در قطعه به صورت هگزادسیمال
// بقیهٔ قطعه
// خط خالی
//اندازه بلوک از خط اول خوانده میشود
$taille=hexdec(rtrim(fgets($connexion,10000)));
//سند زیر خوانده میشود
$doc="";
while($taille!=0){
// یک بخش از کاراکترهای $taille را بخوانید
$doc.=fread($connexion,$taille);
// خط خالی
fgets($connexion,10000);
// بخش بعدی
// اندازهٔ بخش را بخوانید
$taille=hexdec(rtrim(fgets($connexion,10000)));
}//در حالی که
// پایان
return $doc;
}//getChunkedDoc
//--------------------------
function envoie($flux,$msg){
//ارسال میکند $msg به $flux
fwrite($flux,$msg);
// اکوی صفحه
echo "--> $msg";
}//ارسال میکند
?>
بیایید نکات کلیدی این برنامه را بررسی کنیم:
- ما باید N تبادل کلاینت-سرور را انجام دهیم. به همین دلیل اینها در یک حلقه قرار دارند.
- در هر تبادل، کلاینت یک اتصال TCP-IP با سرور برقرار میکند. پس از برقراری اتصال، سرآیندهای HTTP درخواست خود را به سرور ارسال میکند:
<?php
...
// کلاینت سربرگها را ارسال میکند HTTP
// GET URL HTTP/1.1
envoie($connexion, "GET $chemin HTTP/1.1\n");
// host: host:port
envoie($connexion, "Host: $hote:$port\n");
// ارتباط: بسته
envoie($connexion, "Connection: close\n");
// کوکی: $PHPSESSID
if($PHPSESSID) envoie($connexion, "Cookie: PHPSESSID=$PHPSESSID\n");
// خط خالی
envoie($connexion,"\n");
اگر توکن PHPSESSID موجود باشد، به صورت کوکی ارسال میشود؛ در غیر این صورت ارسال نمیشود. توجه داشته باشید که کلاینت اعلام کرده است که از پروتکل HTTP/1.1 استفاده میکند. این موضوع توضیح میدهد که چرا در ادامه، سرور هدر HTTP: Transfer-Encoding: chunked را برای کلاینت ارسال میکند که به پروتکل HTTP/1.1 تعلق دارد اما به پروتکل HTTP/1.0 تعلق ندارد.
- پس از ارسال درخواست، کلاینت منتظر پاسخ سرور میماند. آن با تحلیل هدرهای HTTP در این پاسخ شروع میکند. در آن به دنبال دو خط میگردد:
خط «Cookie:» هدر HTTP است که توکن جلسه PHPSESSID را در خود جای داده است. کلاینت باید این را بازیابی کرده و در تبادل بعدی آن را به سرور بازگرداند. خط «Transfer-Encoding: chunked»، در صورت وجود، نشان میدهد که سرور سند را بهصورت تکهها (chunks) ارسال خواهد کرد. هر تکه سپس با فرمت زیر به کلاینت ارسال میشود:
اگر خط «Transfer-Encoding: chunked» موجود نباشد، سند در یک بسته واحد پس از خط خالی در سربرگ «HTTP» ارسال میشود. بنابراین، بسته به وجود یا عدم وجود این خط، نحوه دریافت سند متفاوت خواهد بود. کد پردازش سربرگهای «HTTP» به شرح زیر است:
<?php
...
// کلاینت با خواندن سرآیندهای HTTP که با یک خط خالی پایان مییابند، شروع میکند
$CHUNKED=0;
while(($ligne=fgets($connexion,10000)) && (($ligne=rtrim($ligne))!="")){
//خط اکو
echo "<-- $ligne\n";
// در صورت یافت نشدن توکن، به دنبال آن بگردید
if(! $PHPSESSID){
// جستجو برای خط 'set-cookie'
if(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i",$ligne,$champs)){
//توکن پیدا شده است – آن را ذخیره کنید
$PHPSESSID=$champs[1];
}//اگر
}//اگر
// جستجو برای حالت انتقال سند
if(! $CHUNKED){
//در حال جستجو برای خط «Transfer-Encoding: chunked»
if(preg_match("/^Transfer-Encoding: chunked/i",$ligne,$champs)){
//انتقال تکهای
$CHUNKED=1;
}//if
}//اگر
}//خط بعدی
- پس از اینکه توکن برای اولین بار یافت شد، در فراخوانیهای بعدی به سرور دیگر جستجو نخواهد شد. پس از پردازش سربرگهای HTTP در پاسخ، سیستم به سند بعدی پس از سربرگهای HTTP منتقل میشود. این سند بسته به حالت انتقال آن به شیوهای متفاوت خوانده میشود:
<?php
...
// نحوهٔ خواندن سند بستگی به نحوهٔ ارسال آن دارد
if($CHUNKED) $document=getChunkedDoc($connexion);
else $document=getDoc($connexion);
- در سند دریافتی $document، ما به دنبال خطی هستیم که حاوی مقدار شمارنده باشد. این جستجو نیز با استفاده از یک عبارت منظم انجام میشود:
<?php
...
// جستجو برای شمارنده در سند
if(preg_match("/<br>compteur : (\d+)/i",$document,$champs)){
// اگر شمارنده پیدا شد – آن را نمایش دهید
echo "\n[Le compteur est égal à $champs[1]]\n\n";
}//اگر
- اگر سرور سند را یکجا ارسال کند، دریافت آن ساده است:
<?php
...
//--------------------------
function getDoc($connexion){
// خواندن سند در $connexion
$doc="";
while($ligne=fgets($connexion,10000))
$doc.=$ligne;
// پایان
return $doc;
}//getDoc
- اگر سرور سند را در چند بخش ارسال کند، خواندن آن پیچیدهتر است:
<?php
...
function getChunkedDoc($connexion){
// خواندن سند در $connexion
// این سند به صورت تکهها در قالب ارسال میشود
// تعداد کاراکترها در بلوک به صورت هگزادسیمال
// بقیهٔ قطعه
//اندازهٔ بخش از خط اول خوانده میشود
$taille=hexdec(rtrim(fgets($connexion,10000)));
// سند زیر خوانده میشود
$doc="";
while($taille!=0){
// بخش $taille کاراکترها را بخوانید
$doc.=fread($connexion,$taille);
// خط خالی
fgets($connexion,10000);
// بخش بعدی
// اندازهٔ بخش را بخوانید
$taille=hexdec(rtrim(fgets($connexion,10000)));
}//در حالی که
//پایان
return $doc;
}//getChunkedDoc
به یاد داشته باشید که یک قطعه سند به صورت ارسال میشود
بنابراین با خواندن اندازه سند شروع میکنیم. پس از مشخص شدن این مقدار، تابع fread را فراخوانی میکنیم تا $taille کاراکتر از جریان $connexion بخواند و سپس خط خالی بعدی را. ما این کار را تا زمانی که سرور اعلام کند قصد ارسال سندی با اندازه 0 را دارد، تکرار میکنیم.
3.10.5. مثال ۴
در مثال قبلی، کلاینت وب توکن را به شکل یک کوکی بازمیگرداند. ما دیدیم که آن همچنین میتواند توکن را در داخل خود URL درخواستشده به شکل URL;PHPSESSID=xxx بازگرداند. بیایید این را بررسی کنیم. برنامه clientCompteur.php به clientCompteur2.php تبدیل شده و به شرح زیر اصلاح میشود:
<?php
...
....
// کلاینت سربرگها را ارسال میکند 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");
// ارتباط: بسته
envoie($connexion, "Connection: close\n");
// خط خالی
envoie($connexion,"\n");
....
بنابراین کلاینت از طریق GET URL، URL را از کنتور درخواست میکند؛PHPSESSID=xx HTTP/1.1 و دیگر کوکی ارسال نمیکند. این تنها تغییر است. در اینجا نتایج یک تماس اولیه آمده است:
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]
درخواست اول، کلاینت بدون توکن جلسه URL را درخواست میکند. سرور با ارسال توکن پاسخ میدهد. سپس کلاینت درخواست دیگری برای همان URL ارسال میکند و توکن دریافتی را به آن اضافه میکند. میتوانیم ببینیم که شمارنده واقعاً افزایش یافته است، که ثابت میکند سرور به درستی تشخیص داده این همان جلسه است.
3.10.6. مثال ۵
این مثال یک برنامه کاربردی را نشان میدهد که شامل سه صفحه است که ما آنها را page1، page2 و page3 مینامیم. کاربر باید به این ترتیب به آنها دسترسی پیدا کند:
- page1 یک فرم است که یک مقدار اطلاعاتی را درخواست میکند: یک نام
- صفحه ۲ فرمى است که در پاسخ به ارسال فرم صفحه ۱ نمایش داده مىشود. این فرم یک اطلاعات دوم را درخواست مىکند: یک سن
- صفحه ۳ یک سند به نام HTML است که نام دریافتشده از صفحه ۱ و سن دریافتشده از صفحه ۲ را نمایش میدهد.
سه تبادل کلاینت-سرور وجود دارد:
- در اولین تبادل، فرم «page1» توسط کلاینت درخواست شده و توسط سرور ارسال میشود
- در تبادل دوم، کلاینت فرم صفحه 1 (نام) را به سرور ارسال میکند. در مقابل، فرم صفحه 2 را دریافت میکند، یا در صورت نادرست بودن، دوباره فرم صفحه 1 را دریافت میکند. سند صفحه 3 نام و سن را نمایش میدهد.
- در سومین تبادل، کلاینت فرم صفحه ۲ (سن) را برای سرور ارسال میکند. در مقابل، فرم صفحه ۳ را دریافت میکند، یا در صورت نادرست بودن، دوباره فرم صفحه ۲ را دریافت میکند. سند صفحه ۳ نام و سن را نمایش میدهد. نام در طول تبادل دوم توسط سرور دریافت شده و از آن زمان «فراموش» شده است. از یک جلسه (session) برای ذخیره نام در طول تبادل ۲ استفاده میشود تا در طول تبادل ۳ در دسترس باشد.
صفحهٔ page1 که در تبادل اول دریافت شده است به شرح زیر است:

ما فیلد نام را پر میکنیم:

ما دکمه [Suite] را کلیک میکنیم و سپس صفحه2 زیر به ما نمایش داده میشود:

میدان سن را پر کنید:

روی دکمه [Suite] کلیک کنید تا صفحه ۳ زیر نمایش داده شود:

وقتی صفحهٔ 1 به سرور ارسال میشود، اگر فیلد نام خالی باشد، سرور ممکن است کد خطا بازگرداند:

وقتی صفحه page2 به سرور ارسال میشود، اگر age نامعتبر باشد، سرور ممکن است آن را با یک کد خطا بازگرداند:

برنامه از شش برنامه تشکیل شده است:
فراخوانی page1.php | |
صفحهٔ 1 را نمایش میدهد. فرم روی صفحهٔ 1 توسط etape2.php پردازش میشود. | |
مقادیر فرم صفحه 1 را پردازش میکند. اگر خطایی وجود داشته باشد، صفحه 1 توسط page1.php مجدداً نمایش داده میشود؛ در غیر این صورت، صفحه 2 توسط page2.php نمایش داده میشود. | |
صفحهٔ ۲ را نمایش میدهد. فرم صفحهٔ ۲ توسط etape3.php پردازش میشود. | |
مقادیر فرم صفحه 2 را پردازش میکند. اگر خطایی وجود داشته باشد، صفحه 2 توسط page2.php مجدداً نمایش داده میشود؛ در غیر این صورت، صفحه 3 توسط page3.php نمایش داده میشود. | |
صفحه ۳ را نمایش میدهد. |
مرحلهٔ ۱ برنامه توسط برنامهٔ زیر، etape1.php، انجام میشود:
<?php
// etape1.php
//پیکربندی
ini_set("register_globals","off");
ini_set("display_errors","off");
// شروع جلسه
session_start();
$_SESSION["session"]=""; //بازنشانی متغیر جلسه
// آمادهسازی صفحهٔ ۱
$requête->nom="";
$requête->erreurs=array();
// صفحه نمایش ۱
include "page1.php";
//پایان
exit(0);
?>
لطفاً به نکات زیر توجه کنید:
- این برنامه به ردیابی جلسه نیاز دارد. در نتیجه، هر مرحله از جلسه یک جلسه جدید را آغاز میکند.
- اطلاعات جلسه که باید حفظ شود در ابجکت $session ذخیره خواهد شد.
- اطلاعات مورد نیاز برای نمایش صفحات مختلف (سه) برنامه در ابجکت با نام $requête قرار داده خواهد شد.
برنامه page1.php اطلاعات موجود در $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>
<? //خطاها؟
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] ?>
<? }//برای ?>
</ul>
<? }//اگر ?>
</body>
</html>
- این صفحه یک شیء $requête را دریافت میکند که شامل دو فیلد nom و erreurs است. این صفحه مقادیر این دو فیلد را نمایش میدهد.
- همچنین یک فرم نمایش داده میشود. مقادیر این فرم (name) از طریق متد POST به برنامه etape2.php ارسال میشوند:
برنامه etape2.php مسئول پردازش مقادیر فرم در صفحه 1 و در صورت وجود خطا (نام نادرست)، نمایش مجدد صفحه 1 است؛ در غیر این صورت، صفحه 2 را برای دریافت سن نمایش میدهد.
<?php
// etape2.php
// پیکربندی
ini_set("register_globals","off");
ini_set("display_errors","off");
//شروع جلسه
session_start();
// به طور معمول، باید یک پارامتر 'name' وجود داشته باشد
//در درخواست ذخیره شده است
$requête->nom=$_POST["nom"];
//اگر هیچ پارامتری وجود نداشته باشد، صفحهٔ 1 بدون خطا ارسال میشود
if (! isset($requête->nom)){
$requête->nom="";
$requête->erreurs=array();
include "page1.php";
exit(0);
}//اگر
// اگر پارامتر 'name' موجود باشد – اعتبار آن را بررسی میکنیم
$page=calculerPage($requête);
//آیا خطایی وجود داشت؟
if(count($page->erreurs)!=0){
//صفحهٔ ۱ با خطاها
$requête->erreurs=$page->erreurs;
include "page1.php";
exit(0);
}//اگر
// بدون خطا – نام را در جلسه ذخیره کنید
unset($session);
$session->nom=$requête->nom;
$_SESSION["session"]=$session;
// نمایش صفحه ۲
$requête->age="";
$requête->erreurs=array();
include "page2.php";
//پایان
exit(0);
// ---------calculerPage
function calculerPage($requête){
//اعتبار درخواست را بررسی میکند $requête
// یک آرایه از خطاها را در $page->errors بازمیگرداند
// در ابتدا، هیچ خطایی وجود ندارد
$page->erreurs=array();
//نام نباید خالی باشد
if (preg_match("/^\s*$/",$requête->nom)){
$page->erreurs[]="Vous n'avez pas indiqué de nom";
}
//بازگشت به صفحه
return $page;
}//calculerPage
?>
- etape2 ابتدا بررسی میکند که پارامتر مورد انتظار nom را دارد. اگر اینطور نباشد، یک صفحه خالی را دوباره نمایش میدهد. این وضعیت ممکن است زمانی رخ دهد که etape2 مستقیماً توسط کلاینتی فراخوانی شود که هیچ پارامتری را به آن ارسال نمیکند.
- اگر پارامتر nom موجود باشد، اعتبار آن بررسی میشود. این کار از طریق رویهای به نام calculerPage انجام میشود که وظیفهاش تولید یک شیء $page با یک فیلد erreurs است که آرایهای از خطاها میباشد. فقط یک خطا ممکن است، اما ما میخواستیم نشان دهیم که میتوانیم یک لیست خطا را مدیریت کنیم.
- اگر خطاهایی وجود داشته باشد، صفحه page1 به همراه فهرست خطاها مجدداً نمایش داده میشود.
- اگر خطایی وجود نداشته باشد، نام در شیء $session که دادههای مربوط به جلسهٔ جاری را ذخیره میکند، نگهداری میشود. سپس صفحهٔ page2 نمایش داده میشود.
برنامه page2.php صفحه 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>
<? // خطاها؟
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];
}//برای
?>
</ul>
</font>
<? } ?>
</body>
</html>
اصل پشت این صفحه بسیار شبیه به اصل page2.php است. محتویات یک شیء $requête را که شامل فیلدهای nom، age و erreurs است، نمایش میدهد. این یک فرم را نمایش میدهد که مقادیر آن توسط etape3.php پردازش خواهد شد.
بنابراین برنامه etape3.php مقادیر فرم صفحه ۲ را پردازش میکند که در اینجا به سن محدود شدهاند:
<?php
// etape3.php
// پیکربندی
ini_set("register_globals","off");
ini_set("display_errors","off");
// شروع جلسه
session_start();
// بازیابی پارامترهای نام و سن
$requête->age=$_POST["age"];
$session=$_SESSION["session"];
$requête->nom=$session->nom;
// بهطور معمول، باید یک نام و یک سن داشته باشیم
if (! isset($requête->age) || ! isset($requête->nom)){
// اگر تماس نادرست باشد، صفحهٔ 1 را ارسال کن
$_SESSION["session"]=""; //بهعنوان یک اقدام احتیاطی
$requête->nom="";
$requête->erreurs=array();
include "page1.php";
exit(0);
}//اگر
//پارامتر 'age' موجود است – اعتبار آن را بررسی کنید
$page=calculerPage($requête);
//آیا خطایی وجود داشت؟
if(count($page->erreurs)!=0){
// صفحهٔ ۲ با خطاها
$requête->erreurs=$page->erreurs;
include "page2.php";
exit(0);
}//اگر
//خطایی وجود ندارد – سن در جلسه ذخیره شده است
$session->age=$requête->age;
$_SESSION["session"]=$session;
// صفحه ۳ را نمایش دهد
include "page3.php";
// پایان
exit(0);
// ---------calculerPage
function calculerPage($requête){
//اعتبار درخواست را بررسی میکند $requête
// یک آرایه از خطاها را در $page->errors بازمیگرداند
// در ابتدا، هیچ خطایی وجود ندارد
$page->erreurs=array();
//سن باید در قالب معتبر باشد
if (! preg_match("/^\s*\d{1,3}\s*$/",$requête->age)){
$page->erreurs[]="âge incorrect";
}
//بازگشت به صفحه
return $page;
}//calculerPage
?>
- برنامه با بازیابی نام از جلسه (از صفحهٔ 1) و سن از فرم در صفحهٔ 2 آغاز میشود. اگر هر یک از این اطلاعات ناقص باشد، صفحهٔ 1 نمایش داده میشود.
- سپس سن از نظر اعتبار بررسی میشود. اگر سن نادرست باشد، صفحه ۲ همراه با فهرستی از خطاها دوباره نمایش داده میشود. اگر سن صحیح باشد، صفحه ۳ نمایش داده میشود. این صفحه صرفاً دو مقدار (نام، سن) را که از دو فرم (صفحه ۱، صفحه ۲) به دست آمدهاند، نمایش میدهد.

