3. وحدة تحكم عامة
3.1. مقدمة
في الطريقة السابقة، كان من المفهوم أننا كان علينا كتابة وحدة التحكم المسماة main.php. مع القليل من الخبرة، ندرك أن وحدة التحكم هذه غالبًا ما تقوم بنفس المهام، ولذلك من المغري كتابة وحدة تحكم عامة يمكن استخدامها في معظم تطبيقات الويب. قد يكون كود وحدة التحكم هذه كما يلي:
<?php
// generic controller
// configurable reading
include 'config.php';
// including libraries
for($i=0;$i<count($dConfig['includes']);$i++){
include($dConfig['includes'][$i]);
}//for
// start or resume session
session_start();
$dSession=$_SESSION["session"];
if($dSession) $dSession=unserialize($dSession);
// retrieve the action to be taken
$sAction=$_GET['action'] ? strtolower($_GET['action']) : 'init';
$sAction=strtolower($_SERVER['REQUEST_METHOD']).":$sAction";
// is the sequence of actions normal?
if( ! enchainementOK($dConfig,$dSession,$sAction)){
// abnormal sequence
$sAction='enchainementinvalide';
}//if
// share processing
$scriptAction=$dConfig['actions'][$sAction] ?
$dConfig['actions'][$sAction]['url'] :
$dConfig['actions']['actionInvalide']['url'];
include $scriptAction;
// send response(view) to customer
$sEtat=$dSession['etat']['principal'];
$scriptVue=$dConfig['etats'][$sEtat]['vue'];
include $scriptVue;
// end of script - we shouldn't get there unless there's a bug
trace ("Erreur de configuration.");
trace("Action=[$sAction]");
trace("scriptAction=[$scriptAction]");
trace("Etat=[$sEtat]");
trace("scriptVue=[$scriptVue]");
trace ("Vérifiez que les script existent et que le script [$scriptVue] se termine par l'appel à finSession.");
exit(0);
// ---------------------------------------------------------------
function finSession(&$dConfig,&$dReponse,&$dSession){
// $dConfig: configuration dictionary
// $dSession: dictionary containing session info
// $dReponse: the dictionary of arguments for the response page
// session registration
if(isset($dSession)){
// put the query parameters in the session
$dSession['requete']=strtolower($_SERVER['REQUEST_METHOD'])=='get' ? $_GET :
strtolower($_SERVER['REQUEST_METHOD'])=='post' ? $_POST : array();
$_SESSION['session']=serialize($dSession);
session_write_close();
}else{
// no session
session_destroy();
}
// we present the answer
include $dConfig['vuesReponse'][$dReponse['vuereponse']]['url'];
// end of script
exit(0);
}//endsession
//--------------------------------------------------------------------
function enchainementOK(&$dConfig,&$dSession,$sAction){
// checks whether the current action is authorized with respect to the previous state
$etat=$dSession['etat']['principal'];
if(! isset($etat)) $etat='sansetat';
// check action
$actionsautorisees=$dConfig['etats'][$etat]['actionsautorisees'];
$autorise= ! isset($actionsautorisees) || in_array($sAction,$actionsautorisees);
return $autorise;
}
//--------------------------------------------------------------------
function dump($dInfos){
// displays an information dictionary
while(list($clé,$valeur)=each($dInfos)){
echo "[$clé,$valeur]<br>\n";
}//while
}//follow-up
//--------------------------------------------------------------------
function trace($msg){
echo $msg."<br>\n";
}//follow-up
?>
3.2. ملف تكوين التطبيق
يتم تكوين التطبيق في نص برمجي يجب أن يُسمى config.php. يتم تخزين إعدادات التطبيق في قاموس يُسمى $dConfig، والذي يستخدمه وحدة التحكم ونصوص الإجراءات والنماذج وطرق العرض الأساسية.
3.3. المكتبات المراد تضمينها في وحدة التحكم
توضع المكتبات المراد تضمينها في كود وحدة التحكم في المصفوفة $dConfig['includes']. تقوم وحدة التحكم بتضمينها باستخدام المقتطف البرمجي التالي:
<?php
...
// configurable reading
include "config.php";
// including libraries
for($i=0;$i<count($dConfig['includes']);$i++){
include($dConfig['includes'][$i]);
}//for
3.4. إدارة الجلسة
تقوم وحدة التحكم العامة بإدارة الجلسة تلقائيًا. فهي تحفظ محتوى الجلسة وتسترده عبر قاموس $dSession. قد يحتوي هذا القاموس على كائنات يجب تسلسلها حتى يمكن استردادها بشكل صحيح لاحقًا. المفتاح المرتبط بهذا القاموس هو 'session'. لذلك، يتم استرداد الجلسة باستخدام الكود التالي:
<?php
…
// start or resume session
session_start();
$dSession=$_SESSION["session"];
if($dSession) $dSession=unserialize($dSession);
إذا احتاجت إحدى الإجراءات إلى تخزين معلومات في الجلسة، فستضيف مفاتيح وقيم إلى قاموس $dSession. ونظرًا لأن جميع الإجراءات تشترك في نفس الجلسة، فهناك خطر حدوث تعارضات في مفاتيح الجلسة إذا تم تطوير التطبيق بشكل مستقل من قبل عدة أشخاص. وهذا يمثل تحديًا. نحتاج إلى تطوير مستودع يسرد مفاتيح الجلسة، وهو مستودع مشترك بين الجميع. وسنرى أن كل إجراء ينتهي باستدعاء الدالة finSession التالية:
<?php
...
// ---------------------------------------------------------------
function finSession(&$dConfig,&$dReponse,&$dSession){
// $dConfig: configuration dictionary
// $dSession: dictionary containing session information
// $dReponse: the dictionary of arguments for the response page
// session registration
if(isset($dSession)){
// put the query parameters in the session
$dSession['requete']=strtolower($_SERVER['REQUEST_METHOD'])=='get' ? $_GET :
strtolower($_SERVER['REQUEST_METHOD'])=='post' ? $_POST : array();
$_SESSION['session']=serialize($dSession);
session_write_close();
}else{
// no session
session_destroy();
}
// we present the answer
include $dConfig['vuesReponse'][$dReponse['vuereponse']]['url'];
// end of script
exit(0);
}//endsession
قد تقرر إحدى الإجراءات عدم متابعة الجلسة. للقيام بذلك، ما عليها سوى عدم تمرير أي قيمة إلى المعلمة $dSession الخاصة بوظيفة endSession، وفي هذه الحالة يتم إتلاف الجلسة (session_destroy). إذا كان قاموس $dSession موجودًا، يتم حفظه في الجلسة، ثم يتم كتابته (session_write_close). وبالتالي، يمكن للإجراء الحالي تخزين العناصر في الجلسة عن طريق إضافة عناصر إلى قاموس $dSession. لاحظ أن وحدة التحكم تخزن تلقائيًا معلمات الطلب الحالي في الجلسة. وهذا يسمح باستردادها إذا لزم الأمر لمعالجة الطلب التالي.
3.5. إرسال الاستجابة إلى العميل
الغرض النهائي من وظيفة finSession هو إرسال استجابة إلى المستخدم. ذكرنا أن الاستجابة يمكن أن تحتوي على قوالب صفحات مختلفة. يتم تكوين هذه القوالب في $dConfig['vuesReponse']. في تطبيق ذي قالبين، قد يكون لدينا:
<?php
…
$dConfig['vuesReponse']['modele1']=array('url'=>'m-modele1.php');
$dConfig['vuesReponse']['modele2']=array('url'=>'m-modele2.php');
يحدد الإجراء الحالي القالب المطلوب في $dResponse['responseView']. يعرض وحدة التحكم هذا باستخدام التعليمات التالية:
بمجرد إرسال هذا الرد إلى العميل، يتوقف وحدة التحكم (exit).
3.6. تنفيذ الإجراءات
تنتظر وحدة التحكم الطلبات التي تحتوي على معلمة action=XX. إذا لم تكن هذه المعلمة موجودة في الطلب وكان الطلب من نوع GET، فإن الإجراء يأخذ القيمة 'init'. وهذا هو الحال بالنسبة للطلب الأول الذي يتم إرساله إلى وحدة التحكم، والذي يكون على الشكل http://machine:port/chemin/main.php.
<?php
…..
// retrieve the action to be taken
$sAction=$_GET['action'] ? strtolower($_GET['action']) : 'init';
بشكل افتراضي، يرتبط كل إجراء ببرنامج نصي مسؤول عن معالجة هذا الإجراء. على سبيل المثال:
<?php
...
// configuration of application actions
$dConfig['actions']['get:init']=array('url'=>'a-init.php');
$dConfig['actions']['post:calculerimpot']=array('url'=>'a-calculimpot.php');
$dConfig['actions']['get:retourformulaire']=array('url'=>'a-retourformulaire.php');
$dConfig['actions']['post:effacerformulaire']=array('url'=>'a-init.php');
$dConfig['actions']['enchainementinvalide']=array('url'=>'a-enchainementinvalide.php');
$dConfig['actions']['actionInvalide']=array('url'=>'a-actioninvalide.php');
هناك إجراءان محددان مسبقًا:
الحالات التي لا يمكن فيها للإجراء الحالي أن يتبع الإجراء السابق | |
الحالات التي لا يوجد فيها الإجراء المطلوب في قاموس الإجراءات |
تُكتب الإجراءات الخاصة بالتطبيق بالصيغة method:action، حيث method هي طريقة GET أو POST للطلب و action هي الإجراء المطلوب، وهنا: init، calculateTax، returnForm، clearForm. لاحظ أن الإجراء يتم استرداده، بغض النظر عما إذا كانت المعلمات مرسلة عبر طريقة GET أو POST، باستخدام التسلسل:
<?php
…
// retrieve the action to be taken
$sAction=$_GET['action'] ? strtolower($_GET['action']) : 'init';
في الواقع، حتى إذا تم إرسال النموذج عبر POST، لا يزال بإمكاننا كتابة:
سيتم إرسال عناصر النموذج (method='post'). ومع ذلك، سيكون عنوان URL المطلوب هو main.php?action=calculerimpot. سيتم استرداد معلمات عنوان URL هذا من المصفوفة $_GET، بينما سيتم استرداد عناصر النموذج الأخرى من المصفوفة $_POST.
باستخدام قاموس الإجراءات، يقوم وحدة التحكم بتنفيذ الإجراء المطلوب على النحو التالي:
<?php
...
// share processing
$scriptAction=$dConfig['actions'][$sAction] ?
$dConfig['actions'][$sAction]['url'] :
$dConfig['actions']['actionInvalide']['url'];
include $scriptAction;
إذا لم يكن الإجراء المطلوب موجودًا في قاموس الإجراءات، فسيتم تنفيذ البرنامج النصي المقابل للإجراء غير الصالح. بمجرد تحميل البرنامج النصي للإجراء في وحدة التحكم، يتم تشغيله. لاحظ أنه يمكنه الوصول إلى متغيرات وحدة التحكم ($dConfig، $dSession) بالإضافة إلى قواميس PHP فائقة العالمية ($_GET، $_POST، $_SERVER، $_ENV، $_SESSION). يحتوي البرنامج النصي على منطق التطبيق واستدعاءات لفئات الأعمال. في جميع الحالات، يجب أن
- ملء قاموس $dSession إذا كان هناك أي عناصر تحتاج إلى الحفظ في الجلسة الحالية
- تحديد اسم قالب الاستجابة المراد عرضه في $dResponse['vuereponse']
- ينتهي باستدعاء `finSession($dConfig, $dReponse, $dSession)`. إذا كان من المقرر إتلاف الجلسة، فسينتهي الإجراء ببساطة باستدعاء `finSession($dConfig, $dReponse)`.
من أجل الاتساق، قد تضع الإجراء جميع المعلومات التي تحتاجها طرق العرض في قاموس $dReponse. لكن هذا ليس مطلوبًا. فقط القيمة $dReponse['vuereponse'] هي الأساسية. لاحظ أن كل نصوص إجراءات تنتهي باستدعاء دالة finSession، والتي تنتهي هي نفسها بعملية خروج. لذلك، لا يوجد عودة من نصوص الإجراءات.
3.7. تسلسل الإجراءات
يمكن النظر إلى تطبيق الويب على أنه آلة ذات حالات محدودة. ترتبط الحالات المختلفة للتطبيق بالطرق المعروضة للمستخدم. يمكن للمستخدم الانتقال إلى طريقة أخرى عبر رابط أو زر. يكون تطبيق الويب قد غير حالته. لقد رأينا أن الإجراء يبدأ بطلب على شكل http://machine:port/chemin/main.php?action=XX. يجب أن يأتي عنوان URL هذا من رابط موجود في الطريقة المعروضة للمستخدم. نريد منع المستخدم من كتابة عنوان URL http://machine:port/chemin/main.php?action=XX مباشرةً، وبالتالي تجاوز المسار الذي خطط له التطبيق. وينطبق هذا أيضًا إذا كان العميل برنامجًا.
يكون مسار التنقل صالحًا إذا كان عنوان URL المطلوب هو عنوان يمكن الوصول إليه من آخر عرض تم تقديمه للمستخدم. من السهل تحديد قائمة عناوين URL هذه. وهي تتكون من
- عناوين URL الموجودة في العرض، إما كروابط أو كأهداف لإجراءات من نوع الإرسال
- عناوين URL التي يُسمح للمستخدم بكتابتها مباشرةً في متصفحه عند عرض العرض.
قائمة حالات التطبيق ليست بالضرورة هي نفس قائمة العروض. لنأخذ على سبيل المثال العرض البسيط التالي، **erreurs.php**:
Les erreurs suivantes se sont produites :
<ul>
<?php
for($i=0;$i<count($dReponse["erreurs"]);$i++){
echo "<li class='erreur'>".$dReponse["erreurs"][$i]."</li>\n";
}//for
?>
</ul>
<div class="info"><?php echo $dReponse["info"] ?></div>
<a href="<?php echo $dReponse["href"] ?>"><?php echo $dReponse["lien"] ?></a>
سيتم دمج هذا العرض الأساسي في تكوين من العروض الأساسية التي ستشكل الاستجابة. في هذا العرض، يوجد رابط يمكن وضعه ديناميكيًا. يمكن بعد ذلك عرض العرض errors.php مع n روابط مختلفة حسب الظروف. سيؤدي هذا إلى n حالات مختلفة للتطبيق. في الحالة #i، سيتم عرض العرض errors.php مع الرابط lieni. في هذه الحالة، يُسمح فقط باستخدام lieni.
سيتم تخزين قائمة حالات التطبيق والإجراءات الممكنة في كل حالة في قاموس $dConfig['etats']:
<?php
...
// application status configuration
$dConfig['etats']['e-formulaire']=array(
'actionsautorisees'=>array('post:calculerimpot','get:init','post:effacerformulaire'),
'vue'=>'e-formulaire2.php');
$dConfig['etats']['e-erreurs']=array(
'actionsautorisees'=>array('get:retourformulaire','get:init'),
'vue'=>'e-erreurs2.php');
$dConfig['etats']['sansetat']=array('actionsautorisees'=>array('get:init'));
يحتوي التطبيق أعلاه على حالتين مسمايتين: e-form و e-errors. نضيف حالة تسمى stateless، والتي تتوافق مع بدء تشغيل التطبيق الأولي عندما لم يكن له حالة. في الحالة E، توجد قائمة الإجراءات المسموح بها في المصفوفة $dConfig['states'][E]['allowedActions']. وهي تحدد الطريقة المسموح بها (get/post) للإجراء واسم الإجراء. في المثال أعلاه، هناك أربعة إجراءات ممكنة: get:init، post:calculateTax، get:returnForm، و post:clearForm.
باستخدام قاموس $dConfig['etats'], يمكن لوحدة التحكم تحديد ما إذا كان $sAction الحالي مسموحًا به في الحالة الحالية للتطبيق. يتم إنشاء هذه الحالة بواسطة كل إجراء وتخزينها في الجلسة في $dSession['etat']. فيما يلي كود وحدة التحكم للتحقق مما إذا كان الإجراء الحالي مسموحًا به:
<?php
.....
// is the sequence of actions normal?
if( ! enchainementOK($dConfig,$dSession,$sAction)){
// abnormal sequence
$sAction='enchainementinvalide';
}//if
// share processing
$scriptAction=$dConfig['actions'][$sAction] ?
$dConfig['actions'][$sAction]['url'] :
$dConfig['actions']['actionInvalide']['url'];
include $scriptAction;
..........
//--------------------------------------------------------------------
function enchainementOK(&$dConfig,&$dSession,$sAction){
// checks whether the current action is authorized with respect to the previous state
$etat=$dSession['etat']['principal'];
if(! isset($etat)) $etat='sansetat';
// check action
$actionsautorisees=$dConfig['etats'][$etat]['actionsautorisees'];
$autorise= ! isset($actionsautorisees) || in_array($sAction,$actionsautorisees);
return $autorise;
}
المنطق هو كما يلي: يُسمح بإجراء $sAction إذا كان موجودًا في القائمة $dConfig['states'][$state]['allowedActions'], أو إذا كانت هذه القائمة غير موجودة، وفي هذه الحالة يُسمح بأي إجراء. $state هي حالة التطبيق في نهاية دورة طلب العميل/استجابة الخادم السابقة. تم تخزين هذه الحالة في الجلسة ويتم استردادها من هناك. إذا تبين أن الإجراء المطلوب غير صالح، يتم تنفيذ البرنامج النصي $dConfig['actions']['invalidSequence']['url']. سيتولى هذا البرنامج النصي إرسال استجابة مناسبة إلى العميل.
خلال مرحلة التطوير، يمكن ترك قاموس $dConfig['etats'] فارغًا. في هذه الحالة، تسمح أي حالة بأي إجراء. يمكن إتمام القاموس بمجرد الانتهاء من تصحيح أخطاء التطبيق بالكامل. سيحمي هذا القاموس التطبيق من الإجراءات غير المصرح بها.
3.8. تصحيح الأخطاء
يوفر وحدة التحكم وظيفتين للتصحيح:
- تعرض وظيفة التتبع رسالة في إخراج HTML
- تعرض وظيفة التفريغ محتويات القاموس في نفس الدفق
يمكن لأي برنامج نصي Action Script استخدام هاتين الوظيفتين. ونظرًا لأن كود برنامج Action Script مضمن في كود وحدة التحكم، فإن وظيفتي التتبع والتفريغ ستكونان مرئيتين للبرامج النصية.
3.9. الخلاصة
تم تصميم وحدة التحكم العامة لتمكين المطور من التركيز على الإجراءات وطرق العرض الخاصة بتطبيقه. وهي تتولى ما يلي نيابة عنه:
- إدارة الجلسة (الاستعادة، الحفظ)
- التحقق من صحة الإجراءات المطلوبة
- تنفيذ البرنامج النصي المرتبط بالإجراء
- إرسال استجابة إلى العميل تتناسب مع نتيجة تنفيذ الإجراء