Skip to content

7. مطالعه موردی: مدیریت پایگاه داده محصول مبتنی بر وب

کدهای این مطالعه موردی در |ICI| موجود است.

اهداف:

  • نوشتن یک کلاس برای مدیریت پایگاه داده مقالات
  • نوشتن یک برنامه وب بر اساس این کلاس
  • معرفی صفحات سبک
  • برای پیشنهاد شروعی از یک روش‌شناسی توسعه برای برنامه‌های وب ساده
  • معرفی جاوااسکریپت در مرورگر سمت کلاینت

اعتبارها: هسته این مطالعه موردی از کتاب «Les cahiers du programmeur – PHP/MySQL» نوشته ژان-فیلیپ لوبو، منتشر شده توسط Eyrolles، گرفته شده است.

7.1. مقدمه

یک فروشنده مایل است اقلامی را که در مغازه خود می‌فروشد مدیریت کند. او در خانه از قبل یک برنامه ACCESS دارد که این کار را انجام می‌دهد، اما دنیای وب او را وسوسه کرده است. او یک حساب کاربری نزد یک ارائه‌دهنده خدمات اینترنتی دارد که به مشتریانش اجازه می‌دهد اسکریپت‌های PHP را در پوشه‌های شخصی خود نصب کنند. این امر به آنها امکان می‌دهد وب‌سایت‌های پویا ایجاد کنند. علاوه بر این، همین مشتریان یک حساب MySQL دارند که به آنها اجازه می‌دهد جداول ایجاد کنند تا داده‌ها را به اسکریپت‌های PHP خود ارائه دهند. بنابراین، فروشنده یک حساب MySQL با نام کاربری «adarticles» و رمز عبور «mdparticles» دارد. او یک پایگاه داده به نام «dbarticles» دارد که بر روی آن حقوق کامل دارد. بنابراین فروشنده ما همه چیز لازم برای آنلاین کردن سیستم مدیریت موجودی خود را در اختیار دارد. با کمک شما - با توجه به تخصص شما در توسعه وب - او در حال آغاز این پروژه است.

7.2. پایگاه داده

خرده‌فروش ما طرح اولیه (مک‌آپ) صفحه اصلی مورد نظر خود را به شرح زیر تهیه کرده است:

Image

دو نوع کاربر وجود خواهد داشت:

  • مدیران، که قادر خواهند بود تمام عملیات را روی جدول محصولات (افزودن، ویرایش، حذف، مشاهده و غیره) انجام دهند. آنها قادر خواهند بود از تمام موارد منوی ذکر شده در بالا استفاده کنند. به طور خاص، آنها قادر خواهند بود هر پرس‌وجویی را از طریق گزینه [Requête SQL] اجرا کنند.
  • کاربران عادی (غیرمدیران) که دارای حقوق محدودی خواهند بود: حقوق افزودن، ویرایش، حذف و مشاهده. آنها ممکن است تنها برخی از این حقوق را داشته باشند، مانند حق مشاهده.

از آنجا که انواع مختلفی از کاربران پایگاه داده با حقوق متفاوت وجود دارند، احراز هویت ضروری است. به همین دلیل است که صفحه اصلی با این مرحله آغاز می‌شود. برای تعیین اینکه چه کسی چه کسی است و چه کسی مجاز به انجام چه کاری است، از دو جدول USERS و DROITS استفاده می‌شود. جدول USERS ساختار زیر را خواهد داشت:

login
نام کاربری کاربر که به‌طور منحصربه‌فرد او را شناسایی می‌کند. این فیلد کلید اصلی جدول است.
mdp
رمز عبور کاربر به صورت متن ساده
admin 
حرف 'y' (بله) اگر کاربر مدیر باشد، در غیر این صورت حرف 'n' (خیر).

محتویات جدول ممکن است به شرح زیر باشد:

Image

جدول DROITS مجوزهای کاربران غیرمدیر را که در جدول USERS فهرست شده‌اند، مشخص می‌کند. ساختار آن به شرح زیر است:

login
نام کاربری ورود، که به‌طور منحصربه‌فرد آن‌ها را شناسایی می‌کند.
این فیلد یک کلید خارجی در جدول DROITS است و به
ستون «login» در جدول USERS.
table
نام جدولی که کاربر حق دسترسی به آن را دارد.
ajouter
حرف 'y' (بله) اگر کاربر حق افزودن به جدول را داشته باشد،
در غیر این صورت، کاراکتر 'n' (خیر).
modifier
حق ویرایش: 'y' یا 'n'
supprimer
حق حذف: 'y' یا 'n'
consulter
اجازه مشاهده: 'y' یا 'n'

محتویات جدول ممکن است به شرح زیر باشد:

Image

یادداشت‌ها:

  • کاربر U که در جدول USERS فهرست شده اما در جدول DROITS نیست، هیچ حقی ندارد.
  • در مثال ما، کاربران فقط به یک جدول، یعنی جدول ARTICLES دسترسی خواهند داشت. با این حال، خرده‌فروش آینده‌نگر ما با این وجود، فیلد «جدول» را به ساختار جدول DROITS اضافه کرده است تا امکان افزودن جدول‌های جدید به برنامه‌اش را در آینده فراهم کند.
  • چرا وقتی فرض می‌کنیم از پایگاه‌داده MySQL استفاده خواهیم کرد، که خود قادر به مدیریت این مجوزها در جداول خودش است (و حتی از ما برای این کار مجهزتر است)، باید مجوزها را در جداول خودمان مدیریت کنیم؟ صرفاً به این دلیل که خرده‌فروش ما حقوق مدیریتی روی پایگاه داده MySQL ندارد که به او اجازه دهد کاربران را ایجاد کرده و به آن‌ها حقوق اعطا کند. بیایید فراموش نکنیم که پایگاه داده MySQL توسط یک ارائه‌دهنده خدمات اینترنتی میزبانی می‌شود و صاحب فروشگاه صرفاً یک کاربر آن پایگاه داده است که (خوشبختانه) هیچ حق مدیریتی ندارد. با این حال، آنها حقوق کامل یک پایگاه داده به نام dbarticles را دارند که در حال حاضر با نام کاربری «adarticles» و رمز عبور «mdparticles» به آن دسترسی پیدا می‌کنند. تمام جداول این برنامه در همین پایگاه داده قرار دارند.

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

code
کد کالا – کلید اصلی جدول
- دقیقاً ۴ کاراکتر
nom
نام آیتم
prix
قیمت
stockActuel
سطح موجودی فعلی
stockMinimum
سطحی که پایین‌تر از آن یک
باید سفارش تأمین مجدد ثبت شود

محتوای آن که در ابتدا برای اهداف آزمایشی استفاده شده است، می‌تواند به شرح زیر باشد:

Image

7.3. محدودیت‌های پروژه

خُرده‌فروش در حال مهاجرت یک برنامه محلی ACCESS به یک برنامه وب است. او نمی‌داند چه بر سر آن خواهد آمد یا چگونه تکامل خواهد یافت. با این حال، او می‌خواهد برنامه جدید کاربرپسند و مقیاس‌پذیر باشد. به همین دلیل، هنگام طراحی جداول، مشاور فناوری اطلاعات او پیش‌بینی کرد که ممکن است:

  • کاربران مختلف با مجوزهای متفاوت: این امکان را برای خرده‌فروش فراهم می‌کند تا برخی وظایف را بدون اعطای حقوق مدیریتی به افراد دیگر واگذار کند
  • در آینده، جداول دیگری به غیر از جدول ARTICLES

همان مشاور پیشنهادات بیشتری ارائه می‌دهد:

  • او می‌داند که در توسعه نرم‌افزار، لایه ارائه و لایه منطق کسب‌وکار باید به‌وضوح از هم جدا شوند. معماری یک برنامه وب اغلب به شرح زیر است:

رابط کاربری در اینجا یک مرورگر وب است، اما می‌تواند یک برنامه مستقل نیز باشد که از طریق شبکه، درخواست‌ها (HTTP) را به سرویس وب ارسال کرده و نتایج دریافتی را قالب‌بندی می‌کند. منطق برنامه از اسکریپت‌هایی تشکیل شده است که درخواست‌های کاربر را پردازش می‌کنند، در این مورد اسکریپت‌های PHP. منبع داده اغلب یک پایگاه داده است، اما می‌تواند یک دایرکتوری LDAP یا یک سرویس وب راه دور نیز باشد. حفظ درجه بالایی از استقلال بین این سه عنصر به نفع توسعه‌دهنده است، به طوری که اگر یکی از آنها تغییر کند، دو عنصر دیگر نیازی به تغییر نداشته باشند یا تنها به حداقل تغییر نیاز داشته باشند. بنابراین مشاور فناوری اطلاعات خرده‌فروش پیشنهادات زیر را ارائه می‌دهد:

  • منطق کسب‌وکار برنامه در کلاسی به نام PHP قرار خواهد گرفت. بنابراین، بلوک [Logique applicative] فوق شامل عناصر زیر خواهد بود:

