Skip to content

3. یک کنترلر عمومی

3.1. مقدمه

در روش قبلی، مشخص شد که باید کنترلر با نام main.php را بنویسیم. با کمی تجربه، درمی‌یابیم که این کنترلر اغلب وظایف یکسانی را انجام می‌دهد و بنابراین وسوسه می‌شود که یک کنترلر عمومی (generic) بنویسیم که بتوان در اکثر برنامه‌های وب از آن استفاده کرد. کد این کنترلر می‌تواند به شکل زیر باشد:

<?php
     //کنترل‌کنندهٔ عمومی

  // خواندن پیکربندی
  include 'config.php';

  // شامل کردن کتابخانه‌ها
  for($i=0;$i<count($dConfig['includes']);$i++){
      include($dConfig['includes'][$i]);
  }//برای  

  // شروع یا از سرگیری جلسه
  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';
  }//اگر

     // پردازش اقدام
  $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";
    }//در حالی که
  }//پیگیری

  //--------------------------------------------------------------------
  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]);
  }//برای  

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'];

پس از ارسال این پاسخ به کلاینت، کنترل‌کننده خاتمه می‌یابد (خروج).

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، calculateTax، returnForm، clearForm. توجه داشته باشید که اقدام، صرف‌نظر از اینکه متد استفاده شده برای ارسال پارامترها GET یا POST باشد، از طریق توالی زیر بازیابی می‌شود:

<?php

  // بازیابی اقدام انجام‌شدنی
  $sAction=$_GET['action'] ? strtolower($_GET['action']) : 'init'; 

در واقع، حتی اگر یک فرم ارسال شده باشد، شما همچنان می‌توانید بنویسید:

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

اجزای فرم ارسال خواهند شد (روش='post'). با این حال، URL درخواستی main.php?action=calculerimpot خواهد بود. پارامترهای این URL از دیکشنری $_GET بازیابی می‌شوند، در حالی که سایر عناصر فرم از دیکشنری $_POST بازیابی خواهند شد.

با استفاده از دیکشنری action، کنترلر اقدام درخواستی را به شرح زیر اجرا می‌کند:

<?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 به پایان می‌رسد که خود با یک عملیات خروج خاتمه می‌یابد. بنابراین، بازگشت از یک اسکریپت عملی امکان‌پذیر نیست.

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";
        }//برای
    ?>
</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-form و e-errors. ما در حال افزودن حالتی به نام «no-state» هستیم که مربوط به راه‌اندازی اولیهٔ برنامه است، زمانی که هیچ حالتی نداشت. در حالت 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';
  }//اگر

     //پردازش اقدام
  $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. عیب‌یابی

کنترل‌کننده دو تابع اشکال‌زدایی ارائه می‌دهد:

  • تابع `trace` پیامی را در جریان HTML نمایش می‌دهد
  • تابع dump محتویات یک دیکشنری را در همان جریان نمایش می‌دهد

هر اسکریپت اکشن می‌تواند از این دو تابع استفاده کند. از آنجایی که کد اسکریپت اکشن در کد کنترل‌کننده گنجانده شده است، توابع trace و dump برای اسکریپت‌ها قابل مشاهده خواهند بود.

3.9. نتیجه‌گیری

کنترلر عمومی طوری طراحی شده است که به توسعه‌دهنده اجازه دهد بر روی اقدامات و نماهای برنامه خود تمرکز کند. این کنترلر موارد زیر را برای او مدیریت می‌کند:

  • مدیریت جلسه (بازیابی، ذخیره)
  • اعتبارسنجی اقدامات درخواستی
  • اجرای اسکریپت مرتبط با اقدام
  • ارسال پاسخی مناسب به کلاینت بر اساس نتیجه اجرای اقدام