Skip to content

3. وحدة تحكم عامة

3.1. مقدمة

في الطريقة السابقة، كان من المفهوم أننا كنا سنكتب وحدة التحكم المسماة main.php. مع قليل من الخبرة، ندرك أن وحدة التحكم هذه غالبًا ما تقوم بنفس المهام، ومن ثم يكون من المغري كتابة وحدة تحكم عامة يمكن استخدامها في معظم تطبيقات الويب. قد يكون كود وحدة التحكم هذه كما يلي:

<?php
     // وحدة تحكم عامة

  // قراءة التكوين
  include 'config.php';

  // تضمين المكتبات
  for($i=0;$i<count($dConfig['includes']);$i++){
      include($dConfig['includes'][$i]);
  }//for  

  // بدء أو استئناف الجلسة
  session_start();
  $dSession=$_SESSION["session"];
  if($dSession) $dSession=unserialize($dSession);

  // استرداد الإجراء المطلوب
  $sAction=$_GET['action'] ? strtolower($_GET['action']) : 'init';
  $sAction=strtolower($_SERVER['REQUEST_METHOD']).":$sAction";

     // هل تسلسل الإجراءات طبيعي؟
  if( ! enchainementOK($dConfig,$dSession,$sAction)){  
    // تسلسل غير طبيعي
    $sAction='enchainementinvalide';
  }//if

     // معالجة الإجراء
  $scriptAction=$dConfig['actions'][$sAction] ? 
    $dConfig['actions'][$sAction]['url'] : 
    $dConfig['actions']['actionInvalide']['url'];
  include $scriptAction;

  // إرسال الرد (العرض) إلى العميل
  $sEtat=$dSession['etat']['principal'];
  $scriptVue=$dConfig['etats'][$sEtat]['vue'];
  include $scriptVue;

  // نهاية البرنامج النصي - لا ينبغي الوصول إلى هذه المرحلة إلا في حالة وجود خطأ
  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: قاموس التكوين
      // $dSession: قاموس يحتوي على معلومات الجلسة
         // $dReponse: قاموس معلمات صفحة الاستجابة

    // تسجيل الجلسة
    if(isset($dSession)){
      // يتم وضع معلمات الاستعلام في الجلسة
      $dSession['requete']=strtolower($_SERVER['REQUEST_METHOD'])=='get' ? $_GET :
          strtolower($_SERVER['REQUEST_METHOD'])=='post' ? $_POST : array();
        $_SESSION['session']=serialize($dSession);
      session_write_close();
    }else{    
        // لا توجد جلسة
      session_destroy();
    }

         // يتم عرض الرد
        include $dConfig['vuesReponse'][$dReponse['vuereponse']]['url'];

    // نهاية البرنامج النصي
    exit(0);
  }//نهاية الجلسة      

  //--------------------------------------------------------------------
    function enchainementOK(&$dConfig,&$dSession,$sAction){
      // التحقق مما إذا كان الإجراء الحالي مسموحًا به بالنسبة للحالة السابقة
    $etat=$dSession['etat']['principal'];
    if(! isset($etat)) $etat='sansetat';

    // التحقق من الإجراء
    $actionsautorisees=$dConfig['etats'][$etat]['actionsautorisees'];
    $autorise= ! isset($actionsautorisees) || in_array($sAction,$actionsautorisees);
        return $autorise;    
  }

  //--------------------------------------------------------------------
  function dump($dInfos){
      // عرض قاموس المعلومات
    while(list($clé,$valeur)=each($dInfos)){
        echo "[$clé,$valeur]<br>\n";
    }//while
  }//المتابعة

  //--------------------------------------------------------------------
  function trace($msg){
      echo $msg."<br>\n";
  }//متابعة

?>

3.2. ملف تكوين التطبيق

يتم تكوين التطبيق في برنامج نصي يحمل بالضرورة الاسم config.php. يتم وضع معلمات التطبيق في قاموس يسمى $dConfig يستخدمه كل من وحدة التحكم وبرامج الإجراءات النصية والقوالب والعروض الأولية.

3.3. المكتبات التي يجب تضمينها في وحدة التحكم