درون بلوک [Logique Applicative]، می‌توانیم تمایز قائل شویم

  • بلوک [IE=Interface d'Entrée]، که نقطه ورود برنامه است. این بلوک صرف‌نظر از نوع کلاینت، یکسان است.
  • بلوک [Classes métier] که شامل کلاس‌های مورد نیاز برای منطق برنامه است. این کلاس‌ها مستقل از نوع کلاینت هستند.
  • بلوک حاوی تولیدکننده‌های صفحه پاسخ، [IS1 IS2 ... IS=Interface de Sortie]. هر ژنراتور مسئول قالب‌بندی نتایج ارائه‌شده توسط منطق برنامه برای یک نوع کلاینت معین است: کد HTML برای یک مرورگر یا تلفن همراه (WAP)، کد XML برای یک برنامه مستقل، و غیره.

این مدل استقلال بالایی از کلاینت‌ها را تضمین می‌کند. چه کلاینت تغییر کند و چه بخواهیم نحوه ارائه نتایج را به‌روزرسانی کنیم، این تولیدکننده‌های خروجی [IS] هستند که باید ایجاد یا تطبیق داده شوند.

  • در یک برنامه وب، جداسازی بین لایه نمایش و لایه منطق کسب‌وکار را می‌توان با استفاده از صفحات سبک (style sheets) بهبود بخشید. این صفحات نحوه نمایش یک صفحه وب را در یک مرورگر کنترل می‌کنند. برای تغییر این نمایش، کافی است صفحه سبک مرتبط را تغییر داد. نیازی به تغییر منطق کسب‌وکار نیست. بنابراین ما در اینجا از یک صفحه سبک استفاده خواهیم کرد.
  • در نمودار بالا، این کلاس کسب‌وکار است که با منبع داده ارتباط برقرار می‌کند. برای اهداف این مثال، این منبع یک پایگاه داده MySQL است. برای امکان‌پذیر ساختن مهاجرت به یک پایگاه داده متفاوت، از کتابخانه PEAR استفاده خواهیم کرد که کلاس‌های دسترسی به پایگاه داده را ارائه می‌دهد که از نوع واقعی پایگاه داده مستقل هستند. بنابراین، اگر خرده‌فروش ما به اندازه کافی ثروتمند شود که یک سرور وب Microsoft IIS را در کسب‌وکار خود نصب کند، قادر خواهد بود پایگاه داده MySQL را با سرور SQL جایگزین کند، بدون اینکه نیازی به ایجاد هیچ‌گونه تغییر (یا با ایجاد تغییرات بسیار اندک) در کلاس کسب‌وکار باشد.

7.4. کلاس «Items»

کلاس 'Items' می‌تواند به صورت زیر تعریف شود:

<?php

     // کلاس آیتم که بر روی پایگاه دادهٔ آیتم شامل جداول زیر عمل می‌کند
     // اقلام: (کد، نام، قیمت، stockActuel, stockMinimum)
     // کاربران: (نام کاربری، رمز عبور، ادمین)
     // مجوزها: (نام کاربری، جدول، افزودن، ویرایش، حذف، مشاهده)

     // این کلاس user است که باید نام کاربری و رمز عبور مورد نیاز برای انجام هر عملیاتی روی پایگاه داده را فراهم کند
     // بنابراین آنها از قبل حقوق کامل روی پایگاه داده دارند. این بدان معناست که نیازی به انجام اقدامات احتیاطی خاص نیست
     // هیچ اقدام امنیتی خاصی در اینجا لازم نیست

     //کتابخانه‌ها
  require_once 'DB.php';

  class articles{

           // ویژگی‌ها
    var $sDSN;                        // رشته اتصال
          var $sDatabase;            // نام پایگاه داده
    var $oDB;                        //اتصال به پایگاه داده
    var $aErreurs;                // فهرست خطاها
    var $oRésultats;            // نتیجه یک پرس‌وجوی SELECT
        var $connecté;                // یک مقدار بولی که نشان می‌دهد آیا اتصال به پایگاه داده فعال است یا خیر
        var $sQuery;                    //آخرین پرس‌وجوی اجرا شده
    var $sUser;                    //شناسه ورود کاربر
    var $bAdmin;                    // در صورتی که کاربر مدیر باشد، مقدار true
    var $dDroits;                // فرهنگ اجازه‌های کاربر: جدول ->> آرایه(مشاهده، افزودن، حذف، ویرایش)

     // سازنده
    function articles($dDSN,$sUser,$sMdp){

             //$dDSN: فرهنگ لغت که پیوندی را که باید برقرار شود تعریف می‌کند
       // $dDSN['sgbd']: نوع SGBD که باید به آن اتصال برقرار شود
       // $dDSN['host']: نام ماشین میزبان که روی آن میزبانی می‌شود      
       // $dDSN['database']: نام پایگاه داده‌ای که باید به آن متصل شوید      
       // $dDSN['admin']: نام کاربری مالک پایگاه داده که باید به آن متصل شوید
       // $dDSN['mdpadmin']: رمز عبور آنها
       // $sUser: نام کاربری کاربری که می‌خواهد به پایگاه داده محصول دسترسی پیدا کند
       // $sMdp: رمز عبور آنها

       //یک اتصال در $oDB به پایگاه‌داده تعریف‌شده توسط $dDSN تحت هویت $dDSN['admin'] ایجاد می‌کند
       //اگر اتصال موفقیت‌آمیز باشد و کاربر $sUser احراز هویت شود  
           // مجوزها را برای کاربر $sUser در $bAdmin و $dDroits بارگذاری می‌کند
           //رشته اتصال پایگاه داده را در $sDSN تنظیم می‌کند
           //نام پایگاه داده برای اتصال را به $sDataBase تنظیم می‌کند
         //مقدار $connecté را روی true تنظیم می‌کند
       // اگر اتصال ناموفق باشد یا کاربر $sUser به درستی احراز هویت نشود
           // پیام‌های خطای مناسب را به فهرست $aErreurs اضافه می‌کند
         //در صورت لزوم اتصال را می‌بندد
         //مقدار $connecté را روی false تنظیم می‌کند 

  ...
    }//

    // ------------------------------------------------------------------
    function connect(){
             // (دوباره) به پایگاه داده متصل می‌شود
...
    }//اتصال

    // ------------------------------------------------------------------
    function disconnect(){
      //بستن اتصال به پایگاه داده $sDSN
...
    }//قطع اتصال

    // -------------------------------------------------------------------
    function execute($sQuery,$bAdmin){
            // $sQuery: پرس‌وجوی قابل اجرا
       // $bAdmin: true اگر اجرای آن به‌عنوان مدیر درخواست شده باشد
...
    }//اجرا

    // --------------------------------------------------------------------------
    function addArticle($dArticle){
         //یک آیتم $dArticle (کد، نام، قیمت، stockActuel، stockMinimum) به جدول اقلام اضافه می‌کند
   ...
    }//اضافه می‌کند          

    // ----------------------------------------------------------------------
    function modifyArticle($dArticle){
             //یک مورد $dArticle (کد، نام، قیمت، stockActuel، stockMinimum) در جدول اقلام
...
    }//به‌روزرسانی

    // ----------------------------------------------------------------------
    function deleteArticle($sCode){
             //یک آیتم را از جدول آیتم‌ها حذف می‌کند
       //که کد آن $sCode است
...
    }//حذف

    // ----------------------------------------------------------------------
    function vérifierArticle(&$dArticle){
         //اعتبار یک آیتم $dArticle (کد، نام، قیمت، stockActuel، stockMinimum)
...
    }//بررسی می‌کند

    // --------------------------------------------------------------------------
    function selectArticles($dQuery){
             //یک پرس‌وجوی SELECT را روی جدول اقلام اجرا می‌کند
       //این شامل سه مؤلفه است
       // فهرست ستون‌ها در $dQuery['colonnes']
       // فیلترسازی در $dQuery['where']
       // ترتیب نمایش در $dQuery['orderby']
...
    }//selectArticles            

        // --------------------------------
    function existeArticle($sCode){
         //اگر آیتم با کد $sCode در جدول آیتم‌ها وجود داشته باشد، مقدار TRUE را برمی‌گرداند
...
    }//existeArticle

    // --------------------------------------
    function existeUser($sUser,$sMdp){
             //بررسی می‌کند که آیا کاربر $sUser با رمز عبور $sMdp وجود دارد
       // بازمی‌گرداند (int $iErreur, string $sAdmin, hashtable $dDroits)
       // $iErreur = -1 در صورت بروز هرگونه خطای عملیاتی پایگاه داده – سپس لیست $aErreurs پر می‌شود
       // $iErreur = 1 اگر کاربر پیدا نشود (وجود ندارد یا رمز عبور نادرست)
       // $iErreur = 2 اگر کاربر وجود داشته باشد اما در جدول مجوزها هیچ مجوزی نداشته باشد
       // $iErreur = 3 اگر کاربر وجود داشته باشد و مدیر باشد
       // $iErreur = 0 اگر کاربر وجود داشته باشد و مدیر نباشد
       // $sAdmin = "y" اگر کاربر وجود داشته باشد و مدیر باشد ($iErreur == 3)، در غیر این صورت برابر با رشتهٔ خالی است
       //$dDroits فرهنگ حقوق کاربر است اگر مدیر نباشد ($iErreur==0)
       //در غیر این صورت یک آرایه خالی است
       //کلیدهای دیکشنری، جداولِ موردِ حقِ دسترسیِ کاربر هستند
       //مقدار مرتبط با این جدول به نوبه خود یک دیکشنری است که کلیدهای آن حقوق هستند
       // (مشاهده، افزودن، اصلاح، حذف) و مقادیر رشته‌های 'y' (بله) یا 'n' (خیر) هستند
...
    }//existeUser

    // --------------------------------------
    function getCodes(){
         //جدول کدها را بازمی‌گرداند
....
    }//getCodes    

  }//طبقه‌بندی می‌کند
?>      

نظرات

  • کلاس 'articles' برای دسترسی به پایگاه داده از کتابخانه PEAR::DB استفاده می‌کند، از این رو دستور
require_once 'DB.php';

این فراخوانی فرض می‌کند که اسکریپت DB.php در یکی از دایرکتوری‌های مشخص‌شده توسط گزینه include_path در فایل پیکربندی PHP قرار دارد.

  • سازنده باید بداند به کدام پایگاه داده متصل شود و تحت کدام هویت. این اطلاعات در فرهنگ لغت $dDSN ارائه می‌شود. به یاد داشته باشید که فرض اولیه این بود که نام پایگاه داده dbarticles است و متعلق به کاربری به نام admarticles با رمز عبور mdparticles می‌باشد. همچنین باید توجه داشت که این برنامه به چندین کاربر با مجوزهای متفاوت اجازه دسترسی می‌دهد. در اینجا ابهامی وجود دارد که باید برطرف شود. اتصال در واقع تحت هویت admarticles برقرار می‌شود، و در نهایت آن استتمام عملیات بر روی پایگاه داده dbarticles تحت این هویت انجام خواهد شد، زیرا این تنها نامی است که توسط SGBD MySQL که دارای حقوق کافی برای مدیریت پایگاه داده dbarticles است، شناخته می‌شود. برای «شبیه‌سازی» وجود کاربران مختلف، کاربر admarticles با مجوزهای کاربری که نام کاربری ($sUser) و رمز عبور ($sMdp) او به‌عنوان پارامتر به سازنده (constructor) ارسال می‌شود، عمل خواهد کرد. بنابراین، قبل از انجام عملیاتی روی پایگاه داده محصول، بررسی خواهیم کرد که کاربر ($sUser, $sMdp) واقعاً دارای مجوزهای لازم برای این کار است. در صورت تأیید، کاربر admarticles عملیات را از طرف او انجام خواهد داد.
  • نام کاربری و رمز عبور مدیر پایگاه داده محصول باید به سازنده ارسال شود. این یک اقدام احتیاطی معقول است. اگر این دو مورد اطلاعات به صورت کد سخت در کد کلاس قرار داده می‌شد، هر کاربری از کلاس می‌توانست به راحتی خود را به جای مدیر پایگاه داده محصول جا بزند. در واقع، کلاسی به نام PHP محافظت نشده است. به همین ترتیب، ویژگی کلاس $bAdmin، که نشان می‌دهد کاربری که ($sUser, $sMdp) برای او کار می‌شود، مدیر است یا خیر، می‌تواند به راحتی از خارج تنظیم شود، همانطور که در مثال زیر نشان داده شده است:
$oArticles=new articles($dDSN,$sUser,$sMdp)
//در اینجا، $sUser به‌عنوان کاربر غیرمدیر پایگاه داده شناخته شده است
$oArticle->bAdmin=TRUE;
//اکنون $sUser به یک مدیر تبدیل شده است

PHP نه JAVA است و نه C# و کلاس PHP صرفاً یک ساختار داده است که کمی پیشرفته‌تر از یک دیکشنری است اما امنیت یک کلاس واقعی را ارائه نمی‌دهد، جایی که ویژگی bAdmin باید به صورت خصوصی یا محافظت‌شده تعریف می‌شد و این امر تغییر آن از خارج را غیرممکن می‌کرد. از آنجا که کاربر کلاس باید نام کاربری و رمز عبور مدیر پایگاه داده مقاله را بداند، تنها خود مدیر می‌تواند از این کلاس استفاده کند. بنابراین عملیات قبلی دیگر برای او فایده‌ای ندارد. این کلاس صرفاً برای تسهیل توسعه وجود دارد. پیامد مهم این امر آن است که نیازی به اتخاذ هیچ‌گونه اقدامات احتیاطی امنیتی نیست. بار دیگر، هر کسی که از کلاس articles استفاده می‌کند، لزوماً مدیر پایگاه داده مقالات است.

  • این کلاس خطاهای اتصال به پایگاه داده یا هر خطای دیگری را به صورت یکنواخت با پر کردن ویژگی $aErreurs با پیام(های) خطا مدیریت می‌کند. بنابراین، کاربر کلاس پس از هر عملیات باید این لیست را بررسی کند.
  • متدهای addArticle، updateArticle، deleteArticle، selectArticles و execute مستقیماً از ماکت رابط کاربری وب که قبلاً ارائه شده است، استخراج شده‌اند. این متدها با گزینه‌های موجود در منوی ارائه‌شده مطابقت دارند. متدهای addArticle و modifyArticle برای بررسی اینکه مقاله مورد نظر برای افزودن یا ویرایش، حاوی داده‌های صحیح است، به متد vérifierArticle متکی هستند. به همین ترتیب، متد existeArticle بررسی می‌کند که در حال افزودن موردی نباشید که از قبل وجود دارد. اگر از جدولی برای مقالات استفاده شود که کد، کلید اصلی آن باشد، می‌توان از این روش صرف‌نظر کرد. در این صورت، خود SGBD افزودن را به دلیل تکراری بودن، ناموفق علامت‌گذاری می‌کند. این کار احتمالاً با یک پیام خطا که خواندن آن دشوار است و به زبان انگلیسی است، انجام می‌شود.
  • موردی که قرار است ویرایش یا حذف شود، با کد منحصربه‌فرد خود شناسایی می‌شود. متد getCodes همه این کدها را بازیابی می‌کند.
  • متد `disconnect` اتصال به پایگاه داده را که هنگام ایجاد شی باز شده بود، قطع می‌کند. هدف متد `connect`—که اتصال به پایگاه داده را دوباره برقرار می‌کند—در اینجا بلافاصله واضح نیست. این امکان را می‌دهد که اتصال را با استفاده از همان شی به دلخواه باز و بسته کرد. کاربرد آن تنها در کنار برنامه وب آشکار می‌شود. برنامه یک شی `articles` ایجاد کرده و آن را در یک جلسه (session) ذخیره می‌کند. در حالی که جلسه (session) قادر است بیشتر ویژگی‌های شیء را در طول تبادلات متوالی کلاینت-سرور حفظ کند، اما نمی‌تواند ویژگی‌ای را که نمایانگر اتصال باز است، حفظ نماید. بنابراین، اتصال باید در هر تبادلات جدید کلاینت-سرور مجدداً باز شود. یک اتصال پایدار درخواست می‌شود تا اتصال باز در یک استخر اتصال ذخیره شده و به طور دائم باز باقی بماند. بنابراین، هنگامی که اسکریپت یک اتصال جدید را درخواست می‌کند، آن از استخر اتصال بازیابی خواهد شد. این کار همان نتیجه‌ای را به دست می‌دهد که گویی جلسه می‌توانست اتصال باز را ذخیره کند.
  • روش existeUser به سازنده امکان می‌دهد تا تعیین کند آیا کاربر $sUser که با رمز عبور $sMdp شناسایی شده است، واقعاً وجود دارد یا خیر. در این صورت، این روش تعیین می‌کند که آیا کاربر مدیر است یا خیر (همان‌طور که در جدول USERS مشخص شده) و این اطلاعات را در ویژگی $bAdmin ذخیره می‌کند. اگر کاربر مدیر نباشد، این متد مجوزهای او را از جدول DROITS بازیابی کرده و در ویژگی $dDroits ذخیره می‌کند که یک دیکشنری با دو شاخص است: $dDroits[$table][$droit] است 'اگر کاربر $sUser مجوز $droit را روی جدول $table داشته باشد، مقدار 'y' و در غیر این صورت مقدار 'n' را برمی‌گرداند.

کلاس articles را بنویسید. دسترسی به پایگاه داده با استفاده از کتابخانه </mark>[<u><span style="color: #0563c1">PEAR::DB](https://pear.php.net/package/DB/) انجام می‌شود که امکان نادیده گرفتن نوع دقیق پایگاه داده را فراهم می‌کند.

7.5. ساختار برنامه WEB

اکنون که کلاس «بیزینس» برای مدیریت پایگاه داده محصولات را داریم، می‌توانیم آن را در محیط‌های مختلف به کار ببریم. در اینجا پیشنهاد می‌کنیم آن را در یک برنامه وب استفاده کنیم. بیایید این موضوع را از طریق صفحات زیر بررسی کنیم:

7.5.1. صفحهٔ استاندارد برنامه

بیایید به صفحه اصلی که قبلاً مشاهده کرده‌ایم بازگردیم:

1234

Image

تمام صفحات در این برنامه دارای ساختار نشان داده شده در بالا خواهند بود: یک جدول با دو سطر و سه ستون که شامل چهار فیلد است:

  • منطقه ۱ ردیف اول جدول را تشکیل می‌دهد. این بخش برای عنوان، که ممکن است با یک تصویر همراه باشد، در نظر گرفته شده است. سه ستون در این ردیف در اینجا ادغام شده‌اند.
  • ردیف دوم سه بخش دارد، یکی برای هر ستون:
    • بخش ۲ حاوی گزینه‌های منو است. این بخش خود شامل یک جدول با یک ستون و چندین سطر است. گزینه‌های منو در سطرهای جدول قرار می‌گیرند.
    • منطقه ۳ خالی است و تنها برای جدا کردن مناطق ۲ و ۴ به کار می‌رود. می‌توانستیم رویکرد متفاوتی برای دستیابی به این جداسازی در پیش بگیریم.
    • منطقه ۴ حاوی بخش پویا صفحه است. این بخش است که از یک اقدام به اقدام بعدی تغییر می‌کند، در حالی که سایر بخش‌ها ثابت می‌مانند.

اسکریپت PHP که این صفحهٔ قالب را تولید می‌کند، به نام main.php نام‌گذاری خواهد شد و ممکن است به شکل زیر باشد:


<html>
  <head>
      <title>Gestion d'articles</title>
      <link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />
   </head>
  <body background="<?php echo $dConfig['urlBackGround'] ?>">
    <table>
      <tr height="60">
        <td colspan="3" align="left" valign="top" >
          <h1><?php echo $main["title"] ?></h1>
        </td>
      </tr>
      <tr>
        <td>
          <table>
            <tr>
              <td class="menutitle" >
                                    <a href="<?php echo  $main["liens"]["login"] ?>" ?>Authentification</a>
              </td>
            </tr>
            <tr>
                <td><br /></td>
            </tr>
            <tr>
              <td class="menutitle" >
                Utilisation
              </td>
            </tr>
            <tr height="10"></tr>
            <tr>
              <td class="menublock" >
                <img alt="-" src="../images/radio.gif" />
                <a href="<?php echo  $main["liens"]["addArticle"] ?>" ?>
                  Ajouter un article
                </a>
                 </td>
            </tr>
            <tr>
              <td class="menublock" >                    
                <img alt="-" src="../images/radio.gif" />
                <a href="<?php echo $main["liens"]["updateArticle"] ?>">
                  Modifier un article
                </a>
                    </td>
            </tr>
              <td class="menublock" >                
                <img alt="-" src="../images/radio.gif" />
                <a href="<?php echo $main["liens"]["deleteArticle"] ?>">
                  Supprimer un article
                </a>
              </td>
            </tr>
            <tr>
              <td class="menublock" >
                <img alt="-" src="../images/radio.gif" />
                <a href="<?php echo $main["liens"]["selectArticle"] ?>">
                  Lister des articles
                </a>
              </td>
            </tr>
            <tr>
                <td><br /></td>
            </tr>                
            <tr>
              <td class="menutitle" >
                Administration
              </td>
            </tr>
            <tr height="10"></tr>
            <tr>
              <td class="menublock" >                
                <img alt="-" src="../images/radio.gif" />
                <a href="<?php echo $main["liens"]["sql"] ?>" >
                  Requête SQL
                </a>
              </td>
            </tr>
          </table>
        </td>
            <td>  
            <img alt="/" src="../images/pix.gif" width="10" height="1" />
            </td>
            <td>
            <fieldset>
              <legend><?php echo $main["légende"] ?></legend>
            <?php
                include $main["contenu"];
            ?>
          </fieldset>
        </td>
      </tr>
    </table>
  </body>
</html>

بخش‌های پیکربندی‌شدهٔ صفحه در فهرست بالا برجسته شده‌اند. صفحهٔ قالب به چندین روش پیکربندی شده است:

  • از طریق یک فرهنگ لغت $main با کلیدهای زیر:
    • title: عنوانی که باید در ناحیه ۱ صفحه قرار گیرد
    • links: فرهنگ‌های لینک‌هایی که باید در ستون منو تولید شوند. این لینک‌ها با گزینه‌های منو در ناحیه ۲ مرتبط هستند
    • content: URL صفحه‌ای که باید در ناحیه ۴ نمایش داده شود
  • توسط یک فرهنگ لغت $dConfig که حاوی اطلاعاتی است که از یک فایل پیکربندی برنامه به نام config.php گرفته شده است
  • از طریق کلاس‌هایی که بخشی از شیوه‌نامه‌ای هستند که توسط صفحه استفاده می‌شود:
      <link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />

این صفحه در اینجا از کلاس‌های سبک زیر استفاده می‌کند:

  • menutitle: برای یک گزینهٔ منوی اصلی
  • menublock: برای یک گزینه منوی ثانویه

تغییر هر یک از پارامترها ظاهر صفحه را تغییر می‌دهد. برای مثال، تغییر $main['title'] عنوان بخش 1 را تغییر خواهد داد.

7.5.2. پردازش استاندارد درخواست مشتری

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

apparticles.php?action=xx&phase=y&PHPSESSID=zzzzzzzzzzzz
action
اقدام فعلی را از میان موارد زیر نشان می‌دهد:
authentifier
احراز هویت مشتری
selectArticles
انتخاب آیتم (مشاهده)
updateArticle
ویرایش یک آیتم
deleteArticle
حذف یک آیتم
sql
ارسال هر درخواست SQL (مدیر)
phase
یک اقدام ممکن است در چند مرحله انجام شود – نشان‌دهنده مرحلهٔ فعلی است
PHPSESSID
توکن جلسه در شروع جلسه – به سرور امکان می‌دهد اطلاعات ذخیره‌شده در جلسه در طول تعاملات قبلی را بازیابی کند

به همین ترتیب، ویژگی «action» در فرم‌ها نیز به همین شکل خواهد بود. برای مثال، در صفحهٔ اصلی یک فرم ورود در بخش ۴ وجود دارد. تگ HTML برای این فرم به صورت زیر تعریف شده است:

<form name="frmLogin" method="post" action="apparticles.php?action=authentifier&phase=1">

درخواست کلاینت توسط اسکریپت اصلی برنامه به نام apparticles.php پردازش می‌شود. نقش آن ساخت پاسخ برای کلاینت است. این اسکریپت همیشه به یک شکل عمل می‌کند:

  • بر اساس نام اقدام و فاز فعلی، درخواست به یک تابع تخصصی واگذار می‌شود. این تابع درخواست را پردازش کرده و صفحه پاسخ مناسب را تولید می‌کند. برای هر درخواست مشتری ممکن است چندین صفحه پاسخ احتمالی وجود داشته باشد: page1، page2، …، pagen. این صفحات حاوی اطلاعاتی هستند که باید توسط تابع محاسبه شوند. بنابراین این‌ها صفحات پارامتریک هستند. این صفحات توسط اسکریپت‌های page1.php، page2.php، …، pagen.php تولید خواهند شد.
  • برای حفظ یکپارچگی، بخش‌های متغیر صفحات که باید در ناحیه ۴ صفحه الگو نمایش داده شوند نیز در فرهنگ لغت $main قرار داده خواهند شد.

فرض کنید که در پاسخ به یک درخواست، سرور نیاز دارد صفحه pagex.php را برای کلاینت ارسال کند. آن به شرح زیر عمل خواهد کرد:

  • مقادیر مورد نیاز برای صفحه pagex.php را در فرهنگ لغت $main قرار خواهد داد
  • آن $main را با ['contenu'] قرار می‌دهد که به URL صفحه نمایش داده شده در ناحیه ۴ صفحه قالب اشاره دارد، URL از pagex.php
  • این با استفاده از دستور نمایش صفحهٔ الگو را درخواست خواهد کرد
include "main.php";

سپس صفحهٔ الگو با کد اسکریپت pagex.php در ناحیهٔ ۴ نمایش داده می‌شود که برای تولید محتوای ناحیهٔ ۴ ارزیابی خواهد شد. باید توجه داشت که این صرفاً یک سلول در یک جدول است. بنابراین کد HTML که توسط pagex.php تولید می‌شود، نباید با تگ‌های <HTML>, <HEAD>, شروع شود. <BODY>, … این تگ‌ها قبلاً در ابتدای صفحهٔ قالب صادر شده‌اند. برای مثال، در اینجا چیزی که اسکریپت login.php، که بخش ۴ صفحهٔ اصلی را تولید می‌کند، ممکن است به این صورت باشد:


<form name="frmLogin" method="post" action="<?php echo $main["post"] ?>">
    <table>
        <tr>
            <td>login</td>
            <td><input type="text" value="<?php echo $main["login"] ?>" name="txtLogin" class="text"></td>
        </tr>
        <tr>
            <td>mot de passe</td>
            <td><input type="password" value="" name="txtMdp" class="text"></td>
      <td><input type="submit" value="Connexion" class="submit"></td>      
        </tr>
    </table>
</form>

می‌توانیم ببینیم که صفحه:

  • به شکلی تقلیل یافته است
  • هم توسط فرهنگ لغت $main و هم توسط صفحهٔ سبک پیکربندی می‌شود.

7.5.3. فایل پیکربندی

همیشه توصیه می‌شود برنامه‌ها را تا حد امکان پیکربندی کنید تا مجبور نشوید صرفاً به این دلیل که، برای مثال، تصمیم گرفته‌اید مسیر یک اسکریپت یا یک تصویر را تغییر دهید، کد را اصلاح کنید. بنابراین، برنامه اصلی apparticles.php هنگام راه‌اندازی، یک فایل پیکربندی config.php را بارگذاری خواهد کرد:

     // بارگذاری فایل پیکربندی
  include "config.php";

این فایل حاوی دستورات پیکربندی برای PHP و مقداردهی اولیه متغیرهای سراسری خواهد بود:

<?php

     //پیکربندی PHP
  ini_set("register_globals","off");
  ini_set("display_errors","off");
  ini_set("expose_php","off");
    ini_set("session.use_cookies","0");    //بدون کوکی‌ها

     // پیکربندی پایه محصول
    $dConfig["DSN"]=array(
        "sgbd"=>"mysql",
        "admin"=>"admarticles",
        "mdpadmin"=>"mdparticles",
        "host"=>"localhost",
        "database"=>"dbarticles"
    );

   // URLهای صفحات
    $dConfig['urlBackGround']="../images/standard.jpg";  
  $dConfig["urlPageStyle"]="mystyle.css";  
  $dConfig["urlAppArticles"]="apparticles.php";
  $dConfig["urlPageMain"]="main.php";
  $dConfig["urlPageLogin"]="login.php";
  $dConfig["urlPageErreurs"]="erreurs.php";
  $dConfig["urlPageInfos"]="infos.php";
  $dConfig["urlPageAddArticle"]="addarticle.php";
  $dConfig["urlPageUpdateArticle1"]="updatearticle1.php";
  $dConfig["urlPageUpdateArticle2"]="updatearticle2.php";
  $dConfig["urlPageDeleteArticle1"]="deletearticle1.php";
  $dConfig["urlPageDeleteArticle2"]="deletearticle2.php";
  $dConfig["urlPageSelectArticle1"]="selectarticle1.php";
  $dConfig["urlPageSelectArticle2"]="selectarticle2.php";
  $dConfig["urlPageSQL1"]="sql1.php";
  $dConfig["urlPageSQL2"]="sql2.php";
  $dConfig["urlPageSQL3"]="sql3.php";

   // پیوندها در صفحهٔ اصلی
  $main["liens"]["login"]="$sUrlAppArticles?action=authentifier&phase=0";  
  $main["liens"]["addArticle"]="$sUrlAppArticles?action=addArticle&phase=0";
  $main["liens"]["updateArticle"]="$sUrlAppArticles?action=updateArticle&phase=0";
  $main["liens"]["deleteArticle"]="$sUrlAppArticles?action=deleteArticle&phase=0";
  $main["liens"]["selectArticle"]="$sUrlAppArticles?action=selectArticle&phase=0";
  $main["liens"]["sql"]="$sUrlAppArticles?action=sql&phase=0";

   // $main در پیکربندی ذخیره شده است
  $dConfig["main"]=$main;    
?>

7.5.4. فایل سبک مرتبط با صفحهٔ قالب

ما دیده‌ایم که پاسخ سرور فرمت منحصربه‌فردی دارد: main.php. ممکن است متوجه شده باشید که این اسکریپت صفحه‌ای ساده تولید می‌کند که فاقد هرگونه افکت نمایشی است. این امر به دلایل متعددی مفید است:

  • توسعه‌دهنده نیازی ندارد نگران ارائهٔ بصری صفحه‌ای باشد که در حال ساخت آن است. در واقع، آن‌ها لزوماً مهارت‌های لازم برای ایجاد صفحات جذاب از نظر بصری را ندارند. در اینجا، می‌توانند کاملاً بر روی کد تمرکز کنند.
  • نگهداری اسکریپت آسان‌تر می‌شود. اگر اسکریپت‌ها شامل ویژگی‌های نمایشی بودند، نه ساختار کد و نه ساختار نمایش به وضوح مشخص نمی‌شد. طراحی بصری صفحات اغلب به یک طراح گرافیک واگذار می‌شود. آن‌ها احتمالاً از اینکه مجبور باشند برای پیدا کردن ویژگی‌های نمایشی مورد نیاز برای تغییر، در اسکریپتی که درک نمی‌کنند جستجو کنند، استقبال نخواهند کرد.

با این حال، توجه به ظاهر بصری صفحات مهم است. در نهایت، این ظاهر است که کاربران وب را به یک سایت جذب می‌کند. در اینجا، ارائه به یک شیوه‌نامه واگذار می‌شود. صفحه main.php در کد خود شیوه‌نامه‌ای را که باید برای نمایش آن استفاده شود، مشخص می‌کند:

  <head>
      <title>Gestion d'articles</title>
      <link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />
   </head>

فایل سبک مورد استفاده در این سند به شرح زیر است:

BODY {
    background : url(../images/standard.jpg);
    border : 2px none #FFDAB9;
    font-family : Garamond;
    font-size : 16px;
    margin-left : 0px;
    padding-left : 20px;
}

INPUT {
    background : #EEE8AA;
    border : 1px solid #EE82EE;
    font-family : Garamond;
    font-size : 18px;
}

INPUT.submit{
    font-family : "Times New Roman";
    font-size : 16px;
    background : #FA8072;
    border : 2px double Green;
    font-weight : bold;
    text-align : center;
    vertical-align : middle;
    cursor : pointer;
}

TD.menutitle{
    background-image : url(../images/menugelgd.gif);
    height : 23px;
    text-align : center;
    vertical-align : middle;
    background : url(../images/menugelgd.gif) no-repeat center;
}

TD.menublock{
    background : url(../images/bandegrismenugd.gif) repeat-x;
    text-align : left;
    vertical-align : middle;
}

A {
    font-family : "Comic Sans MS";
    color : #FF7F50;
    font-size : 15px;
    text-decoration : none;
}

A:HOVER {
    background : #FFA07A;
    color : Red;
}

FIELDSET {
    border : 1px solid #A0522D;
    background : #FFE4C4;
    margin : 10px 10px 10px 10px;
    padding-left : 10px;
    padding-right : 10px;
    padding-bottom : 10px;
}

LEGEND{
    background : #FFA500;
}

TH {
    background : #228B22;
    text-align : center;
    vertical-align : middle;
}

TD.libellé{
    border : 1px solid #008B8B;
    color : #339966;
}

H1 {
    font : bold 20px/30px Garamond;
    color : #FF7F50;
    background : #D1E1F8;
    background-attachment : fixed;
    text-align : center;
    vertical-align : middle;
    font-family : Garamond;
}

SELECT.TEXT {
    background : #6495ED;
    text-align : center;
    color : Aqua;
}

ما وارد جزئیات این شیوه‌نامه نمی‌شویم. آن را همان‌طور که هست می‌پذیریم. کمی بعد، خواهیم دید که چگونه آن را ایجاد و ویرایش کنیم. نرم‌افزارهایی برای این منظور در دسترس هستند. با این حال، بیایید نقش ویژگی‌های ارائه مورد استفاده در شیوه‌نامه را تشریح کنیم:

ویژگی:
کنترل می‌کند نحوه نمایش تگ HTML:
BODY
<BODY>
H1
<H1> (سربرگ۱)
A
<A> (لنگر)
A:HOVER
ویژگی‌های نمایش لنگر را زمانی که کاربر ماوس را روی آن قرار می‌دهد، تنظیم می‌کند
FIELDSET
<FIELDSET> – این تگ توسط همه مرورگرها شناسایی نمی‌شود
LEGEND
<LEGEND> – این تگ توسط همه مرورگرها شناسایی نمی‌شود
INPUT
<INPUT>
INPUT.TEXT
<INPUT class="TEXT">
INPUT.SUBMIT
<INPUT class="SUBMIT">
TH
<TH> (سربرگ جدول)
TD.menutitle
<TD class="menutitle"> (داده‌های جدول)
TD.menublock
<TD class="menublock">
TD.libellé
<TD class="label">

بیایید به یک مثال نگاه کنیم تا ببینیم این قواعد قالب‌بندی چگونه می‌توانند نوشته شوند. در این مثال، از نرم‌افزار TopStyle Lite استفاده خواهیم کرد که به‌صورت رایگان در http://www.bradsoft.com در دسترس است. پس از بارگذاری شیوه‌نامه، پنجره‌ای با سه بخش ظاهر می‌شود:

  1. یک ناحیه ویرایش متن. ویژگی‌های قالب‌بندی را می‌توان به‌صورت دستی تعریف کرد، مشروط بر اینکه با قوانین نوشتن صفحات سبک که از استانداردی به نام CSS (صفحات سبک آبشاری) پیروی می‌کنند، آشنا باشید.
  2. منطقه ۲ ویژگی‌های قابل ویرایش صفتی را که در حال ایجاد است نمایش می‌دهد. این ساده‌ترین روش است. این روش نیاز به دانستن نام‌های دقیق صفت‌های ارائه را که تعداد زیادی از آن‌ها وجود دارد، از بین می‌برد.
  3. منطقه ۳ ظاهر بصری صفتی را که در حال ایجاد است نشان می‌دهد

در ناحیه ۱ بالا، ویژگی INPUT.submit را کپی و در یک ویژگی INPUT.fantaisie جای‌گذاری کنید. این ویژگی، نحوه نمایش تگ HTML <INPUT class="fantaisie"> را تعیین می‌کند.

بیایید از منطقه ۲ برای تغییر برخی از ویژگی‌های صفت INPUT.fantaisie استفاده کنیم:

از این پس، هر تگ <INPUT ... class="fantaisie"> که در صفحه‌ای مرتبط با استایل‌شیت قبلی یافت شود، همان‌طور که در مثال بخش ۳ فوق نشان داده شده است، نمایش داده خواهد شد.

صفحه‌آرایی‌ها فوق‌العاده مفید هستند. استفاده از آن‌ها به شما امکان می‌دهد ظاهر یک برنامه وب را تنها با تغییر یک چیز: صفحه‌آرایی آن، تغییر دهید. صفحه‌آرایی‌ها توسط مرورگرهای قدیمی‌تر شناسایی نمی‌شوند. دستور <link ..> زیر توسط برخی از آن‌ها نادیده گرفته خواهد شد:

  <head>
      <title>Gestion d'articles</title>
      <link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />
   </head>

در برنامه ما، این کار به صفحه اصلی زیر منجر می‌شود:

Image

این یک صفحه حداقلی بدون هیچ عنصر گرافیکی است. اوضاع می‌توانست بدتر باشد. برخی نسخه‌های مرورگر، صفحات سبک (style sheets) را تشخیص می‌دهند اما آن‌ها را به اشتباه تفسیر می‌کنند. این امر می‌تواند منجر به یک صفحه تحریف‌شده و غیرقابل استفاده شود. این موضوع، مسئله نوع مرورگر کلاینت را مطرح می‌کند. تکنیک‌هایی برای تعیین نوع مرورگر کلاینت وجود دارد، اما آن‌ها کاملاً قابل اعتماد نیستند. بنابراین می‌توان برای مرورگرهای مختلف، صفحات سبک (style sheets) متفاوتی نوشت، یا حتی نسخه‌ای بدون صفحه سبک برای مرورگرهایی که آن‌ها را نادیده می‌گیرند، تهیه کرد. این کار، البته، وظیفه توسعه را پرزحمت‌تر می‌کند. این مشکل مهم در اینجا نادیده گرفته شده است.

با استفاده از صفحات سبک، می‌توانیم محیطی شخصی‌سازی‌شده برای کاربران برنامه‌مان فراهم کنیم. می‌توانیم صفحه‌ای را به آن‌ها ارائه دهیم که چندین سبک چیدمان ممکن را نمایش می‌دهد. آن‌ها می‌توانند مناسب‌ترین سبک را انتخاب کنند. این انتخاب می‌تواند در یک پایگاه داده ذخیره شود. وقتی کاربر دوباره وارد می‌شود، می‌توانیم برنامه را با صفحه‌ی سبک انتخابی او راه‌اندازی کنیم.

7.5.5. ماژول ورودی برنامه

کلاینت‌ها تنها با ماژول ورودی برنامه: apparticles.php آشنا خواهند بود. طرح کلی نحوه عملکرد آن به شرح زیر است:

  • درخواست مشتری بازیابی و تحلیل می‌شود. ممکن است پیکربندی شده باشد یا نباشد. هنگامی که پارامترگذاری می‌شود، پارامترهای مورد انتظار به شرح زیر است: action=[action]&phase=[phase]&PHPSESSID=[PHPSESSID]
  • اگر درخواست پیکربندی نشده باشد یا پارامترهای دریافتی با موارد مورد انتظار مطابقت نداشته باشند، سرور صفحه احراز هویت (نام کاربری، رمز عبور) را به عنوان پاسخ بازمی‌گرداند. پس از ورود موفقیت‌آمیز کاربر، یک جلسه (session) ایجاد می‌شود. از این جلسه برای ذخیره اطلاعات در طول تعاملات کلاینت-سرور استفاده می‌شود.
  • اگر یک درخواست به درستی شناسایی شود، توسط ماژولی که به هر دو عمل و مرحله فعلی بستگی دارد، پردازش می‌شود.
  • تمام دسترسی‌ها به پایگاه داده از طریق کلاس کسب‌وکار articles.php انجام می‌شود.
  • پردازش یک درخواست همیشه با ارسال صفحه main.php به کلاینت به پایان می‌رسد؛URL مربوط به صفحه‌ای که باید در فیلد ۴ صفحهٔ قالب قرار گیرد.

ساختار پایه اسکریپت apparticles.php می‌تواند به شرح زیر باشد:

<?php
     // مدیریت جدول آیتم
  include "config.php";
  include "articles.php";  

  // اقدامی که باید انجام شود
  $sAction=$_POST["action"] ? $_POST["action"] : $_GET["action"] ? $_GET["action"] : "authentifier";
  $sAction=strtolower($sAction);
   // مرحلهٔ ممکن
  $sPhase=$_POST["phase"] ? $_POST["phase"] : $_GET["phase"] ? $_GET["phase"] : "0";

  // جلسه
  session_start();
  $dSession=$_SESSION["session"];

     //آیا جلسه‌ای در حال اجراست؟
  if(! isset($dSession)){
      // احراز هویت کاربر
    if($sAction=="authentifier" && $sPhase=="0") authentifier_0($dConfig);
    if($sAction=="authentifier" && $sPhase=="1") authentifier_1($dConfig);
    if($sAction=="authentifier" && $sPhase=="2") authentifier_2($dConfig);
     // درخواست نامعتبر
    authentifier_0($dConfig);        
  }//اگر – هیچ جلسه‌ای وجود ندارد

     // بازیابی جلسه
  $dSession=unserialize($dSession);

     // در حال پردازش درخواست
     // ----- احراز هویت
  if($sAction=="authentifier" && $sPhase=="0") authentifier_0($dConfig);
  if($sAction=="authentifier" && $sPhase=="1") authentifier_1($dConfig);
  if($sAction=="authentifier" && $sPhase=="2") authentifier_2($dConfig);  
     // ----- افزودن یک آیتم
  if($sAction=="addarticle" && $sPhase=="0") addArticle_0($dConfig,$dSession);
  if($sAction=="addarticle" && $sPhase=="1") addArticle_1($dConfig,$dSession);
  if($sAction=="addarticle" && $sPhase=="2") addArticle_2($dConfig,$dSession);
     // ----- به‌روزرسانی آیتم
  if($sAction=="updatearticle" && $sPhase=="0") updateArticle_0($dConfig,$dSession);
  if($sAction=="updatearticle" && $sPhase=="1") updateArticle_1($dConfig,$dSession);
  if($sAction=="updatearticle" && $sPhase=="2") updateArticle_2($dConfig,$dSession);
  if($sAction=="updatearticle" && $sPhase=="3") updateArticle_3($dConfig,$dSession);
     //---- حذف یک آیتم
  if($sAction=="deletearticle" && $sPhase=="0") deleteArticle_0($dConfig,$dSession);
  if($sAction=="deletearticle" && $sPhase=="1") deleteArticle_1($dConfig,$dSession);
  if($sAction=="deletearticle" && $sPhase=="2") deleteArticle_2($dConfig,$dSession);
    // ----- مشاهده آیتم‌ها
  if($sAction=="selectarticle" && $sPhase=="0") selectArticle_0($dConfig,$dSession);
  if($sAction=="selectarticle" && $sPhase=="1") selectArticle_1($dConfig,$dSession);
  if($sAction=="selectarticle" && $sPhase=="2") selectArticle_2($dConfig,$dSession);
     // ----- ارسال یک درخواست SQL
  if($sAction=="sql" && $sPhase=="0") sql_0($dConfig,$dSession);
  if($sAction=="sql" && $sPhase=="1") sql_1($dConfig,$dSession);
  if($sAction=="sql" && $sPhase=="2") sql_2($dConfig,$dSession);


    //اقدام نامعتبر – صفحه ورود نمایش داده می‌شود
  session_destroy();
  authentifier_0($dConfig,"0");
...
?>

لطفاً به نکات زیر توجه کنید:

  • توابعی که به یک درخواست خاص مشتری رسیدگی می‌کنند، با تولید صفحه پاسخ و با یک دستور خروج که اجرای اسکریپت apparticles.php را متوقف می‌کند، پایان می‌یابند. به عبارت دیگر، هیچ «بازگشت»ی از این توابع وجود ندارد.
  • این توابع یک یا دو پارامتر را می‌پذیرند:
    • $dConfig یک فرهنگ لغت حاوی اطلاعات از فایل پیکربندی config.php است. همه توابع از آن استفاده می‌کنند.
    • $dSession یک دیکشنری حاوی اطلاعات جلسه است. این دیکشنری تنها پس از ایجاد جلسه، یعنی پس از احراز هویت موفقیت‌آمیز کاربر، وجود دارد. به همین دلیل، توابع احراز هویت این پارامتر را ندارند.

7.5.6. صفحه خطا

هر نرم‌افزار باید بتواند هرگونه خطایی را که ممکن است رخ دهد، به درستی مدیریت کند. یک اپلیکیشن وب نیز از این قاعده مستثنی نیست. در اینجا، در صورت بروز خطا، صفحه زیر، erreurs.php، را در ناحیه ۴ صفحه قالب قرار خواهیم داد:

Les erreurs suivantes se sont produites :
<ul>
    <?php
        for($i=0;$i<count($main["erreurs"]);$i++){
            echo "<li>".$main["erreurs"][$i]."</li>\n";
        }//برای
    ?>
</ul>
<a href="<?php echo $main["href"] ?>"><?php echo $main["lien"] ?></a>

این لینک فهرست خطاهای تعریف‌شده در $main['erreurs'] را نمایش می‌دهد. همچنین ممکن است یک لینک بازگشت، معمولاً به صفحه‌ای که قبل از صفحهٔ خطا بوده است، ارائه کند. این لینک توسط یک برچسب $main['lien'] و یک URL $main['href'] تعریف می‌شود. برای جلوگیری از نمایش این لینک، کافی است یک رشتهٔ خالی را در $main['lien'] وارد کنید. در ادامه مثالی از صفحهٔ خطا در صورتی که کاربر به اشتباه وارد شود، آورده شده است:

Image

7.5.7. صفحه اطلاعات

گاهی ممکن است بخواهید یک اطلاعات ساده به کاربر ارائه دهید، برای مثال اینکه با موفقیت وارد سیستم شده است. برای این کار، از صفحه زیر استفاده کنید: infos.php:

<?php echo $main["infos"] ?>

برای نمایش اطلاعات در پاسخ به یک درخواست کلاینت، ما

  • اطلاعات را در $main['infos'] قرار دهید
  • و URL را از infos.php در $main['contenu'] قرار دهید

در اینجا، به‌عنوان مثال، اطلاعاتی که هنگام ورود موفقیت‌آمیز کاربر بازگردانده می‌شود، آورده شده است:

Image

7.6. نحوه کار برنامه

اکنون ما یک درک خوب از ساختار کلی برنامه‌ای که باید نوشته شود داریم. ما همچنان باید مسیر کاربر را در سراسر برنامه، اقداماتی که می‌تواند انجام دهد و پاسخ‌هایی که از سرور دریافت می‌کند، مشخص کنیم. پس از انجام این کار، قادر خواهیم بود توابعی را که با درخواست‌های مختلف از سوی کلاینت سروکار دارند، بنویسیم. در ادامه، با بررسی صفحات نمایش داده شده به کاربر در پاسخ به برخی از این اقدامات، نحوه عملکرد برنامه را تشریح خواهیم کرد. در هر مورد، نکات زیر را مشخص خواهیم کرد:

action utilisateur
اقدام اولیه کاربر که منجر به پاسخ نمایش داده شده شده است
paramètres envoyés
پارامترهای ارسال‌شده توسط مرورگر کلاینت به سرور در پاسخ به اقدام دستی کاربر
page réponse
اسکریپتی که بخش ۴ صفحهٔ الگو را تولید می‌کند

7.6.1. احراز هویت

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

Image

action utilisateur
۱ – درخواست اولیه برای URL apparticles.php
۲ – استفاده از گزینه «احراز هویت» در منو
۳ - درخواست مستقیم برای URL articles.php با پارامترهای نادرست
paramètres envoyés
۱ - بدون پارامتر
2 - action=authenticate?phase=0
۳ - فهرستی از پارامترهای نادرست
page réponse
login.php

در صفحهٔ اصلی، لینک [Ajouter un article] شکل زیر را دارد: action=addarticle?phase=0. سایر لینک‌ها نیز همین شکل را دارند، با action= (authenticate, updatearticle, deletearticle, selectarticle, sql). کاربر فرم را پر می‌کند و دکمهٔ [Connexion] را کلیک می‌کند:

Image

پاسخ به شرح زیر است:

Image

action utilisateur
bouton [Connexion]
paramètres envoyés
action=authenticate?phase=1
page réponse
infos.php

عنوان صفحه تغییر یافته تا ورود کاربر و حقوق مدیر/کاربر او نمایش داده شود. علاوه بر این، تمام لینک‌های ناحیه ۲ اصلاح شده‌اند تا نشان دهند یک جلسه آغاز شده است. پارامتر PHPSESSID=[PHPSESSID] به آن‌ها اضافه شده است.

اگر سرور نتواند مشتری را شناسایی کند، مشتری پاسخ متفاوتی دریافت خواهد کرد:

Image

action utilisateur
bouton [Connexion]
paramètres envoyés
action=authenticate?phase=1
page réponse
erreurs.php

لینک [Retour à la page de login] یک لینک به URL apparticles.php?action=authenticate&phase=2&txtLogin=x است. این لینک مشتری را به صفحهٔ ورود بازمی‌گرداند، جایی که فیلد ورود با مقدار پارامتر txtLogin از پیش پر شده است:

Image

action utilisateur
lien [Retour à la page de login]
paramètres envoyés
action=authenticate?phase=2&txtLogin=x
page réponse
login.php

7.6.2. افزودن یک مقاله

لینک منو [Ajouter un article] شما را به صفحه زیر در بخش ۴ صفحهٔ قالب هدایت می‌کند:

Image

action utilisateur
lien [Ajouter un article]
paramètres envoyés
action=addArticle?phase=0&PHPSESSID=[PHPSESSID]
page réponse
addarticle.php

کاربر فیلدها را پر می‌کند و فرم را با استفاده از دکمه [Ajouter] که از نوع submit است، به سرور ارسال می‌کند. هیچ اعتبارسنجی در سمت کلاینت انجام نمی‌شود. این کار توسط سرور انجام می‌شود. سرور ممکن است در پاسخ یک صفحه خطا بازگرداند، همانطور که در مثال زیر نشان داده شده است:

درخواست
پاسخ
action utilisateur
bouton [Ajouter]
paramètres envoyés
action=addArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
erreurs.php

لینک [Retour à la page d'ajout d'article] شما را به صفحه ورود بازمی‌گرداند:

درخواست
پاسخ
action utilisateur
lien [Retour à la page d'ajout d'article]
paramètres envoyés
action=addArticle?phase=2&PHPSESSID=[PHPSESSID]
page réponse
article.php

اگر آیتم بدون هیچ خطایی اضافه شود، کاربر یک پیام تأیید دریافت می‌کند:

درخواست
پاسخ
action utilisateur
bouton [Ajouter]
paramètres envoyés
action=addArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
infos.php

7.6.3. مشاهده مقالات

لینک منو [Lister des articles] شما را به صفحه زیر در بخش ۴ صفحهٔ الگو هدایت می‌کند:

Image

action utilisateur
لینک منو [Lister des articles]
paramètres envoyés
action=selectArticle?phase=0&PHPSESSID=[PHPSESSID]
page réponse
select1.php

یک پرس‌وجوی SELECT [colonnes] FROM articles WHERE [where] ORDER BY [orderby] بر روی جدول articles صادر خواهد شد، که در آن [colonnes]، [where] و [orderby] مقادیر موجود در فیلدهای بالا هستند. برای مثال:

درخواست
پاسخ
action utilisateur
bouton [Afficher]
paramètres envoyés
action=selectArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
select2.php

درخواست ممکن است نادرست باشد، در این صورت صفحه خطا به مشتری نمایش داده می‌شود:

درخواست
پاسخ

در هر دو حالت (چه خطا وجود داشته باشد و چه نداشته باشد)، لینک [Retour à la page de sélection d'articles] شما را به صفحه select1.php بازمی‌گرداند:

درخواست
پاسخ
action utilisateur
lien [Retour à la page de sélection d'articles]
paramètres envoyés
action=selectArticle?phase=2&PHPSESSID=[PHPSESSID]
page réponse
select1.php

7.6.4. ویرایش مقالات

لینک منو [Modifier un article] شما را به صفحهٔ زیر در بخش ۴ صفحهٔ الگو هدایت می‌کند:

Image

action utilisateur
پیوند منو [Modifier un article]
paramètres envoyés
action=updateArticle?phase=0&PHPSESSID=[PHPSESSID]
page réponse
updatearticle1.php

کد مقاله مورد نظر برای ویرایش را از لیست کشویی انتخاب کرده و [OK] را برای ویرایش مقاله با آن کد وارد کنید:

درخواست
پاسخ
action utilisateur
bouton [OK]
paramètres envoyés
action=updateArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
updatearticle2.php

پس از بازیابی جزئیات مقاله مورد ویرایش، کاربر می‌تواند تغییرات خود را اعمال کند:

درخواست
پاسخ
action utilisateur
bouton [Modifier]
paramètres envoyés
action=updateArticle?phase=2&PHPSESSID=[PHPSESSID]
page réponse
infos.php

کاربر ممکن است هنگام ویرایش دچار اشتباه شود:

درخواست
پاسخ

لینک [Retour à la page de modification d'article] شما را به صفحه ورود بازمی‌گرداند:

Image

action utilisateur
lien [Retour à la page de modification d'article]
paramètres envoyés
action=updateArticle?phase=3&PHPSESSID=[PHPSESSID]
page réponse
updatearticle2.php

7.6.5. حذف یک مقاله

لینک منو [Supprimer un article] شما را به صفحه زیر در بخش ۴ صفحهٔ الگو هدایت می‌کند:

Image

action utilisateur
پیوند منو [Supprimer un article]
paramètres envoyés
action=deleteArticle?phase=0&PHPSESSID=[PHPSESSID]
page réponse
deletearticle1.php

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

درخواست
پاسخ
action utilisateur
bouton [OK]
paramètres envoyés
action=deleteArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
deletearticle2.php

کاربر با استفاده از دکمه [Supprimer] حذف مقاله را تأیید می‌کند:

درخواست
پاسخ
action utilisateur
bouton [Supprimer]
paramètres envoyés
action=deleteArticle?phase=2&PHPSESSID=[PHPSESSID]
page réponse
infos.php

7.6.6. ارسال‌های پرس‌وجوی مدیر

لینک منو [Requête SQL] شما را به صفحه زیر در بخش ۴ صفحهٔ الگو هدایت می‌کند:

Image

action utilisateur
پیوند منو [Requête SQL]
paramètres envoyés
action=sql?phase=0&PHPSESSID=[PHPSESSID]
page réponse
sql1.php

متن پرس‌وجوی SQL را در فیلد ورودی وارد کرده و برای اجرای آن از دکمه [Exécuter] استفاده کنید. تنها یک مدیر می‌تواند این پرس‌وجوها را اجرا کند، همان‌طور که در مثال زیر نشان داده شده است:

درخواست
پاسخ
action utilisateur
bouton [Exécuter]
paramètres envoyés
action=sql?phase=1&PHPSESSID=[PHPSESSID]
page réponse
erreurs.php

لینک [Retour à la page d'émission de requêtes SQL] شما را به صفحهٔ ورود بازمی‌گرداند:

Image

action utilisateur
lien [Retour à la page d'émission de requêtes SQL]
paramètres envoyés
action=sql?phase=2&PHPSESSID=[PHPSESSID]
page réponse
sql1.php

اگر شما مدیر هستید و پرس‌وجو از نظر نحوی صحیح است:

درخواست

شما نتیجه پرس‌وجو را دریافت می‌کنید:

پاسخ
action utilisateur
bouton [Exécuter]
paramètres envoyés
action=sql?phase=1&PHPSESSID=[PHPSESSID]
page réponse
sql2.php

می‌توانید پرس‌وجوهایی برای به‌روزرسانی جداول صادر کنید:

درخواست
پاسخ
action utilisateur
bouton [Exécuter]
paramètres envoyés
action=sql?phase=1&PHPSESSID=[PHPSESSID]
page réponse
infos.php

7.6.7. کارهای انجام‌دادنی

اسکریپت‌ها و توابع مورد نیاز برای برنامه را بنویسید:

نام کاربری
نوع
نقش
apparticles.php
اسکریپت
نقطه ورود برای پردازش درخواست‌های مشتری
authentifier_0
تابع
درخواست را با پارامترهای action=authenticate&phase=0 پردازش می‌کند
authentifier_1
تابع
درخواست را با پارامترهای action=authenticate&phase=1 پردازش می‌کند
authentifier_2
تابع
درخواست را با پارامترهای action=authenticate&phase=2 پردازش می‌کند
addarticle_0
تابع
درخواست را با پارامترهای action=addArticle&phase=0 پردازش می‌کند
addarticle_1
تابع
درخواست را با پارامترهای action=addArticle&phase=1 پردازش می‌کند
addarticle_2
تابع
درخواست را با پارامترهای action=addArticle&phase=2 پردازش می‌کند
updatearticle_0
تابع
درخواست را با پارامترهای action=updatearticle&phase=0 پردازش می‌کند
updatearticle_1
تابع
درخواست را با پارامترهای action=updatearticle&phase=1 پردازش می‌کند
updatearticle_2
تابع
درخواست را با پارامترهای action=updatearticle&phase=2 پردازش می‌کند
updatearticle_3
تابع
درخواست را با پارامترهای action=updatearticle&phase=3 پردازش می‌کند
deletearticle_0
تابع
درخواست را با پارامترهای action=deletearticle&phase=0 پردازش می‌کند
deletearticle_1
تابع
درخواست را با پارامترهای action=deletearticle&phase=1 پردازش می‌کند
deletearticle_2
تابع
درخواست را با پارامترهای action=deletearticle&phase=2 پردازش می‌کند
selectarticle_0
تابع
درخواست را با پارامترهای action=selectarticle&phase=0 پردازش می‌کند
selectarticle_1
تابع
درخواست را با پارامترهای action=selectarticle&phase=1 پردازش می‌کند
selectarticle_2
تابع
درخواست را با پارامترهای action=selectarticle&phase=2 پردازش می‌کند
sql_0
تابع
درخواست را با پارامترهای action=sql&phase=0 پردازش می‌کند
sql_1
تابع
درخواست را با پارامترهای action=sql&phase=1 پردازش می‌کند
sql_2
تابع
درخواست را با پارامترهای action=sql&phase=2 پردازش می‌کند
main.php
اسکریپت
صفحهٔ استاندارد را تولید می‌کند
login.php
اسکریپت
صفحه ورود را تولید می‌کند
erreurs.php
اسکریپت
صفحه خطا را تولید می‌کند
infos.php
اسکریپت
صفحه اطلاعات را تولید می‌کند
addarticle.php
اسکریپت
صفحه افزودن یک مقاله را تولید می‌کند
updatearticle1.php
اسکریپت
صفحه ۱ صفحه ویرایش مقاله را تولید می‌کند
updatearticle2.php
اسکریپت
تولید صفحه ۲ ویرایش مقاله
deletearticle1.php
اسکریپت
تولید صفحهٔ ۱ از حذف مقاله
deletearticle2.php
اسکریپت
تولید صفحه ۲ از حذف مقاله
select1.php
اسکریپت
تولید صفحه ۱ از انتخاب آیتم
select2.php
اسکریپت
صفحه ۲ از مجموعه مقالات را تولید می‌کند
sql1.php
اسکریپت
تولید صفحهٔ ۱ ارسال پرس‌وجو
sql2.php
اسکریپت
تولید صفحه ۲ خروجی پرس‌وجو

7.7. توسعهٔ برنامه

در این مرحله، ما یک برنامه‌ای داریم که کاری را که باید انجام دهد با قابلیت استفاده قابل قبول انجام می‌دهد. ما قصد داریم آن را در چندین حوزه بهبود دهیم:

  • SGBD
  • امنیت آن
  • ظاهر آن
  • عملکرد آن

7.7.1. تغییر نوع پایگاه داده

مطالعه ما فرض کرد که SGBD مورد استفاده، MySQL بود. آن را به SGBD تغییر دهید و نشان دهید که تنها تغییری که لازم است در تعریف متغیر $dDSN در فایل پیکربندی config.php انجام شود.

7.7.2. بهبود امنیت

هنگام توسعه یک برنامه وب، هرگز نباید فرض کرد که کلاینت یک مرورگر است و درخواستی که ارسال می‌کند تحت کنترل فرم ارسال‌شده به آن قبل از آن درخواست است. هر برنامه‌ای می‌تواند به‌عنوان کلاینت برای یک برنامه وب عمل کند و در نتیجه هر درخواستی – چه پارامتریک باشد چه نباشد – را به برنامه ارسال کند. بنابراین، برنامه باید همه چیز را تأیید کند.

اگر به کد اسکریپت apparticles.php نگاه کنیم، می‌توانیم ببینیم

  • که هیچ عملی به جز احراز هویت بدون جلسه (session) امکان‌پذیر نیست. یک جلسه تنها در صورتی وجود دارد که کاربر با موفقیت احراز هویت شده باشد. شایان ذکر است که یک جلسه با یک رشته نسبتاً طولانی از کاراکترها که به عنوان توکن جلسه شناخته می‌شود، شناسایی می‌شود که شکل زیر را دارد: 176a43609572907333118333edf6d1fb. این توکن را می‌توان به روش‌های مختلف برای برنامه ارسال کرد، برای مثال با استفاده از یک URL پیکربندی‌شده:

apparticles.php?PHPSESSID=176a43609572907333118333edf6d1fb. 

یک برنامه که با تغییر تصادفی توکن، بارها درخواست URL قبلی را می‌کند تا به ترکیب صحیح دست یابد، به دلیل تعداد بسیار زیاد ترکیب‌های ممکن، احتمالاً روزها طول می‌کشد تا ترکیب صحیح را تولید کند. تا آن زمان، از آنجایی که جلسه (session) محدودیت زمانی دارد، به احتمال زیاد منقضی شده است. خطر دیگر این است که توکن، در حالی که به صورت متن ساده از طریق شبکه منتقل می‌شود، ممکن است رهگیری شود. این یک خطر واقعی است. بنابراین می‌توان از یک اتصال رمزگذاری‌شده بین سرور و کلاینت آن استفاده کرد.

  • پس از شروع جلسه، تنها برخی از اقدامات مجاز هستند. توکنی مانند URL که با پارامترهای action=cheat&phase=0&PHPSESSID=[PHPSESSID] پیکربندی شده باشد، رد خواهد شد زیرا اقدام «cheat» یک اقدام مجاز نیست. وقتی پارامترها (action, phase) شناسایی نشوند، اپلیکیشن ما صفحه احراز هویت را نمایش می‌دهد.

با این حال، برنامه بررسی نمی‌کند که آیا اقدامات مجاز به درستی پشت سر هم انجام می‌شوند یا خیر. برای مثال، دو اقدام زیر:

  1. action=addArticle&phase=0&PHPSESSID=[PHPSESSID]
  2. action=updateArticle&phase=1&PHPSESSID=[PHPSESSID]

هر دو اقدام مجاز هستند. با این حال، اقدام ۲ مجاز نیست پس از اقدام ۱ انجام شود.

چگونه می‌توانیم توالی درخواست‌های URL را که توسط مرورگر کلاینت ارسال می‌شوند، ردیابی کنیم؟

ما می‌توانیم از دو متغیر استفاده کنیم: $_SERVER['REQUEST_URI] و $_SERVER['HTTP_REFERER]، که دو مورد اطلاعات هستند که توسط مرورگرهای کلاینت در هدرهای HTTP آنها ارسال می‌شوند.

$_SERVER['REQUEST_URI]: این URI است که توسط کلاینت درخواست شده است. برای مثال

/apparticles.php?action=addArticle&phase=0&PHPSESSID=[PHPSESSID]

$_SERVER['HTTP_REFERER]: این URL است که قبل از URL جدید که مرورگر در حال درخواست آن است، در مرورگر نمایش داده شده بود (URI قبلی). برای مثال، اگر مرورگری که URI مذکور را نمایش داده، درخواست جدیدی به سرور ارسال کند، متغیر $_SERVER['HTTP_REFERER'] سرور مقدار خواهد داشت

http://machine:port//apparticles.php?action=addArticle&phase=0&PHPSESSID=[PHPSESSID]

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

در طول مرحله ۱:

  • ما URI (URI1) درخواستی را ثبت می‌کنیم و آن را در جلسه ثبت می‌کنیم

در طول اقدام ۲:

  • HTTP-REFERER را از اقدام ۲ بازیابی کنید. از این طریق، URI (URI2) را از URL که قبلاً در مرورگری که درخواست را ارسال کرده بود نمایش داده شده بود، استخراج می‌کنیم.
  • URI و URI1 از جلسه بازیابی می‌شوند؛ URI مربوط به عملی است که قبلاً از سرور درخواست شده بود
  • اگر عمل ۲ پس از عمل ۱ انجام شود، آنگاه URI2 باید برابر URI1 باشد. اگر اینطور نباشد، عمل درخواست‌شده رد شده و صفحه احراز هویت نمایش داده می‌شود.
  • ما مقادیر URI و URI2 را از اقدام فعلی در جلسه (session) برای تأیید اقدام بعدی ثبت می‌کنیم. و به همین ترتیب.

در اینجا یک مثال آورده شده است. پس از احراز هویت، پیوند [Ajouter un article] انتخاب می‌شود:

Image

کد URL برای این صفحه عبارت است از:

http://localhost:81/st/php/articles/management/articles8/apparticles.php?action=addArticle&phase=0&PHPSESSID=006a63e6027f16c70b63cdae93405eeb

مستقیماً در فیلد [Adresse] مرورگر، URL را به شرح زیر تغییر می‌دهیم:

http://localhost:81/st/php/articles/management/articles8/apparticles.php?action=deleteArticle&phase=1&PHPSESSID=006a63e6027f16c70b63cdae93405eeb

سپس به صفحهٔ احراز هویت هدایت می‌شویم:

Image

این موضوع نیاز به توضیح دارد. هنگامی که با تایپ مستقیم HTTP_REFERER در نوار آدرس مرورگر، URL را درخواست می‌کنید، مرورگر هدر HTTP_REFERER را ارسال نمی‌کند. بنابراین، برنامه ما نمی‌تواند URI مربوط به اقدام قبلی—URI—را که در جلسه (session) ذخیره کرده بود، پیدا کند. سپس در پاسخ، صفحه احراز هویت را بازمی‌گرداند.

این مکانیزم برای مرورگرهای وب به خوبی عمل می‌کند اما برای یک کلاینت برنامه‌نویسی‌شده کاملاً بی‌اثر است. چنین کلاینتی می‌تواند هر سربرگی با شناسه HTTP_REFERER را که بخواهد ارسال کند. بنابراین می‌تواند با ادعای اینکه واقعاً یک مرحله خاص را پشت سر گذاشته است، در حالی که در واقع این کار را نکرده، «تقلب» کند. بنابراین لازم است اطمینان حاصل شود که توالی مراحل رعایت می‌شود. بنابراین، اگر اقدام درخواستی action=addArticle&phase=1 (ورودی داده)، آنگاه اقدام قبلی لزوماً باید action=deleteArticle&phase=0 (درخواست اولیه برای صفحه ورود داده) یا action=addArticle&phase=2 (بازگشت به ورود داده پس از افزودن نادرست) باشد. به همین ترتیب، اگر اقدام درخواستی action=addArticle&phase=2 (افزودن) باشد، آنگاه اقدام قبلی باید action=addArticle&phase=1 (ورود) باشد. کاربر را می‌توان مجبور به رعایت این توالی‌ها کرد.

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

  // احراز هویت
  $dPrec['authentifier']['0']=array();
  $dPrec['authentifier']['1']=array(
         array('action'=>'authentifier','phase'=>'0'),
    array('action'=>'authentifier','phase'=>'2')
  );
  $dPrec['authentifier']['2']=array(
         array('action'=>'authentifier','phase'=>'1'),
  );

   // افزودن مقاله
  $dPrec['addarticle']['0']=array();  
  $dPrec['addarticle']['1']=array(
         array('action'=>'addarticle','phase'=>'0'),
    array('action'=>'addarticle','phase'=>'2')
  );
  $dPrec['addarticle']['2']=array(
         array('action'=>'addarticle','phase'=>'1'),
  );

   // ویرایش آیتم
  $dPrec['updatearticle']['0']=array();  
  $dPrec['updatearticle']['1']=array(
         array('action'=>'updatearticle','phase'=>'0'),
  );
  $dPrec['updatearticle']['2']=array(
         array('action'=>'updatearticle','phase'=>'1'),
    array('action'=>'updatearticle','phase'=>'3')
  );
  $dPrec['updatearticle']['3']=array(
         array('action'=>'updatearticle','phase'=>'2'),
  );

   // حذف آیتم
  $dPrec['deletearticle']['0']=array();  
  $dPrec['deletearticle']['1']=array(
         array('action'=>'deletearticle','phase'=>'0'),
  );
  $dPrec['deletearticle']['2']=array(
         array('action'=>'deletearticle','phase'=>'1'),
  );

      // انتخاب اقلام
  $dPrec['selectarticle']['0']=array();  
  $dPrec['selectarticle']['1']=array(
         array('action'=>'selectarticle','phase'=>'0'),
    array('action'=>'selectarticle','phase'=>'2')
  );
  $dPrec['selectarticle']['2']=array(
         array('action'=>'selectarticle','phase'=>'1'),
  );

      // درخواست مدیر
  $dPrec['sql']['0']=array();  
  $dPrec['sql']['1']=array(
         array('action'=>'sql','phase'=>'0'),
    array('action'=>'sql','phase'=>'2')
  );
  $dPrec['sql']['2']=array(
         array('action'=>'sql','phase'=>'1'),
  );

$dPrec['action']['phase'] جدولی است که شامل اقدامات پیشین و فازی است که به‌عنوان شاخص برای فرهنگ لغت عمل می‌کنند. این اقدامات پیشین نیز با یک فرهنگ لغت با دو کلید «action» و «phase» نمایش داده می‌شوند. اگر یک اقدام بتواند توسط هر اقدامی پیش‌روی شود، آنگاه $dPrec['action']['phase'] یک آرایه خالی خواهد بود. عدم وجود یک عمل در فرهنگ لغت به این معنی است که مجاز نیست. عمل «احراز هویت» را در بالا در نظر بگیرید:

  // احراز هویت
  $dPrec['authentifier']['0']=array();
  $dPrec['authentifier']['1']=array(
         array('action'=>'authentifier','phase'=>'0'),
    array('action'=>'authentifier','phase'=>'2')
  );
  $dPrec['authentifier']['2']=array(
         array('action'=>'authentifier','phase'=>'1'),
  );

کد بالا به این معنی است که عمل action=authentifier&phase=0 ممکن است توسط هر عملی پیش از آن انجام شود، که action=authentifier&phase=1 ممکن است توسط action=authentifier&phase=0 یا action=authentifier&phase=2 پیش‌روی شود، و اینکه action=authentifier&phase=2 ممکن است توسط عمل action=authentifier&phase=1 پیش‌روی شود.

تابع زیر را بنویسید:

  // ---------------------------------------------------------------
  function enchainementOK(&$dConfig, &$dSession, $sAction, $sPhase){
       //بررسی می‌کند که آیا اقدام فعلی ($sAction, $sPhase) می‌تواند پس از اقدام قبلی انجام شود
         //در $dSession ذخیره شده است ['précédent']
         // واژه‌نامه توالی‌های مجاز در $dConfig['précédents'] قرار دارد
         // در صورتی که توالی ممکن باشد، TRUE را برمی‌گرداند، در غیر این صورت FALSE
....

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

<?php
     //مدیریت جدول آیتم
  include "config.php";
  include "articles.php";  

  // جلسه
  session_start();
  $dSession=$_SESSION["session"];

   // اقدامی که باید انجام شود
  $sAction=$_POST["action"] ? $_POST["action"] : $_GET["action"] ? $_GET["action"] : "authentifier";
  $sAction=strtolower($sAction);
   // مرحله ممکن اقدام
  $sPhase=$_POST["phase"] ? $_POST["phase"] : $_GET["phase"] ? $_GET["phase"] : "0";

     //آیا جلسهٔ فعلی وجود دارد؟  
  if(! isset($dSession)){
      // احراز هویت کاربر
    if($sAction=="authentifier" && $sPhase=="0") authentifier_0($dConfig);
    if($sAction=="authentifier" && $sPhase=="1") authentifier_1($dConfig);   
    if($sAction=="authentifier" && $sPhase=="2") authentifier_2($dConfig);    
     // اقدام غیرعادی
    authentifier_0($dConfig);        
  }//اگر – هیچ جلسه‌ای وجود ندارد

     //بازیابی جلسه
  $dSession=unserialize($dSession);

     //آیا توالی اقدامات طبیعی است؟
  if( ! enchainementOK($dConfig,$dSession,$sAction,$sPhase)){
     // ترتیب غیرعادی
    authentifier_0($dConfig);        
  }//اگر

     //پردازش اقدامات
  if($sAction=="authentifier"){
   if($sPhase=="0") authentifier_0($dConfig);  
   if($sPhase=="1") authentifier_1($dConfig);  
   if($sPhase=="2") authentifier_2($dConfig);  
  }//اگر     
  if($sAction=="addarticle"){
...

7.7.3. به‌روزرسانی ظاهر

بیایید به یاد داشته باشیم که یکی از الزامات تعیین‌شده در مرحله طراحی این برنامه، تطبیق‌پذیر بودن آن بود. فرض کنید که پس از چند هفته، متوجه می‌شویم که قابلیت استفاده برنامه نیاز به بهبود دارد. برنامه را طوری اصلاح کنید که ساختار و چیدمان صفحه استاندارد تغییر کند. تغییرات در دو مکان اعمال خواهند شد:

  • در اسکریپت main.php که ساختار صفحهٔ الگو را تعریف می‌کند. این اسکریپت را به‌روزرسانی کنید.
  • در فایل سبک (style sheet) که «ظاهر» برنامه را تعیین می‌کند. آن را تغییر دهید.

7.7.4. بهبود عملکرد

فعلاً، ما یک مرورگر کلاینت-نازک (thin-client) را انتخاب کرده‌ایم: این مرورگر کاری جز نمایش صفحه انجام نمی‌دهد. ما می‌توانیم با قرار دادن اسکریپت‌ها در صفحات وبی که برای آن ارسال می‌کنیم، آن را وادار به پردازش کنیم. این اسکریپت‌ها را می‌توان به زبان‌های مختلفی، به‌ویژه VBScript و JavaScript، نوشت. اینترنت اکسپلورر و نت‌اسکیپ بازار مرورگرها را به نسبت تقریباً ۶۰:۴۰ در اختیار دارند. علاوه بر این، IE فقط روی ویندوز در دسترس است و نه روی یونیکس، برای مثال، جایی که نت‌اسکیپ غالب است. نِت‌اسکیپ به‌طور بومی اسکریپت‌های VBScript را اجرا نمی‌کند، در حالی که هر دو مرورگر اسکریپت‌های JavaScript را اجرا می‌کنند. از آنجا که نت‌اسکیپ هنوز سهم قابل توجهی از بازار مرورگرها را در اختیار دارد، باید از اسکریپت‌های VBScript اجتناب کرد. بنابراین، JavaScript عموماً در اسکریپت‌های سمت کلاینت استفاده می‌شود.

وظایفی که نیاز به دخالت سرور ندارند به اسکریپت‌های سمت کلاینت واگذار می‌شوند. در برنامهٔ ما، مفید است که مرورگر کلاینت تنها پس از اعتبارسنجی، درخواست را به سرور ارسال کند. این بدان معناست که اگر کاربر فیلد [login] را در فرم احراز هویت خالی گذاشته باشد، ارسال درخواست احراز هویت به سرور بی‌فایده است. ترجیحاً باید به کاربر اطلاع داده شود که درخواست او نادرست است:

Image

شایان ذکر است که این کار مانع از بررسی سرور برای خالی نبودن فیلد «login» نخواهد شد، زیرا کلاینت آن لزوماً یک مرورگر نیست و بنابراین ممکن است اعتبارسنجی قبلی انجام نشده باشد. فرض بر این که کلاینت یک مرورگر است، یک خطر بزرگ برای امنیت برنامه به همراه دارد.

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

برای بازگشت به مثال قبلی، اسکریپت login.php که صفحه احراز هویت را تولید می‌کند، به شکل زیر درمی‌آید:


<script language="javascript">
    function check(){
       //بررسی کنید که ورود انجام شده است
    with(document.frmLogin){
        champs=/^\s*$/.exec(txtLogin.value);
      if(champs!=null){
          // ورود وجود ندارد
        alert("Vous n'avez pas indiqué de login");
        txtLogin.focus();
        return;
      }//if
       // اگر داده‌ها موجود باشند – آن را به سرور ارسال کنید
      submit();
    }//با
  }//بررسی
</script>   
    
<form name="frmLogin" method="post" action="<?php echo $main["post"] ?>">
    <table>
        <tr>
            <td>login</td>
            <td><input type="text" value="<?php echo $main["login"] ?>" name="txtLogin" class="text"></td>
        </tr>
        <tr>
            <td>mot de passe</td>
            <td><input type="password" value="" name="txtMdp" class="text"></td>
      <td><input type="button" onclick="check()" value="Connexion" class="submit"></td>      
        </tr>
    </table>
</form>    

7.8. مطالعهٔ بیشتر

در پایان، در اینجا چند پیشنهاد برای پیشبرد این مطالعه موردی ارائه می‌شود:

  • جالب خواهد بود که ببینیم آیا می‌توان صفحهٔ استاندارد این برنامه را به صورت یک کلاس پیاده‌سازی کرد. سپس می‌توان از آن در سایر برنامه‌ها استفاده کرد.
  • اپلیکیشن ما برای کلاینت‌های مبتنی بر مرورگر مناسب است اما برای کلاینت‌های «اپلیکیشن مستقل» کمتر مناسب است. این کلاینت‌ها باید:
    • یک اتصال TCP با سرور برقرار کنند
    • با استفاده از HTTP با آن «ارتباط برقرار کنند»
    • پاسخ‌های آن را تحلیل کند HTML تا اطلاعات مورد نظر را بیابد، زیرا کلاینت مستقل احتمالاً به کد نمایش HTML که برای مرورگرها در نظر گرفته شده است، علاقه‌ای نخواهد داشت.

برای برنامه ما مفید خواهد بود که XML را به جای HTML تولید کند. سپس مشتریان آن می‌توانند یا مرورگرهای وب (حداقل نسخه‌های جدید) یا برنامه‌های مستقل باشند. مورد دوم در یافتن اطلاعاتی که به دنبال آن هستند، هیچ مشکلی نخواهد داشت، زیرا پاسخ سرور XML حاوی هیچ‌گونه اطلاعات نمایشی نبوده و تنها شامل محتوا خواهد بود.

  • قطعاً بررسی دسترسی همزمان به پایگاه داده مقالات ارزشمند است. حداقل دو نکته برای روشن شدن وجود دارد:
  1. آیا SGBD مورد استفاده توسط برنامه، دسترسی همزمان به یک مقاله را به درستی مدیریت می‌کند؟ برای مثال، اگر دو کاربر همزمان یک مقاله را ویرایش کنند (آنها همزمان دکمه [Modifier] را فشار می‌دهند) چه اتفاقی می‌افتد؟ این احتمالاً به SGBD زیربنایی بستگی دارد.
  2. در حال حاضر، برنامه ما دسترسی همزمان را مدیریت نمی‌کند. با این حال، پایگاه داده باید در وضعیت سازگار باقی بماند، هرچند ممکن است رفتار غیرمنتظره‌ای رخ دهد. بیایید توالی رویدادهای زیر را در نظر بگیریم:
      • کاربر U1 وارد حالت ویرایش برای یک آیتم می‌شود
      • کاربر U2 کمی بعد تلاش می‌کند همان مقاله را حذف کند
      • هر یک از این دو اقدام نیازمند ارتباط کلاینت-سرور است. بسته به نحوه کار هر کاربر، ممکن است کاربر U2 کار خود را قبل از U1 به پایان برساند. وقتی کاربر دوم ویرایش‌های خود را تمام کرده و از طریق [Modifier] آن را ارسال می‌کند، در پاسخ صفحهٔ اطلاعاتی را دریافت خواهد کرد که در آن SGBD نشان می‌دهد [0 ligne(s) ont été modifiées]، زیرا صفحه‌ای که می‌خواست ویرایش کند در این فاصله حذف شده است. کاربر بی‌شک متعجب خواهد شد. از منظر تجربه کاربری، احتمالاً بهتر است صفحه‌ای نمایش داده شود که خطا را واضح‌تر کند. علاوه بر این، می‌توان در نظر گرفت که به محض شروع ویرایش یک مقاله توسط کاربر، دسترسی انحصاری به آن مقاله به او داده شود. کاربر دیگری که بخواهد همان مقاله را ویرایش کند، مطلع خواهد شد که ویرایش دیگری در حال انجام است. این امر زمانی مشکل‌ساز می‌شود که کاربر اول در ذخیره کردن تغییرات خود تأخیر زیادی داشته باشد: سایرین مسدود خواهند شد. در اینجا باید راه‌حل‌هایی یافت، و این راه‌حل‌ها تا حد زیادی به قابلیت‌های SGBD مورد استفاده بستگی دارد. برای مثال، Oracle در این زمینه قابلیت‌های بیشتری نسبت به MySQL دارد.