يتم وضع المكتبات التي يجب تضمينها في كود وحدة التحكم في الجدول $dConfig['includes']. تقوم وحدة التحكم بتضمينها باستخدام تسلسل الكود التالي:

<?php
...
  // قراءة التكوين
  include "config.php";

  // تضمين المكتبات
  for($i=0;$i<count($dConfig['includes']);$i++){
      include($dConfig['includes'][$i]);
  }//for  

3.4. إدارة الجلسات

يقوم وحدة التحكم العامة بإدارة الجلسة تلقائيًا. وهي تقوم بحفظ واسترداد محتوى الجلسة عبر القاموس $dSession. قد يحتوي هذا القاموس على كائنات يجب تسلسلها حتى يمكن استردادها بشكل صحيح لاحقًا. المفتاح المرتبط بهذا القاموس هو 'session'. كما يتم استرداد الجلسة باستخدام الكود التالي:

<?php

  // بدء أو استئناف الجلسة
  session_start();
  $dSession=$_SESSION["session"];
  if($dSession) $dSession=unserialize($dSession);

إذا أرادت إحدى الإجراءات حفظ معلومات في الجلسة، فستضيف مفاتيح وقيمًا إلى القاموس $dSession. ونظرًا لأن جميع الإجراءات تشترك في نفس الجلسة، فهناك خطر حدوث تعارض في مفاتيح الجلسة إذا تم تطوير التطبيق بشكل مستقل من قبل عدة أشخاص. وهذه مشكلة. يجب تطوير مرجع يسرد مفاتيح الجلسة، وهو مرجع مشترك بين الجميع. سنرى أن كل إجراء ينتهي باستدعاء الدالة التالية finSession:

<?php
... 
 // ---------------------------------------------------------------
  function finSession(&$dConfig,&$dReponse,&$dSession){
    // $dConfig: قاموس التكوين
      // $dSession: قاموس يحتوي على معلومات الجلسة
         // $dReponse: قاموس معلمات صفحة الاستجابة

    // تسجيل الجلسة
    if(isset($dSession)){
      // يتم وضع معلمات الطلب في الجلسة
      $dSession['requete']=strtolower($_SERVER['REQUEST_METHOD'])=='get' ? $_GET :
          strtolower($_SERVER['REQUEST_METHOD'])=='post' ? $_POST : array();
        $_SESSION['session']=serialize($dSession);
      session_write_close();
    }else{    
        // لا توجد جلسة
      session_destroy();
    }

         // عرض الرد
        include $dConfig['vuesReponse'][$dReponse['vuereponse']]['url'];

    // نهاية البرنامج النصي
    exit(0);
  }//إنهاء الجلسة      

يمكن أن تقرر إحدى الإجراءات عدم متابعة الجلسة. ويكفي لذلك عدم تمرير أي قيمة إلى المعلمة $dSession للدالة finSession، وفي هذه الحالة يتم حذف الجلسة (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');

تحدد العملية الجارية في $dReponse['vuereponse'] النموذج المطلوب. ويتم عرض هذا النموذج بواسطة وحدة التحكم من خلال التعليمات التالية:

<?php

         // يتم عرض الرد
        include $dConfig['vuesReponse'][$dReponse['vuereponse']]['url'];

بمجرد إرسال هذا الرد إلى العميل، يتوقف وحدة التحكم (exit).

3.6. تنفيذ الإجراءات

ينتظر وحدة التحكم الطلبات التي تحتوي على المعلمة action=XX. إذا لم يكن هذا المعامل موجودًا في الطلب وكان الطلب هو GET، فإن الإجراء يأخذ القيمة 'init'. وهذا هو الحال بالنسبة للطلب الأول الذي يتم إرساله إلى وحدة التحكم والذي يكون بالشكل http://machine:port/chemin/main.php.

<?php
..
  // استرداد الإجراء المطلوب
  $sAction=$_GET['action'] ? strtolower($_GET['action']) : 'init';

بشكل افتراضي، يرتبط بكل إجراء برنامج نصي مسؤول عن معالجة هذا الإجراء. على سبيل المثال:

<?php
... 
// تكوين إجراءات التطبيق
  $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');          

هناك إجراءان محددان مسبقًا:

enchainementInvalide
في حالة عدم تمكن الإجراء الحالي من متابعة الإجراء السابق
actionInvalide
في حالة عدم وجود الإجراء المطلوب في قاموس الإجراءات

يتم تدوين الإجراءات الخاصة بالتطبيق بالصيغة method:action حيث method هي طريقة get أو post للطلب و action هي الإجراء المطلوب، وهنا: init، calculerimpot، retourformulaire، effacerformulaire. يُلاحظ أن الإجراء يتم استرداده، بغض النظر عن طريقة إرسال المعلمات GET أو POST، من خلال التسلسل:

<?php

  // استرداد الإجراء المطلوب
  $sAction=$_GET['action'] ? strtolower($_GET['action']) : 'init'; 

في الواقع، حتى إذا تم إرسال نموذج، يمكن دائمًا كتابة:

<form method='post' action='main.php?action=calculerimpot'>
..
</form>

سيتم إرسال عناصر النموذج (method='post'). ومع ذلك، سيكون عنوان URL المطلوب هو main.php?action=calculerimpot. سيتم استرداد معلمات هذا URL في القاموس $_GET بينما سيتم استرداد عناصر النموذج الأخرى في القاموس $_POST.

وباستخدام قاموس الإجراءات، يقوم وحدة التحكم بتنفيذ الإجراء المطلوب على النحو التالي:

<?php
...
    // معالجة الإجراء
  $scriptAction=$dConfig['actions'][$sAction] ? 
    $dConfig['actions'][$sAction]['url'] : 
    $dConfig['actions']['actionInvalide']['url'];
  include $scriptAction;

إذا لم تكن الإجراء المطلوب موجودًا في قاموس الإجراءات، فسيتم تنفيذ البرنامج النصي المقابل لإجراء غير صالح. بمجرد تحميل البرنامج النصي للإجراء داخل وحدة التحكم، يتم تنفيذه. وتجدر الإشارة إلى أنه يمكنه الوصول إلى متغيرات وحدة التحكم ($dConfig، $dSession) وكذلك إلى القواميس الفائقة الشمولية لـ PHP ($_GET، $_POST، $_SERVER، $_ENV، $_SESSION). سيحتوي البرنامج النصي على منطق التطبيق واستدعاءات لفئات الأعمال. في جميع الحالات، يجب أن

  • ملء القاموس $dSession إذا كان يجب حفظ العناصر في الجلسة الحالية
  • وإشارة في $dReponse['vuereponse'] إلى اسم نموذج الاستجابة المراد عرضه
  • أن تنتهي باستدعاء finSession($dConfig, $dReponse, $dSession). إذا كان يجب إغلاق الجلسة، فستنتهي العملية ببساطة باستدعاء finSession($dConfig, $dReponse).

من أجل الاتساق، يمكن للإجراء وضع جميع المعلومات المطلوبة للعروض في القاموس $dReponse. ولكن هذا ليس إلزامياً. القيمة $dReponse['vuereponse'] هي الوحيدة الضرورية. يُلاحظ أن كل نصوص الإجراءات تنتهي باستدعاء الدالة finSession التي تنتهي بدورها بعملية exit. وبالتالي، لا يمكن العودة من نص إجراء.

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>

سيتم دمج هذا العرض الأساسي في مجموعة من العروض الأساسية التي ستشكل الاستجابة. يوجد في هذا العرض رابط يمكن وضعه ديناميكيًا. يمكن عندئذ عرض العرض erreurs.php مع n روابط مختلفة حسب الظروف. سيؤدي ذلك إلى ظهور n حالات مختلفة للتطبيق. في الحالة رقم i، سيتم عرض العرض erreurs.php مع الرابط lieni. في هذه الحالة، لا يُقبل سوى استخدام lieni.

سيتم تسجيل قائمة حالات التطبيق والإجراءات الممكنة في كل حالة في القاموس $dConfig['etats']:

<?php
...  
// تكوين حالات التطبيق
  $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-formulaire و e-erreurs. نضيف حالة تسمى sansetat تتوافق مع بدء تشغيل التطبيق في البداية عندما لم يكن له حالة. في الحالة E، توجد قائمة الإجراءات المسموح بها في الجدول $dConfig['etats'][E]['actionsautorisees']. يتم تحديد الطريقة (get/post) المسموح بها للإجراء واسمه. في المثال أعلاه، هناك أربعة إجراءات ممكنة: get:init، post:alculerimpot، get:retourformulaire و post:effacerformulaire.

وباستخدام القاموس $dConfig['etats']، يمكن لوحدة التحكم تحديد ما إذا كانت الإجراء $sAction الجاري مسموحًا به أم لا في الحالة الحالية للتطبيق. يتم إنشاء هذا القيم بواسطة كل إجراء وتخزينه في الجلسة في $dSession['etat']. رمز وحدة التحكم للتحقق مما إذا كان الإجراء الحالي مسموحًا به أم لا هو كما يلي:

<?php
.....
     // هل تسلسل الإجراءات طبيعي؟
  if( ! enchainementOK($dConfig,$dSession,$sAction)){  
    // تسلسل غير طبيعي
    $sAction='enchainementinvalide';
  }//if

     // معالجة الإجراء
  $scriptAction=$dConfig['actions'][$sAction] ? 
    $dConfig['actions'][$sAction]['url'] : 
    $dConfig['actions']['actionInvalide']['url'];
  include $scriptAction;
..........
  //--------------------------------------------------------------------
    function enchainementOK(&$dConfig,&$dSession,$sAction){
      // التحقق مما إذا كان الإجراء الحالي مسموحًا به بالنسبة للحالة السابقة
    $etat=$dSession['etat']['principal'];
    if(! isset($etat)) $etat='sansetat';

    // التحقق من الإجراء
    $actionsautorisees=$dConfig['etats'][$etat]['actionsautorisees'];
    $autorise= ! isset($actionsautorisees) || in_array($sAction,$actionsautorisees);
        return $autorise;    
  }

المنطق هو كما يلي: يُسمح بإجراء $sAction إذا كان موجودًا في القائمة $dConfig['etats'][$etat]['actionsautorisees'] أو إذا كانت هذه القائمة غير موجودة مما يسمح عندئذ بأي إجراء. $etat هي حالة التطبيق في نهاية الدورة السابقة لطلب العميل/استجابة الخادم. تم تخزين هذه الحالة في الجلسة ويتم العثور عليها هناك. إذا تبين أن الإجراء المطلوب غير قانوني، يتم تنفيذ البرنامج النصي $dConfig['actions']['enchainementInvalide']['url']. سيتولى هذا البرنامج النصي إرسال استجابة مناسبة إلى العميل.

في مرحلة التطوير، قد لا يتم ملء القاموس $dConfig['etats']. في هذه الحالة، تسمح أي حالة بأي إجراء. يمكن تطوير القاموس عندما يتم تصحيح أخطاء التطبيق بالكامل. سيحمي القاموس التطبيق من الإجراءات غير المصرح بها.

3.8. التصحيح

يوفر جهاز التحكم وظيفتين للتصحيح:

  • تسمح وظيفة التتبع بعرض رسالة في التدفق HTML
  • تسمح وظيفة dump بعرض محتوى قاموس في نفس التدفق

يمكن لأي سكريبت إجراء استخدام هاتين الوظيفتين. وبالفعل، بما أن كود سكريبت الإجراء مضمن (include) في كود وحدة التحكم، فإن وظيفتي trace و dump ستكونان مرئيتين من السكريبتات.

3.9. الخلاصة

يهدف وحدة التحكم العامة إلى تمكين المطور من التركيز على الإجراءات وطرق عرض تطبيقه. وهي تضمن له:

  • إدارة الجلسة (الاستعادة، الحفظ)
  • التحقق من صحة الإجراءات المطلوبة
  • تنفيذ البرنامج النصي المرتبط بالإجراء
  • إرسال الرد المناسب إلى العميل بناءً على نتيجة تنفيذ الإجراء